diff options
Diffstat (limited to 'spec/javascripts')
22 files changed, 533 insertions, 487 deletions
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js index 2f72c9ed89d..2fa807657de 100644 --- a/spec/javascripts/badges/components/badge_list_spec.js +++ b/spec/javascripts/badges/components/badge_list_spec.js @@ -60,7 +60,7 @@ describe('BadgeList component', () => { Vue.nextTick() .then(() => { - const loadingIcon = vm.$el.querySelector('.spinner'); + const loadingIcon = vm.$el.querySelector('.gl-spinner'); expect(loadingIcon).toBeVisible(); }) diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js index 4e4d1ae2e99..c82a03a628a 100644 --- a/spec/javascripts/badges/components/badge_spec.js +++ b/spec/javascripts/badges/components/badge_spec.js @@ -15,7 +15,7 @@ describe('Badge component', () => { const buttons = vm.$el.querySelectorAll('button'); return { badgeImage: vm.$el.querySelector('img.project-badge'), - loadingIcon: vm.$el.querySelector('.spinner'), + loadingIcon: vm.$el.querySelector('.gl-spinner'), reloadButton: buttons[buttons.length - 1], }; }; diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 9c9b435d7fd..6774a46ed58 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -148,7 +148,7 @@ describe('Board list component', () => { component.list.loadingMore = true; Vue.nextTick(() => { - expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull(); + expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull(); done(); }); diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js index 504bc51778c..473cc0612ea 100644 --- a/spec/javascripts/boards/components/boards_selector_spec.js +++ b/spec/javascripts/boards/components/boards_selector_spec.js @@ -76,7 +76,8 @@ describe('BoardsSelector', () => { document.querySelector('.js-boards-selector'), ); - vm.$el.querySelector('.js-dropdown-toggle').click(); + // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time + vm.$children[0].$emit('show'); Promise.all([allBoardsResponse, recentBoardsResponse]) .then(() => vm.$nextTick()) diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index 27428197c1c..531686efff1 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -1,3 +1,5 @@ +// Copied to ee/spec/frontend/diffs/mock_data/diff_file.js + export default { submodule: false, submodule_link: null, diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js index 68326e37ae7..5b749b11246 100644 --- a/spec/javascripts/helpers/vue_test_utils_helper.js +++ b/spec/javascripts/helpers/vue_test_utils_helper.js @@ -1,35 +1,5 @@ -const vNodeContainsText = (vnode, text) => - (vnode.text && vnode.text.includes(text)) || - (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); +// No new code should be added to this file. Instead, modify the +// file this one re-exports from. For more detail about why, see: +// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31349 -/** - * Determines whether a `shallowMount` Wrapper contains text - * within one of it's slots. This will also work on Wrappers - * acquired with `find()`, but only if it's parent Wrapper - * was shallowMounted. - * NOTE: Prefer checking the rendered output of a component - * wherever possible using something like `text()` instead. - * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted) - * @param {String} slotName - * @param {String} text - */ -export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) => - Boolean( - shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length, - ); - -/** - * Returns a promise that waits for a mutation to be fired before resolving - * NOTE: There's no reject action here so it will hang if it waits for a mutation that won't happen. - * @param {Object} store - The Vue store that contains the mutations - * @param {String} expectedMutationType - The Mutation to wait for - */ -export const waitForMutation = (store, expectedMutationType) => - new Promise(resolve => { - const unsubscribe = store.subscribe(mutation => { - if (mutation.type === expectedMutationType) { - unsubscribe(); - resolve(); - } - }); - }); +export * from '../../frontend/helpers/vue_test_utils_helper'; diff --git a/spec/javascripts/helpers/vue_test_utils_helper_spec.js b/spec/javascripts/helpers/vue_test_utils_helper_spec.js deleted file mode 100644 index 41714066da5..00000000000 --- a/spec/javascripts/helpers/vue_test_utils_helper_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { shallowWrapperContainsSlotText } from './vue_test_utils_helper'; - -describe('Vue test utils helpers', () => { - describe('shallowWrapperContainsSlotText', () => { - const mockText = 'text'; - const mockSlot = `<div>${mockText}</div>`; - let mockComponent; - - beforeEach(() => { - mockComponent = shallowMount( - { - render(h) { - h(`<div>mockedComponent</div>`); - }, - }, - { - slots: { - default: mockText, - namedSlot: mockSlot, - }, - }, - ); - }); - - it('finds text within shallowWrapper default slot', () => { - expect(shallowWrapperContainsSlotText(mockComponent, 'default', mockText)).toBe(true); - }); - - it('finds text within shallowWrapper named slot', () => { - expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', mockText)).toBe(true); - }); - - it('returns false when text is not present', () => { - const searchText = 'absent'; - - expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false); - expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false); - }); - - it('searches with case-sensitivity', () => { - const searchText = mockText.toUpperCase(); - - expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false); - expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false); - }); - }); -}); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index f28d2c2a882..d3c1cf831bb 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -3,7 +3,9 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import jobApp from '~/jobs/components/job_app.vue'; import createStore from '~/jobs/store'; +import * as types from '~/jobs/store/mutation_types'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; +import { waitForMutation } from 'spec/helpers/vue_test_utils_helper'; import { resetStore } from '../store/helpers'; import job from '../mock_data'; @@ -19,12 +21,24 @@ describe('Job App ', () => { runnerHelpUrl: 'help/runner', deploymentHelpUrl: 'help/deployment', runnerSettingsUrl: 'settings/ci-cd/runners', + variablesSettingsUrl: 'settings/ci-cd/variables', terminalPath: 'jobs/123/terminal', pagePath: `${gl.TEST_HOST}jobs/123`, + projectPath: 'user-name/project-name', logState: 'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D', }; + const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS); + const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => { + mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData }); + mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData); + + vm = mountComponentWithStore(Component, { props, store }); + + return waitForJobReceived(); + }; + beforeEach(() => { mock = new MockAdapter(axios); store = createStore(); @@ -38,103 +52,81 @@ describe('Job App ', () => { describe('while loading', () => { beforeEach(() => { - mock.onGet(props.endpoint).reply(200, job, {}); - mock.onGet(`${props.pagePath}/trace.json`).reply(200, {}); - vm = mountComponentWithStore(Component, { props, store }); + setupAndMount(); }); - it('renders loading icon', done => { + it('renders loading icon', () => { expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull(); expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull(); expect(vm.$el.querySelector('.js-job-content')).toBeNull(); - - setTimeout(() => { - done(); - }, 0); }); }); describe('with successful request', () => { - beforeEach(() => { - mock.onGet(`${props.pagePath}/trace.json`).replyOnce(200, {}); - }); - describe('Header section', () => { describe('job callout message', () => { it('should not render the reason when reason is absent', done => { - mock.onGet(props.endpoint).replyOnce(200, job); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect(vm.shouldRenderCalloutMessage).toBe(false); - - done(); - }, 0); + setupAndMount() + .then(() => { + expect(vm.shouldRenderCalloutMessage).toBe(false); + }) + .then(done) + .catch(done.fail); }); it('should render the reason when reason is present', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { - callout_message: 'There is an unknown failure, please try again', - }), - ); - - vm = mountComponentWithStore(Component, { props, store }); - setTimeout(() => { - expect(vm.shouldRenderCalloutMessage).toBe(true); - done(); - }, 0); + setupAndMount({ + jobData: { + callout_message: 'There is an unkown failure, please try again', + }, + }) + .then(() => { + expect(vm.shouldRenderCalloutMessage).toBe(true); + }) + .then(done) + .catch(done.fail); }); }); describe('triggered job', () => { - beforeEach(() => { + beforeEach(done => { const aYearAgo = new Date(); aYearAgo.setFullYear(aYearAgo.getFullYear() - 1); - mock - .onGet(props.endpoint) - .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() })); - vm = mountComponentWithStore(Component, { props, store }); + setupAndMount({ jobData: { started: aYearAgo.toISOString() } }) + .then(done) + .catch(done.fail); }); - it('should render provided job information', done => { - setTimeout(() => { - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toContain('passed Job #4757 triggered 1 year ago by Root'); - done(); - }, 0); + it('should render provided job information', () => { + expect( + vm.$el + .querySelector('.header-main-content') + .textContent.replace(/\s+/g, ' ') + .trim(), + ).toContain('passed Job #4757 triggered 1 year ago by Root'); }); - it('should render new issue link', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( - job.new_issue_path, - ); - done(); - }, 0); + it('should render new issue link', () => { + expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual( + job.new_issue_path, + ); }); }); describe('created job', () => { it('should render created key', done => { - mock.onGet(props.endpoint).replyOnce(200, job); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect( - vm.$el - .querySelector('.header-main-content') - .textContent.replace(/\s+/g, ' ') - .trim(), - ).toContain('passed Job #4757 created 3 weeks ago by Root'); - done(); - }, 0); + setupAndMount() + .then(() => { + expect( + vm.$el + .querySelector('.header-main-content') + .textContent.replace(/\s+/g, ' ') + .trim(), + ).toContain('passed Job #4757 created 3 weeks ago by Root'); + }) + .then(done) + .catch(done.fail); }); }); }); @@ -142,9 +134,8 @@ describe('Job App ', () => { describe('stuck block', () => { describe('without active runners availabl', () => { it('renders stuck block when there are no runners', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'pending', icon: 'status_pending', @@ -158,23 +149,23 @@ describe('Job App ', () => { online: false, }, tags: [], - }), - ); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull(); + expect( + vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'), + ).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('when available runners can not run specified tag', () => { it('renders tags in stuck block when there are no runners', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'pending', icon: 'status_pending', @@ -187,27 +178,21 @@ describe('Job App ', () => { available: false, online: false, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); + expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('when runners are offline and build has tags', () => { it('renders message about job being stuck because of no runners with the specified tags', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'pending', icon: 'status_pending', @@ -220,48 +205,35 @@ describe('Job App ', () => { available: true, online: true, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); - expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]); + expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); it('does not renders stuck block when there are no runners', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { runners: { available: true }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('unmet prerequisites block', () => { it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { status: { group: 'failed', icon: 'status_failed', @@ -281,104 +253,81 @@ describe('Job App ', () => { available: true, }, tags: [], - }), - ); - vm = mountComponentWithStore(Component, { props, store }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('environments block', () => { it('renders environment block when job has environment', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { deployment_status: { environment: { environment_path: '/path', name: 'foo', }, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render environment block when job has environment', done => { - mock.onGet(props.endpoint).replyOnce(200, job); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); - done(); - }, 0); + setupAndMount() + .then(() => { + expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('erased block', () => { it('renders erased block when `erased` is true', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { erased_by: { username: 'root', web_url: 'gitlab.com/root', }, erased_at: '2016-11-07T11:11:16.525Z', - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render erased block when `erased` is false', done => { - mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { erased_at: null })); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull(); - - done(); - }, 0); + setupAndMount({ + jobData: { + erased_at: null, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); }); describe('empty states block', () => { it('renders empty state when job does not have trace and is not running', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { has_trace: false, status: { group: 'pending', @@ -398,25 +347,18 @@ describe('Job App ', () => { path: '/path', }, }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render empty state when job does not have trace but it is running', done => { - mock.onGet(props.endpoint).replyOnce( - 200, - Object.assign({}, job, { + setupAndMount({ + jobData: { has_trace: false, status: { group: 'running', @@ -425,34 +367,23 @@ describe('Job App ', () => { text: 'running', details_path: 'path', }, - }), - ); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); - - done(); - }, 0); + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }) + .then(done) + .catch(done.fail); }); it('does not render empty state when job has trace but it is not running', done => { - mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { has_trace: true })); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); - - done(); - }, 0); + setupAndMount({ jobData: { has_trace: true } }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }) + .then(done) + .catch(done.fail); + done(); }); it('displays remaining time for a delayed job', done => { @@ -460,120 +391,114 @@ describe('Job App ', () => { spyOn(Date, 'now').and.callFake( () => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds, ); - mock.onGet(props.endpoint).replyOnce(200, { ...delayedJobFixture }); + setupAndMount({ jobData: delayedJobFixture }) + .then(() => { + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - vm = mountComponentWithStore(Component, { - props, - store, - }); - - store.subscribeAction(action => { - if (action.type !== 'receiveJobSuccess') { - return; - } + const title = vm.$el.querySelector('.js-job-empty-state-title'); - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); - - const title = vm.$el.querySelector('.js-job-empty-state-title'); + expect(title).toContainText('01:00:00'); + }) + .then(done) + .catch(done.fail); + }); + }); - expect(title).toContainText('01:00:00'); - done(); - }) - .catch(done.fail); - }); + describe('sidebar', () => { + it('has no blank blocks', done => { + setupAndMount({ + jobData: { + duration: null, + finished_at: null, + erased_at: null, + queued: null, + runner: null, + coverage: null, + tags: [], + cancel_path: null, + }, + }) + .then(() => { + vm.$el.querySelectorAll('.blocks-container > *').forEach(block => { + expect(block.textContent.trim()).not.toBe(''); + }); + }) + .then(done) + .catch(done.fail); }); }); }); describe('archived job', () => { - beforeEach(() => { - mock.onGet(props.endpoint).reply(200, Object.assign({}, job, { archived: true }), {}); - vm = mountComponentWithStore(Component, { - props, - store, - }); + beforeEach(done => { + setupAndMount({ jobData: { archived: true } }) + .then(done) + .catch(done.fail); }); - it('renders warning about job being archived', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull(); - done(); - }, 0); + it('renders warning about job being archived', () => { + expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull(); }); }); describe('non-archived job', () => { - beforeEach(() => { - mock.onGet(props.endpoint).reply(200, job, {}); - vm = mountComponentWithStore(Component, { - props, - store, - }); + beforeEach(done => { + setupAndMount() + .then(done) + .catch(done.fail); }); - it('does not warning about job being archived', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-archived-job ')).toBeNull(); - done(); - }, 0); + it('does not warning about job being archived', () => { + expect(vm.$el.querySelector('.js-archived-job ')).toBeNull(); }); }); describe('trace output', () => { - beforeEach(() => { - mock.onGet(props.endpoint).reply(200, job, {}); - }); - describe('with append flag', () => { it('appends the log content to the existing one', done => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: '<span>More<span>', - status: 'running', - state: 'newstate', - append: true, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - vm.$store.state.trace = 'Update'; - - setTimeout(() => { - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update'); - - done(); - }, 0); + setupAndMount({ + traceData: { + html: '<span>More<span>', + status: 'running', + state: 'newstate', + append: true, + complete: true, + }, + }) + .then(() => { + vm.$store.state.trace = 'Update'; + + return vm.$nextTick(); + }) + .then(() => { + expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update'); + }) + .then(done) + .catch(done.fail); }); }); describe('without append flag', () => { it('replaces the trace', done => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: '<span>Different<span>', - status: 'running', - append: false, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - vm.$store.state.trace = 'Update'; - - setTimeout(() => { - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain( - 'Update', - ); + setupAndMount({ + traceData: { + html: '<span>Different<span>', + status: 'running', + append: false, + complete: true, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain( + 'Update', + ); - expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different'); - done(); - }, 0); + expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain( + 'Different', + ); + }) + .then(done) + .catch(done.fail); }); }); @@ -589,83 +514,76 @@ describe('Job App ', () => { complete: true, }); - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain( - '50 bytes', - ); - done(); - }, 0); + setupAndMount({ + traceData: { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + complete: true, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain( + '50 bytes', + ); + }) + .then(done) + .catch(done.fail); }); }); describe('when size is equal than total', () => { it('does not show the truncated information', done => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: '<span>Update</span>', - status: 'success', - append: false, - size: 100, - total: 100, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); - - setTimeout(() => { - expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain( - '50 bytes', - ); - done(); - }, 0); + setupAndMount({ + traceData: { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 100, + total: 100, + complete: true, + }, + }) + .then(() => { + expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain( + '50 bytes', + ); + }) + .then(done) + .catch(done.fail); }); }); }); describe('trace controls', () => { - beforeEach(() => { - mock.onGet(`${props.pagePath}/trace.json`).reply(200, { - html: '<span>Update</span>', - status: 'success', - append: false, - size: 50, - total: 100, - complete: true, - }); - - vm = mountComponentWithStore(Component, { - props, - store, - }); + beforeEach(done => { + setupAndMount({ + traceData: { + html: '<span>Update</span>', + status: 'success', + append: false, + size: 50, + total: 100, + complete: true, + }, + }) + .then(done) + .catch(done.fail); }); - it('should render scroll buttons', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull(); - expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull(); - done(); - }, 0); + it('should render scroll buttons', () => { + expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull(); + expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull(); }); - it('should render link to raw ouput', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull(); - done(); - }, 0); + it('should render link to raw ouput', () => { + expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull(); }); - it('should render link to erase job', done => { - setTimeout(() => { - expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); - done(); - }, 0); + it('should render link to erase job', () => { + expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull(); }); }); }); diff --git a/spec/javascripts/jobs/components/manual_variables_form_spec.js b/spec/javascripts/jobs/components/manual_variables_form_spec.js new file mode 100644 index 00000000000..093aa905185 --- /dev/null +++ b/spec/javascripts/jobs/components/manual_variables_form_spec.js @@ -0,0 +1,88 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; +import Form from '~/jobs/components/manual_variables_form.vue'; + +describe('Manual Variables Form', () => { + let wrapper; + const requiredProps = { + action: { + path: '/play', + method: 'post', + button_title: 'Trigger this manual action', + }, + variablesSettingsUrl: '/settings', + }; + + const factory = (props = {}) => { + wrapper = shallowMount(Form, { + propsData: props, + }); + }; + + beforeEach(() => { + factory(requiredProps); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders empty form with correct placeholders', () => { + expect(wrapper.find({ ref: 'inputKey' }).attributes('placeholder')).toBe('Input variable key'); + expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('placeholder')).toBe( + 'Input variable value', + ); + }); + + it('renders help text with provided link', () => { + expect(wrapper.find('p').text()).toBe( + 'Specify variable values to be used in this run. The values specified in CI/CD settings will be used as default', + ); + + expect(wrapper.find('a').attributes('href')).toBe(requiredProps.variablesSettingsUrl); + }); + + describe('when adding a new variable', () => { + it('creates a new variable when user types a new key and resets the form', done => { + wrapper.vm + .$nextTick() + .then(() => wrapper.find({ ref: 'inputKey' }).setValue('new key')) + .then(() => { + expect(wrapper.vm.variables.length).toBe(1); + expect(wrapper.vm.variables[0].key).toBe('new key'); + expect(wrapper.find({ ref: 'inputKey' }).attributes('value')).toBe(undefined); + }) + .then(done) + .catch(done.fail); + }); + + it('creates a new variable when user types a new value and resets the form', done => { + wrapper.vm + .$nextTick() + .then(() => wrapper.find({ ref: 'inputSecretValue' }).setValue('new value')) + .then(() => { + expect(wrapper.vm.variables.length).toBe(1); + expect(wrapper.vm.variables[0].secret_value).toBe('new value'); + expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('value')).toBe(undefined); + }) + .then(done) + .catch(done.fail); + }); + }); + + describe('when deleting a variable', () => { + it('removes the variable row', () => { + wrapper.vm.variables = [ + { + key: 'new key', + secret_value: 'value', + id: '1', + }, + ]; + + wrapper.find(GlButton).vm.$emit('click'); + + expect(wrapper.vm.variables.length).toBe(0); + }); + }); +}); diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 1df5cf9ef68..5f81a168498 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -1,3 +1,5 @@ +// Copied to ee/spec/frontend/notes/mock_data.js + export const notesDataMock = { discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json', lastFetchedAt: 1501862675, diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js index 6dea570266b..efeb65acf87 100644 --- a/spec/javascripts/pdf/page_spec.js +++ b/spec/javascripts/pdf/page_spec.js @@ -17,7 +17,7 @@ describe('Page component', () => { pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc; pdfjsLib .getDocument(testPDF) - .then(pdf => pdf.getPage(1)) + .promise.then(pdf => pdf.getPage(1)) .then(page => { testPage = page; }) @@ -31,7 +31,8 @@ describe('Page component', () => { it('renders the page when mounting', done => { const promise = Promise.resolve(); - spyOn(testPage, 'render').and.callFake(() => promise); + spyOn(testPage, 'render').and.returnValue({ promise }); + vm = mountComponent(Component, { page: testPage, number: 1, diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js index 8a7aa057186..0486b5fa3db 100644 --- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js +++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js @@ -44,7 +44,6 @@ describe('detailedMetric', () => { }, metric: 'gitaly', header: 'Gitaly calls', - details: 'details', keys: ['feature', 'request'], }); }); @@ -79,8 +78,32 @@ describe('detailedMetric', () => { }); }); - it('displays the metric name', () => { + it('displays the metric title', () => { expect(vm.$el.innerText).toContain('gitaly'); }); + + describe('when using a custom metric title', () => { + beforeEach(() => { + vm = mountComponent(Vue.extend(detailedMetric), { + currentRequest: { + details: { + gitaly: { + duration: '123ms', + calls: '456', + details: requestDetails, + }, + }, + }, + metric: 'gitaly', + title: 'custom', + header: 'Gitaly calls', + keys: ['feature', 'request'], + }); + }); + + it('displays the custom title', () => { + expect(vm.$el.innerText).toContain('custom'); + }); + }); }); }); diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js index 2fdfff3db03..d15758be5d2 100644 --- a/spec/javascripts/persistent_user_callout_spec.js +++ b/spec/javascripts/persistent_user_callout_spec.js @@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => { return fixture; } + function createDeferredLinkFixture() { + const fixture = document.createElement('div'); + fixture.innerHTML = ` + <div + class="container" + data-dismiss-endpoint="${dismissEndpoint}" + data-feature-id="${featureName}" + data-defer-links="true" + > + <button type="button" class="js-close"></button> + <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a> + <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a> + </div> + `; + + return fixture; + } + describe('dismiss', () => { let button; let mockAxios; @@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => { }); }); + describe('deferred links', () => { + let button; + let deferredLink; + let normalLink; + let mockAxios; + let persistentUserCallout; + let windowSpy; + + beforeEach(() => { + const fixture = createDeferredLinkFixture(); + const container = fixture.querySelector('.container'); + button = fixture.querySelector('.js-close'); + deferredLink = fixture.querySelector('.deferred-link'); + normalLink = fixture.querySelector('.normal-link'); + mockAxios = new MockAdapter(axios); + persistentUserCallout = new PersistentUserCallout(container); + spyOn(persistentUserCallout.container, 'remove'); + windowSpy = spyOn(window, 'open').and.callFake(() => {}); + }); + + afterEach(() => { + mockAxios.restore(); + }); + + it('defers loading of a link until callout is dismissed', done => { + const { href, target } = deferredLink; + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + deferredLink.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).toHaveBeenCalledWith(href, target); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + expect(mockAxios.history.post[0].data).toBe( + JSON.stringify({ feature_name: featureName }), + ); + }) + .then(done) + .catch(done.fail); + }); + + it('does not dismiss callout on non-deferred links', done => { + normalLink.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).not.toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + + it('does not follow link when notification is closed', done => { + mockAxios.onPost(dismissEndpoint).replyOnce(200); + + button.click(); + + setTimeoutPromise() + .then(() => { + expect(windowSpy).not.toHaveBeenCalled(); + expect(persistentUserCallout.container.remove).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('factory', () => { it('returns an instance of PersistentUserCallout with the provided container property', () => { const fixture = createFixture(); diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js index 7b9b8d2b039..e7675669f7a 100644 --- a/spec/javascripts/registry/components/app_spec.js +++ b/spec/javascripts/registry/components/app_spec.js @@ -106,7 +106,7 @@ describe('Registry List', () => { it('should render a loading spinner', done => { Vue.nextTick(() => { - expect(vm.$el.querySelector('.spinner')).not.toBe(null); + expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null); done(); }); }); diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js index a17494966a3..1f1e626ed33 100644 --- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js +++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js @@ -34,7 +34,7 @@ describe('Grouped Test Reports App', () => { it('renders success summary text', done => { setTimeout(() => { - expect(vm.$el.querySelector('.fa-spinner')).toBeNull(); + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Test summary contained no changed test results out of 11 total tests', ); @@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => { it('renders success summary text', done => { setTimeout(() => { - expect(vm.$el.querySelector('.spinner')).not.toBeNull(); + expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Test summary results are being parsed', ); @@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => { it('renders failed summary text + new badge', done => { setTimeout(() => { - expect(vm.$el.querySelector('.spinner')).toBeNull(); + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Test summary contained 2 failed test results out of 11 total tests', ); @@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => { it('renders summary text', done => { setTimeout(() => { - expect(vm.$el.querySelector('.spinner')).toBeNull(); + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests', ); @@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => { it('renders summary text', done => { setTimeout(() => { - expect(vm.$el.querySelector('.spinner')).toBeNull(); + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Test summary contained 2 fixed test results out of 11 total tests', ); @@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => { }); it('renders loading summary text with loading icon', done => { - expect(vm.$el.querySelector('.spinner')).not.toBeNull(); + expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull(); expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( 'Test summary results are being parsed', ); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js index f622f52a7b9..5aac37d28df 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js @@ -18,7 +18,7 @@ describe('MR widget status icon component', () => { it('renders loading icon', () => { vm = mountComponent(Component, { status: 'loading' }); - expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner'); + expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner'); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js index d93badf8cd3..55a11a72551 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js @@ -38,7 +38,9 @@ describe('MRWidgetAutoMergeFailed', () => { Vue.nextTick(() => { expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled'); - expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner'); + expect(vm.$el.querySelector('button .loading-container span').classList).toContain( + 'gl-spinner', + ); done(); }); }); diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js index 96e512d222a..70c70eca746 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js @@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => { }); it('renders loading icon', () => { - expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner'); + expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner'); }); it('renders information about merging', () => { diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js index d6d8eecfcb9..cb656525f06 100644 --- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js +++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js @@ -21,7 +21,7 @@ describe('Squash before merge component', () => { }); describe('checkbox', () => { - const findCheckbox = () => wrapper.find('.qa-squash-checkbox'); + const findCheckbox = () => wrapper.find('.js-squash-checkbox'); it('is unchecked if passed value prop is false', () => { createComponent({ diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js index 5bea8c43da3..1f61e19fa84 100644 --- a/spec/javascripts/vue_shared/components/file_icon_spec.js +++ b/spec/javascripts/vue_shared/components/file_icon_spec.js @@ -72,7 +72,7 @@ describe('File Icon component', () => { const { classList } = vm.$el.querySelector('.loading-container span'); - expect(classList.contains('spinner')).toEqual(true); + expect(classList.contains('gl-spinner')).toEqual(true); }); it('should add a special class and a size class', () => { diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index a9c1a67b39b..2b059e5e9f4 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -88,7 +88,7 @@ describe('Header CI Component', () => { vm.actions[0].isLoading = true; Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy(); + expect(vm.$el.querySelector('.btn .gl-spinner').getAttribute('style')).toBeFalsy(); done(); }); }); diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js index ea74cb9eb21..dc929e83eb7 100644 --- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js @@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => { describe('init', () => { it('renders a suggestion header', () => { - expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull(); + expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull(); }); it('renders a diff table with syntax highlighting', () => { |