Commit c5482796 authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge branch 'dynamic-build-fixture' into 'master'

Create dynamic fixture for build_spec

## What does this MR do?

Replace `spec/javascripts/fixtures/build.html.haml` by a dynamically created fixture (using `rake teaspoon:fixtures`).

## Why was this MR needed?

The existing fixture was not representing the real page. 

## What are the relevant issue numbers?

#24614 would have been avoided

following !6059

See merge request !7589
parents cfb4d65f 31a5ed97
---
title: Create dynamic fixture for build_spec
merge_request: 7589
author: winniehell
...@@ -8,184 +8,177 @@ ...@@ -8,184 +8,177 @@
//= require jquery.nicescroll //= require jquery.nicescroll
//= require turbolinks //= require turbolinks
(() => { describe('Build', () => {
describe('Build', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1`;
fixture.preload('build.html'); // see spec/factories/ci/builds.rb
const BUILD_TRACE = 'BUILD TRACE';
// see lib/ci/ansi2html.rb
const INITIAL_BUILD_TRACE_STATE = window.btoa(JSON.stringify({
offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0,
}));
fixture.preload('builds/build-with-artifacts.html.raw');
beforeEach(() => {
fixture.load('builds/build-with-artifacts.html.raw');
spyOn($, 'ajax');
});
describe('constructor', () => {
beforeEach(() => {
jasmine.clock().install();
});
beforeEach(function () { afterEach(() => {
fixture.load('build.html'); jasmine.clock().uninstall();
spyOn($, 'ajax');
}); });
describe('constructor', () => { describe('setup', () => {
beforeEach(function () { beforeEach(function () {
jasmine.clock().install(); this.build = new Build();
}); });
afterEach(() => { it('copies build options', function () {
jasmine.clock().uninstall(); expect(this.build.pageUrl).toBe(BUILD_URL);
expect(this.build.buildUrl).toBe(`${BUILD_URL}.json`);
expect(this.build.buildStatus).toBe('success');
expect(this.build.buildStage).toBe('test');
expect(this.build.state).toBe(INITIAL_BUILD_TRACE_STATE);
}); });
describe('setup', function () { it('only shows the jobs matching the current stage', () => {
const removeDate = new Date(); expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false);
removeDate.setUTCFullYear(removeDate.getUTCFullYear() + 1); expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true);
// give the test three days to run expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
removeDate.setTime(removeDate.getTime() + (3 * 24 * 60 * 60 * 1000)); });
beforeEach(function () { it('selects the current stage in the build dropdown menu', () => {
const removeDateElement = document.querySelector('.js-artifacts-remove'); expect($('.stage-selection').text()).toBe('test');
removeDateElement.innerText = removeDate.toString(); });
this.build = new Build(); it('updates the jobs when the build dropdown changes', () => {
}); $('.stage-item:contains("build")').click();
it('copies build options', function () { expect($('.stage-selection').text()).toBe('build');
expect(this.build.pageUrl).toBe('http://example.com/root/test-build/builds/2'); expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
expect(this.build.buildUrl).toBe('http://example.com/root/test-build/builds/2.json'); expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect(this.build.buildStatus).toBe('passed'); expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
expect(this.build.buildStage).toBe('test'); });
expect(this.build.state).toBe('buildstate');
});
it('only shows the jobs matching the current stage', function () { it('displays the remove date correctly', () => {
expect($('.build-job[data-stage="build"]').is(':visible')).toBe(false); const removeDateElement = document.querySelector('.js-artifacts-remove');
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(true); expect(removeDateElement.innerText.trim()).toBe('1 year');
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false); });
}); });
it('selects the current stage in the build dropdown menu', function () { describe('initial build trace', () => {
expect($('.stage-selection').text()).toBe('test'); beforeEach(() => {
}); new Build();
});
it('updates the jobs when the build dropdown changes', function () { it('displays the initial build trace', () => {
$('.stage-item:contains("build")').click(); expect($.ajax.calls.count()).toBe(1);
const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0);
expect(url).toBe(`${BUILD_URL}.json`);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
expect($('.stage-selection').text()).toBe('build'); success.call(context, { trace_html: '<span>Example</span>', status: 'running' });
expect($('.build-job[data-stage="build"]').is(':visible')).toBe(true);
expect($('.build-job[data-stage="test"]').is(':visible')).toBe(false);
expect($('.build-job[data-stage="deploy"]').is(':visible')).toBe(false);
});
it('displays the remove date correctly', function () { expect($('#build-trace .js-build-output').text()).toMatch(/Example/);
const removeDateElement = document.querySelector('.js-artifacts-remove');
expect(removeDateElement.innerText.trim()).toBe('1 year');
});
}); });
describe('initial build trace', function () { it('removes the spinner', () => {
beforeEach(function () { const [{ success, context }] = $.ajax.calls.argsFor(0);
new Build(); success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
});
it('displays the initial build trace', function () { expect($('.js-build-refresh').length).toBe(0);
expect($.ajax.calls.count()).toBe(1); });
const [{ url, dataType, success, context }] = $.ajax.calls.argsFor(0); });
expect(url).toBe('http://example.com/root/test-build/builds/2.json');
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, { trace_html: '<span>Example</span>', status: 'running' }); describe('running build', () => {
beforeEach(function () {
$('.js-build-options').data('buildStatus', 'running');
this.build = new Build();
spyOn(this.build, 'location').and.returnValue(BUILD_URL);
});
expect($('#build-trace .js-build-output').text()).toMatch(/Example/); it('updates the build trace on an interval', function () {
jasmine.clock().tick(4001);
expect($.ajax.calls.count()).toBe(2);
let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
expect(url).toBe(
`${BUILD_URL}/trace.json?state=${encodeURIComponent(INITIAL_BUILD_TRACE_STATE)}`
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, {
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
append: true,
}); });
it('removes the spinner', function () { expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
const [{ success, context }] = $.ajax.calls.argsFor(0); expect(this.build.state).toBe('newstate');
success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
expect($('.js-build-refresh').length).toBe(0); jasmine.clock().tick(4001);
expect($.ajax.calls.count()).toBe(3);
[{ url, dataType, success, context }] = $.ajax.calls.argsFor(2);
expect(url).toBe(`${BUILD_URL}/trace.json?state=newstate`);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, {
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
append: true,
}); });
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
expect(this.build.state).toBe('finalstate');
}); });
describe('running build', function () { it('replaces the entire build trace', () => {
beforeEach(function () { jasmine.clock().tick(4001);
$('.js-build-options').data('buildStatus', 'running'); let [{ success, context }] = $.ajax.calls.argsFor(1);
this.build = new Build(); success.call(context, {
spyOn(this.build, 'location') html: '<span>Update</span>',
.and.returnValue('http://example.com/root/test-build/builds/2'); status: 'running',
append: true,
}); });
it('updates the build trace on an interval', function () { expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
jasmine.clock().tick(4001);
expect($.ajax.calls.count()).toBe(2);
let [{ url, dataType, success, context }] = $.ajax.calls.argsFor(1);
expect(url).toBe(
'http://example.com/root/test-build/builds/2/trace.json?state=buildstate'
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, {
html: '<span>Update<span>',
status: 'running',
state: 'newstate',
append: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
expect(this.build.state).toBe('newstate');
jasmine.clock().tick(4001);
expect($.ajax.calls.count()).toBe(3);
[{ url, dataType, success, context }] = $.ajax.calls.argsFor(2);
expect(url).toBe(
'http://example.com/root/test-build/builds/2/trace.json?state=newstate'
);
expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function));
success.call(context, {
html: '<span>More</span>',
status: 'running',
state: 'finalstate',
append: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/UpdateMore/);
expect(this.build.state).toBe('finalstate');
});
it('replaces the entire build trace', function () { jasmine.clock().tick(4001);
jasmine.clock().tick(4001); [{ success, context }] = $.ajax.calls.argsFor(2);
let [{ success, context }] = $.ajax.calls.argsFor(1); success.call(context, {
success.call(context, { html: '<span>Different</span>',
html: '<span>Update</span>', status: 'running',
status: 'running', append: false,
append: true,
});
expect($('#build-trace .js-build-output').text()).toMatch(/Update/);
jasmine.clock().tick(4001);
[{ success, context }] = $.ajax.calls.argsFor(2);
success.call(context, {
html: '<span>Different</span>',
status: 'running',
append: false,
});
expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
}); });
it('reloads the page when the build is done', function () { expect($('#build-trace .js-build-output').text()).not.toMatch(/Update/);
spyOn(Turbolinks, 'visit'); expect($('#build-trace .js-build-output').text()).toMatch(/Different/);
});
jasmine.clock().tick(4001); it('reloads the page when the build is done', () => {
const [{ success, context }] = $.ajax.calls.argsFor(1); spyOn(Turbolinks, 'visit');
success.call(context, {
html: '<span>Final</span>',
status: 'passed',
append: true,
});
expect(Turbolinks.visit).toHaveBeenCalledWith( jasmine.clock().tick(4001);
'http://example.com/root/test-build/builds/2' const [{ success, context }] = $.ajax.calls.argsFor(1);
); success.call(context, {
html: '<span>Final</span>',
status: 'passed',
append: true,
}); });
expect(Turbolinks.visit).toHaveBeenCalledWith(BUILD_URL);
}); });
}); });
}); });
})(); });
.build-page
.prepend-top-default
.autoscroll-container
%button.btn.btn-success.btn-sm#autoscroll-button{:type => "button", :data => {:state => 'disabled'}} enable autoscroll
#js-build-scroll.scroll-controls
%a.btn{href: '#build-trace'}
%i.fa.fa-angle-up
%a.btn{href: '#down-build-trace'}
%i.fa.fa-angle-down
%pre.build-trace#build-trace
%code.bash.js-build-output
%i.fa.fa-refresh.fa-spin.js-build-refresh
%aside.right-sidebar.right-sidebar-expanded.build-sidebar.js-build-sidebar
.block.build-sidebar-header.visible-xs-block.visible-sm-block.append-bottom-default
Build
%strong #1
%a.gutter-toggle.pull-right.js-sidebar-build-toggle{ href: "#" }
%i.fa.fa-angle-double-right
.blocks-container
.dropdown.build-dropdown
.title Stage
%button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'}
%span.stage-selection More
%i.fa.fa-caret-down
%ul.dropdown-menu
%li
%a.stage-item build
%li
%a.stage-item test
%li
%a.stage-item deploy
.builds-container
.build-job{data: {stage: 'build'}}
%a{href: 'http://example.com/root/test-build/builds/1'}
%i.fa.fa-check
%i.fa.fa-check-circle-o
%span
Setup
.build-job{data: {stage: 'test'}}
%a{href: 'http://example.com/root/test-build/builds/2'}
%i.fa.fa-check
%i.fa.fa-check-circle-o
%span
Tests
.build-job{data: {stage: 'deploy'}}
%a{href: 'http://example.com/root/test-build/builds/3'}
%i.fa.fa-check
%i.fa.fa-check-circle-o
%span
Deploy
.js-build-options{ data: { page_url: 'http://example.com/root/test-build/builds/2',
build_url: 'http://example.com/root/test-build/builds/2.json',
build_status: 'passed',
build_stage: 'test',
log_state: 'buildstate' }}
%p.build-detail-row
The artifacts will be removed in
%span.js-artifacts-remove
2016-12-19 09:02:12 UTC
require 'spec_helper'
describe Projects::BuildsController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'builds-project') }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') }
render_views
before(:all) do
clean_frontend_fixtures('builds/')
end
before(:each) do
sign_in(admin)
end
it 'builds/build-with-artifacts.html.raw' do |example|
get :show,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: build_with_artifacts.to_param
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
...@@ -4,7 +4,8 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller ...@@ -4,7 +4,8 @@ describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller
include JavaScriptFixturesHelpers include JavaScriptFixturesHelpers
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:project) { create(:project_empty_repo) } let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
render_views render_views
......
...@@ -74,7 +74,7 @@ ...@@ -74,7 +74,7 @@
it('submits an ajax request on tasklist:changed', function() { it('submits an ajax request on tasklist:changed', function() {
spyOn(jQuery, 'ajax').and.callFake(function(req) { spyOn(jQuery, 'ajax').and.callFake(function(req) {
expect(req.type).toBe('PATCH'); expect(req.type).toBe('PATCH');
expect(req.url).toBe('https://fixture.invalid/namespace3/project3/issues/1.json'); expect(req.url).toBe(gl.TEST_HOST + '/frontend-fixtures/issues-project/issues/1.json'); // eslint-disable-line prefer-template
expect(req.data.issue.description).not.toBe(null); expect(req.data.issue.description).not.toBe(null);
}); });
......
...@@ -41,3 +41,8 @@ ...@@ -41,3 +41,8 @@
}).call(this); }).call(this);
// defined in ActionDispatch::TestRequest
// see https://github.com/rails/rails/blob/v4.2.7.1/actionpack/lib/action_dispatch/testing/test_request.rb#L7
window.gl = window.gl || {};
gl.TEST_HOST = 'http://test.host';
require 'action_dispatch/testing/test_request'
require 'fileutils' require 'fileutils'
require 'gitlab/popen' require 'gitlab/popen'
...@@ -30,13 +31,17 @@ module JavaScriptFixturesHelpers ...@@ -30,13 +31,17 @@ module JavaScriptFixturesHelpers
if response_mime_type.html? if response_mime_type.html?
doc = Nokogiri::HTML::DocumentFragment.parse(fixture) doc = Nokogiri::HTML::DocumentFragment.parse(fixture)
link_tags = doc.css('link')
link_tags.remove
scripts = doc.css('script') scripts = doc.css('script')
scripts.remove scripts.remove
fixture = doc.to_html fixture = doc.to_html
# replace relative links # replace relative links
fixture.gsub!(%r{="/}, '="https://fixture.invalid/') test_host = ActionDispatch::TestRequest::DEFAULT_ENV['HTTP_HOST']
fixture.gsub!(%r{="/}, "=\"http://#{test_host}/")
end end
FileUtils.mkdir_p(File.dirname(fixture_file_name)) FileUtils.mkdir_p(File.dirname(fixture_file_name))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment