diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-08 15:09:37 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-08 15:09:37 +0300 |
commit | 96897f83e965318f70032eea0196c4c0b807b1d4 (patch) | |
tree | 80cd64c9ad08215adffdc3be89497ad1fdab690e /spec/frontend | |
parent | 5bdbc604c8a08f827c3833e2c28ec0c299bb41fc (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
17 files changed, 1203 insertions, 71 deletions
diff --git a/spec/frontend/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js index f4fecb68b64..00781c16609 100644 --- a/spec/frontend/ide/components/new_dropdown/index_spec.js +++ b/spec/frontend/ide/components/new_dropdown/index_spec.js @@ -23,9 +23,9 @@ describe('new dropdown component', () => { tree: [], }; - jest.spyOn(vm, 'openNewEntryModal').mockImplementation(() => {}); - vm.$mount(); + + jest.spyOn(vm.$refs.newModal, 'open').mockImplementation(() => {}); }); afterEach(() => { @@ -43,16 +43,16 @@ describe('new dropdown component', () => { }); describe('createNewItem', () => { - it('sets modalType to blob when new file is clicked', () => { + it('opens modal for a blob when new file is clicked', () => { vm.$el.querySelectorAll('.dropdown-menu button')[0].click(); - expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'blob', path: '' }); + expect(vm.$refs.newModal.open).toHaveBeenCalledWith('blob', ''); }); - it('sets modalType to tree when new directory is clicked', () => { + it('opens modal for a tree when new directory is clicked', () => { vm.$el.querySelectorAll('.dropdown-menu button')[2].click(); - expect(vm.openNewEntryModal).toHaveBeenCalledWith({ type: 'tree', path: '' }); + expect(vm.$refs.newModal.open).toHaveBeenCalledWith('tree', ''); }); }); diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js index 2f10bf787b3..23da4df188b 100644 --- a/spec/frontend/ide/components/new_dropdown/modal_spec.js +++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js @@ -14,55 +14,48 @@ describe('new file modal component', () => { vm.$destroy(); }); - describe.each(['tree', 'blob'])('%s', type => { - beforeEach(() => { + describe.each` + entryType | modalTitle | btnTitle | showsFileTemplates + ${'tree'} | ${'Create new directory'} | ${'Create directory'} | ${false} + ${'blob'} | ${'Create new file'} | ${'Create file'} | ${true} + `('$entryType', ({ entryType, modalTitle, btnTitle, showsFileTemplates }) => { + beforeEach(done => { const store = createStore(); - store.state.entryModal = { - type, - path: '', - entry: { - path: '', - }, - }; vm = createComponentWithStore(Component, store).$mount(); - + vm.open(entryType); vm.name = 'testing'; - }); - it(`sets modal title as ${type}`, () => { - const title = type === 'tree' ? 'directory' : 'file'; + vm.$nextTick(done); + }); - expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`); + afterEach(() => { + vm.close(); }); - it(`sets button label as ${type}`, () => { - const title = type === 'tree' ? 'directory' : 'file'; + it(`sets modal title as ${entryType}`, () => { + expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle); + }); - expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`); + it(`sets button label as ${entryType}`, () => { + expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle); }); - it(`sets form label as ${type}`, () => { - expect(vm.$el.querySelector('.label-bold').textContent.trim()).toBe('Name'); + it(`sets form label as ${entryType}`, () => { + expect(document.querySelector('.label-bold').textContent.trim()).toBe('Name'); }); - it(`${type === 'tree' ? 'does not show' : 'shows'} file templates`, () => { - const templateFilesEl = vm.$el.querySelector('.file-templates'); - if (type === 'tree') { - expect(templateFilesEl).toBeNull(); - } else { - expect(templateFilesEl instanceof Element).toBeTruthy(); - } + it(`shows file templates: ${showsFileTemplates}`, () => { + const templateFilesEl = document.querySelector('.file-templates'); + expect(Boolean(templateFilesEl)).toBe(showsFileTemplates); }); }); describe('rename entry', () => { beforeEach(() => { const store = createStore(); - store.state.entryModal = { - type: 'rename', - path: '', - entry: { + store.state.entries = { + 'test-path': { name: 'test', type: 'blob', path: 'test-path', @@ -72,23 +65,29 @@ describe('new file modal component', () => { vm = createComponentWithStore(Component, store).$mount(); }); - ['tree', 'blob'].forEach(type => { - it(`renders title and button for renaming ${type}`, done => { - const text = type === 'tree' ? 'folder' : 'file'; - - vm.$store.state.entryModal.entry.type = type; + it.each` + entryType | modalTitle | btnTitle + ${'tree'} | ${'Rename folder'} | ${'Rename folder'} + ${'blob'} | ${'Rename file'} | ${'Rename file'} + `( + 'renders title and button for renaming $entryType', + ({ entryType, modalTitle, btnTitle }, done) => { + vm.$store.state.entries['test-path'].type = entryType; + vm.open('rename', 'test-path'); vm.$nextTick(() => { - expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Rename ${text}`); - expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Rename ${text}`); + expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle); + expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle); done(); }); - }); - }); + }, + ); describe('entryName', () => { it('returns entries name', () => { + vm.open('rename', 'test-path'); + expect(vm.entryName).toBe('test-path'); }); @@ -115,15 +114,6 @@ describe('new file modal component', () => { describe('submitForm', () => { it('throws an error when target entry exists', () => { const store = createStore(); - store.state.entryModal = { - type: 'rename', - path: 'test-path/test', - entry: { - name: 'test', - type: 'blob', - path: 'test-path/test', - }, - }; store.state.entries = { 'test-path/test': { name: 'test', @@ -132,6 +122,7 @@ describe('new file modal component', () => { }; vm = createComponentWithStore(Component, store).$mount(); + vm.open('rename', 'test-path/test'); expect(createFlash).not.toHaveBeenCalled(); diff --git a/spec/frontend/ide/stores/mutations_spec.js b/spec/frontend/ide/stores/mutations_spec.js index 5d0fe35a10e..bca355b820a 100644 --- a/spec/frontend/ide/stores/mutations_spec.js +++ b/spec/frontend/ide/stores/mutations_spec.js @@ -339,23 +339,6 @@ describe('Multi-file store mutations', () => { }); }); - describe('OPEN_NEW_ENTRY_MODAL', () => { - it('sets entryModal', () => { - localState.entries.testPath = file(); - - mutations.OPEN_NEW_ENTRY_MODAL(localState, { - type: 'test', - path: 'testPath', - }); - - expect(localState.entryModal).toEqual({ - type: 'test', - path: 'testPath', - entry: localState.entries.testPath, - }); - }); - }); - describe('RENAME_ENTRY', () => { beforeEach(() => { localState.trees = { diff --git a/spec/frontend/monitoring/store/variable_mapping_spec.js b/spec/frontend/monitoring/store/variable_mapping_spec.js new file mode 100644 index 00000000000..5081092a519 --- /dev/null +++ b/spec/frontend/monitoring/store/variable_mapping_spec.js @@ -0,0 +1,150 @@ +import { parseTemplatingVariables } from '~/monitoring/stores/variable_mapping'; + +describe('parseTemplatingVariables', () => { + const generateMockTemplatingData = data => { + const vars = data + ? { + variables: { + ...data, + }, + } + : {}; + return { + dashboard: { + templating: vars, + }, + }; + }; + + const simpleVar = ['value1', 'value2', 'value3']; + const advVar = { + label: 'Advanced Var', + type: 'custom', + options: { + values: [ + { value: 'value1', text: 'Var 1 Option 1' }, + { + value: 'value2', + text: 'Var 1 Option 2', + default: true, + }, + ], + }, + }; + const advVarWithoutOptions = { + type: 'custom', + options: {}, + }; + const advVarWithoutLabel = { + type: 'custom', + options: { + values: [ + { value: 'value1', text: 'Var 1 Option 1' }, + { + value: 'value2', + text: 'Var 1 Option 2', + default: true, + }, + ], + }, + }; + const advVarWithoutType = { + label: 'Variable 2', + options: { + values: [ + { value: 'value1', text: 'Var 1 Option 1' }, + { + value: 'value2', + text: 'Var 1 Option 2', + default: true, + }, + ], + }, + }; + + const responseForSimpleCustomVariable = { + simpleVar: { + label: 'simpleVar', + options: [ + { + default: false, + text: 'value1', + value: 'value1', + }, + { + default: false, + text: 'value2', + value: 'value2', + }, + { + default: false, + text: 'value3', + value: 'value3', + }, + ], + type: 'custom', + }, + }; + + const responseForAdvancedCustomVariableWithoutOptions = { + advVarWithoutOptions: { + label: 'advVarWithoutOptions', + options: [], + type: 'custom', + }, + }; + + const responseForAdvancedCustomVariableWithoutLabel = { + advVarWithoutLabel: { + label: 'advVarWithoutLabel', + options: [ + { + default: false, + text: 'Var 1 Option 1', + value: 'value1', + }, + { + default: true, + text: 'Var 1 Option 2', + value: 'value2', + }, + ], + type: 'custom', + }, + }; + + const responseForAdvancedCustomVariable = { + ...responseForSimpleCustomVariable, + advVar: { + label: 'Advanced Var', + options: [ + { + default: false, + text: 'Var 1 Option 1', + value: 'value1', + }, + { + default: true, + text: 'Var 1 Option 2', + value: 'value2', + }, + ], + type: 'custom', + }, + }; + + it.each` + case | input | expected + ${'Returns empty object for no dashboard input'} | ${{}} | ${{}} + ${'Returns empty object for empty dashboard input'} | ${{ dashboard: {} }} | ${{}} + ${'Returns empty object for empty templating prop'} | ${generateMockTemplatingData()} | ${{}} + ${'Returns empty object for empty variables prop'} | ${generateMockTemplatingData({})} | ${{}} + ${'Returns parsed object for simple variable'} | ${generateMockTemplatingData({ simpleVar })} | ${responseForSimpleCustomVariable} + ${'Returns parsed object for advanced variable without options'} | ${generateMockTemplatingData({ advVarWithoutOptions })} | ${responseForAdvancedCustomVariableWithoutOptions} + ${'Returns parsed object for advanced variable without type'} | ${generateMockTemplatingData({ advVarWithoutType })} | ${{}} + ${'Returns parsed object for advanced variable without label'} | ${generateMockTemplatingData({ advVarWithoutLabel })} | ${responseForAdvancedCustomVariableWithoutLabel} + ${'Returns parsed object for simple and advanced variables'} | ${generateMockTemplatingData({ simpleVar, advVar })} | ${responseForAdvancedCustomVariable} + `('$case', ({ input, expected }) => { + expect(parseTemplatingVariables(input?.dashboard?.templating)).toEqual(expected); + }); +}); diff --git a/spec/frontend/reports/components/grouped_test_reports_app_spec.js b/spec/frontend/reports/components/grouped_test_reports_app_spec.js new file mode 100644 index 00000000000..1a01db391da --- /dev/null +++ b/spec/frontend/reports/components/grouped_test_reports_app_spec.js @@ -0,0 +1,260 @@ +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import state from '~/reports/store/state'; +import component from '~/reports/components/grouped_test_reports_app.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; +import { failedReport } from '../mock_data/mock_data'; +import newFailedTestReports from '../mock_data/new_failures_report.json'; +import newErrorsTestReports from '../mock_data/new_errors_report.json'; +import successTestReports from '../mock_data/no_failures_report.json'; +import mixedResultsTestReports from '../mock_data/new_and_fixed_failures_report.json'; +import resolvedFailures from '../mock_data/resolved_failures.json'; + +describe('Grouped Test Reports App', () => { + let vm; + let mock; + const Component = Vue.extend(component); + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + vm.$store.replaceState(state()); + vm.$destroy(); + mock.restore(); + }); + + describe('with success result', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, successTestReports, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders success summary text', done => { + setImmediate(() => { + 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', + ); + + expect(vm.$el.textContent).toContain( + 'rspec:pg found no changed test results out of 8 total tests', + ); + + expect(vm.$el.textContent).toContain( + 'java ant found no changed test results out of 3 total tests', + ); + done(); + }); + }); + }); + + describe('with 204 result', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(204, {}, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders success summary text', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull(); + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary results are being parsed', + ); + + done(); + }); + }); + }); + + describe('with new failed result', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, newFailedTestReports, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders failed summary text + new badge', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary contained 2 failed out of 11 total tests', + ); + + expect(vm.$el.textContent).toContain('rspec:pg found 2 failed out of 8 total tests'); + + expect(vm.$el.textContent).toContain('New'); + expect(vm.$el.textContent).toContain( + 'java ant found no changed test results out of 3 total tests', + ); + done(); + }); + }); + }); + + describe('with new error result', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, newErrorsTestReports, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders error summary text + new badge', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary contained 2 errors out of 11 total tests', + ); + + expect(vm.$el.textContent).toContain('karma found 2 errors out of 3 total tests'); + + expect(vm.$el.textContent).toContain('New'); + expect(vm.$el.textContent).toContain( + 'rspec:pg found no changed test results out of 8 total tests', + ); + done(); + }); + }); + }); + + describe('with mixed results', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, mixedResultsTestReports, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders summary text', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary contained 2 failed and 2 fixed test results out of 11 total tests', + ); + + expect(vm.$el.textContent).toContain( + 'rspec:pg found 1 failed and 2 fixed test results out of 8 total tests', + ); + + expect(vm.$el.textContent).toContain('New'); + expect(vm.$el.textContent).toContain(' java ant found 1 failed out of 3 total tests'); + done(); + }); + }); + }); + + describe('with resolved failures and resolved errors', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, resolvedFailures, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders summary text', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.gl-spinner')).toBeNull(); + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary contained 4 fixed test results out of 11 total tests', + ); + + expect(vm.$el.textContent).toContain( + 'rspec:pg found 4 fixed test results out of 8 total tests', + ); + done(); + }); + }); + + it('renders resolved failures', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.report-block-container').textContent).toContain( + resolvedFailures.suites[0].resolved_failures[0].name, + ); + + expect(vm.$el.querySelector('.report-block-container').textContent).toContain( + resolvedFailures.suites[0].resolved_failures[1].name, + ); + done(); + }); + }); + + it('renders resolved errors', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.report-block-container').textContent).toContain( + resolvedFailures.suites[0].resolved_errors[0].name, + ); + + expect(vm.$el.querySelector('.report-block-container').textContent).toContain( + resolvedFailures.suites[0].resolved_errors[1].name, + ); + done(); + }); + }); + }); + + describe('with a report that failed to load', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, failedReport, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders an error status for the report', done => { + setImmediate(() => { + const { name } = failedReport.suites[0]; + + expect(vm.$el.querySelector('.report-block-list-issue').textContent).toContain( + `An error occurred while loading ${name} results`, + ); + done(); + }); + }); + }); + + describe('with error', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(500, {}, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders loading summary text with loading icon', done => { + setImmediate(() => { + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary failed loading results', + ); + done(); + }); + }); + }); + + describe('while loading', () => { + beforeEach(() => { + mock.onGet('test_results.json').reply(200, {}, {}); + vm = mountComponent(Component, { + endpoint: 'test_results.json', + }); + }); + + it('renders loading summary text with loading icon', done => { + expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull(); + expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual( + 'Test summary results are being parsed', + ); + + setImmediate(() => { + done(); + }); + }); + }); +}); diff --git a/spec/frontend/reports/components/modal_open_name_spec.js b/spec/frontend/reports/components/modal_open_name_spec.js new file mode 100644 index 00000000000..d59f3571c4b --- /dev/null +++ b/spec/frontend/reports/components/modal_open_name_spec.js @@ -0,0 +1,47 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; +import component from '~/reports/components/modal_open_name.vue'; + +Vue.use(Vuex); + +describe('Modal open name', () => { + const Component = Vue.extend(component); + let vm; + + const store = new Vuex.Store({ + actions: { + openModal: () => {}, + }, + state: {}, + mutations: {}, + }); + + beforeEach(() => { + vm = mountComponentWithStore(Component, { + store, + props: { + issue: { + title: 'Issue', + }, + status: 'failed', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders the issue name', () => { + expect(vm.$el.textContent.trim()).toEqual('Issue'); + }); + + it('calls openModal actions when button is clicked', () => { + jest.spyOn(vm, 'openModal').mockImplementation(() => {}); + + vm.$el.click(); + + expect(vm.openModal).toHaveBeenCalled(); + }); +}); diff --git a/spec/frontend/reports/components/modal_spec.js b/spec/frontend/reports/components/modal_spec.js new file mode 100644 index 00000000000..ff046e64b6e --- /dev/null +++ b/spec/frontend/reports/components/modal_spec.js @@ -0,0 +1,54 @@ +import Vue from 'vue'; +import component from '~/reports/components/modal.vue'; +import state from '~/reports/store/state'; +import mountComponent from '../../helpers/vue_mount_component_helper'; +import { trimText } from '../../helpers/text_helper'; + +describe('Grouped Test Reports Modal', () => { + const Component = Vue.extend(component); + const modalDataStructure = state().modal.data; + + // populate data + modalDataStructure.execution_time.value = 0.009411; + modalDataStructure.system_output.value = 'Failure/Error: is_expected.to eq(3)\n\n'; + modalDataStructure.class.value = 'link'; + + let vm; + + beforeEach(() => { + vm = mountComponent(Component, { + title: 'Test#sum when a is 1 and b is 2 returns summary', + modalData: modalDataStructure, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders code block', () => { + expect(vm.$el.querySelector('code').textContent).toEqual( + modalDataStructure.system_output.value, + ); + }); + + it('renders link', () => { + expect(vm.$el.querySelector('.js-modal-link').getAttribute('href')).toEqual( + modalDataStructure.class.value, + ); + + expect(trimText(vm.$el.querySelector('.js-modal-link').textContent)).toEqual( + modalDataStructure.class.value, + ); + }); + + it('renders seconds', () => { + expect(vm.$el.textContent).toContain(`${modalDataStructure.execution_time.value} s`); + }); + + it('render title', () => { + expect(trimText(vm.$el.querySelector('.modal-title').textContent)).toEqual( + 'Test#sum when a is 1 and b is 2 returns summary', + ); + }); +}); diff --git a/spec/frontend/reports/components/summary_row_spec.js b/spec/frontend/reports/components/summary_row_spec.js new file mode 100644 index 00000000000..cb0cc025e80 --- /dev/null +++ b/spec/frontend/reports/components/summary_row_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import component from '~/reports/components/summary_row.vue'; + +describe('Summary row', () => { + const Component = Vue.extend(component); + let vm; + + const props = { + summary: 'SAST detected 1 new vulnerability and 1 fixed vulnerability', + popoverOptions: { + title: 'Static Application Security Testing (SAST)', + content: '<a>Learn more about SAST</a>', + }, + statusIcon: 'warning', + }; + + beforeEach(() => { + vm = mountComponent(Component, props); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders provided summary', () => { + expect( + vm.$el.querySelector('.report-block-list-issue-description-text').textContent.trim(), + ).toEqual(props.summary); + }); + + it('renders provided icon', () => { + expect(vm.$el.querySelector('.report-block-list-icon span').classList).toContain( + 'js-ci-status-icon-warning', + ); + }); +}); diff --git a/spec/frontend/reports/components/test_issue_body_spec.js b/spec/frontend/reports/components/test_issue_body_spec.js new file mode 100644 index 00000000000..ff81020a4eb --- /dev/null +++ b/spec/frontend/reports/components/test_issue_body_spec.js @@ -0,0 +1,72 @@ +import Vue from 'vue'; +import component from '~/reports/components/test_issue_body.vue'; +import createStore from '~/reports/store'; +import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper'; +import { trimText } from '../../helpers/text_helper'; +import { issue } from '../mock_data/mock_data'; + +describe('Test Issue body', () => { + let vm; + const Component = Vue.extend(component); + const store = createStore(); + + const commonProps = { + issue, + status: 'failed', + }; + + afterEach(() => { + vm.$destroy(); + }); + + describe('on click', () => { + it('calls openModal action', () => { + vm = mountComponentWithStore(Component, { + store, + props: commonProps, + }); + + jest.spyOn(vm, 'openModal').mockImplementation(() => {}); + + vm.$el.querySelector('button').click(); + + expect(vm.openModal).toHaveBeenCalledWith({ + issue: commonProps.issue, + }); + }); + }); + + describe('is new', () => { + beforeEach(() => { + vm = mountComponentWithStore(Component, { + store, + props: { ...commonProps, isNew: true }, + }); + }); + + it('renders issue name', () => { + expect(vm.$el.textContent).toContain(commonProps.issue.name); + }); + + it('renders new badge', () => { + expect(trimText(vm.$el.querySelector('.badge').textContent)).toEqual('New'); + }); + }); + + describe('not new', () => { + beforeEach(() => { + vm = mountComponentWithStore(Component, { + store, + props: commonProps, + }); + }); + + it('renders issue name', () => { + expect(vm.$el.textContent).toContain(commonProps.issue.name); + }); + + it('does not renders new badge', () => { + expect(vm.$el.querySelector('.badge')).toEqual(null); + }); + }); +}); diff --git a/spec/frontend/reports/mock_data/mock_data.js b/spec/frontend/reports/mock_data/mock_data.js new file mode 100644 index 00000000000..3caaab2fd79 --- /dev/null +++ b/spec/frontend/reports/mock_data/mock_data.js @@ -0,0 +1,24 @@ +export const issue = { + result: 'failure', + name: 'Test#sum when a is 1 and b is 2 returns summary', + execution_time: 0.009411, + system_output: + "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in \u003ctop (required)\u003e'", +}; + +export const failedReport = { + summary: { total: 11, resolved: 0, errored: 2, failed: 0 }, + suites: [ + { + name: 'rspec:pg', + status: 'error', + summary: { total: 0, resolved: 0, errored: 0, failed: 0 }, + new_failures: [], + resolved_failures: [], + existing_failures: [], + new_errors: [], + resolved_errors: [], + existing_errors: [], + }, + ], +}; diff --git a/spec/frontend/reports/mock_data/new_and_fixed_failures_report.json b/spec/frontend/reports/mock_data/new_and_fixed_failures_report.json new file mode 100644 index 00000000000..6141e5433a6 --- /dev/null +++ b/spec/frontend/reports/mock_data/new_and_fixed_failures_report.json @@ -0,0 +1,55 @@ +{ + "status": "failed", + "summary": { "total": 11, "resolved": 2, "errored": 0, "failed": 2 }, + "suites": [ + { + "name": "rspec:pg", + "status": "failed", + "summary": { "total": 8, "resolved": 2, "errored": 0, "failed": 1 }, + "new_failures": [ + { + "status": "failed", + "name": "Test#subtract when a is 2 and b is 1 returns correct result", + "execution_time": 0.00908, + "system_output": "Failure/Error: is_expected.to eq(1)\n\n expected: 1\n got: 3\n\n (compared using ==)\n./spec/test_spec.rb:43:in `block (4 levels) in <top (required)>'" + } + ], + "resolved_failures": [ + { + "status": "success", + "name": "Test#sum when a is 1 and b is 2 returns summary", + "execution_time": 0.000318, + "system_output": null + }, + { + "status": "success", + "name": "Test#sum when a is 100 and b is 200 returns summary", + "execution_time": 0.000074, + "system_output": null + } + ], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + }, + { + "name": "java ant", + "status": "failed", + "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 1 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [ + { + "status": "failed", + "name": "sumTest", + "execution_time": 0.004, + "system_output": "junit.framework.AssertionFailedError: expected:<3> but was:<-1>\n\tat CalculatorTest.sumTest(Unknown Source)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n\tat java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n\tat java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + } + ], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + } + ] +} diff --git a/spec/frontend/reports/mock_data/new_errors_report.json b/spec/frontend/reports/mock_data/new_errors_report.json new file mode 100644 index 00000000000..cebf98fdb63 --- /dev/null +++ b/spec/frontend/reports/mock_data/new_errors_report.json @@ -0,0 +1,38 @@ +{ + "summary": { "total": 11, "resolved": 0, "errored": 2, "failed": 0 }, + "suites": [ + { + "name": "rspec:pg", + "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 0 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + }, + { + "name": "karma", + "summary": { "total": 3, "resolved": 0, "errored": 2, "failed": 0 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [ + { + "result": "error", + "name": "Test#sum when a is 1 and b is 2 returns summary", + "execution_time": 0.009411, + "system_output": "Failed: Error in render: 'TypeError: Cannot read property 'status' of undefined'" + }, + { + "result": "error", + "name": "Test#sum when a is 100 and b is 200 returns summary", + "execution_time": 0.000162, + "system_output": "Failed: Error in render: 'TypeError: Cannot read property 'length' of undefined'" + } + ], + "resolved_errors": [], + "existing_errors": [] + } + ] +} diff --git a/spec/frontend/reports/mock_data/new_failures_report.json b/spec/frontend/reports/mock_data/new_failures_report.json new file mode 100644 index 00000000000..8b9c12c6271 --- /dev/null +++ b/spec/frontend/reports/mock_data/new_failures_report.json @@ -0,0 +1,38 @@ +{ + "summary": { "total": 11, "resolved": 0, "errored": 0, "failed": 2 }, + "suites": [ + { + "name": "rspec:pg", + "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 2 }, + "new_failures": [ + { + "result": "failure", + "name": "Test#sum when a is 1 and b is 2 returns summary", + "execution_time": 0.009411, + "system_output": "Failure/Error: is_expected.to eq(3)\n\n expected: 3\n got: -1\n\n (compared using ==)\n./spec/test_spec.rb:12:in `block (4 levels) in <top (required)>'" + }, + { + "result": "failure", + "name": "Test#sum when a is 100 and b is 200 returns summary", + "execution_time": 0.000162, + "system_output": "Failure/Error: is_expected.to eq(300)\n\n expected: 300\n got: -100\n\n (compared using ==)\n./spec/test_spec.rb:21:in `block (4 levels) in <top (required)>'" + } + ], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + }, + { + "name": "java ant", + "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + } + ] +} diff --git a/spec/frontend/reports/mock_data/no_failures_report.json b/spec/frontend/reports/mock_data/no_failures_report.json new file mode 100644 index 00000000000..7da9e0c6211 --- /dev/null +++ b/spec/frontend/reports/mock_data/no_failures_report.json @@ -0,0 +1,28 @@ +{ + "status": "success", + "summary": { "total": 11, "resolved": 0, "errored": 0, "failed": 0 }, + "suites": [ + { + "name": "rspec:pg", + "status": "success", + "summary": { "total": 8, "resolved": 0, "errored": 0, "failed": 0 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + }, + { + "name": "java ant", + "status": "success", + "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + } + ] +} diff --git a/spec/frontend/reports/mock_data/resolved_failures.json b/spec/frontend/reports/mock_data/resolved_failures.json new file mode 100644 index 00000000000..49de6aa840b --- /dev/null +++ b/spec/frontend/reports/mock_data/resolved_failures.json @@ -0,0 +1,58 @@ +{ + "status": "success", + "summary": { "total": 11, "resolved": 4, "errored": 0, "failed": 0 }, + "suites": [ + { + "name": "rspec:pg", + "status": "success", + "summary": { "total": 8, "resolved": 4, "errored": 0, "failed": 0 }, + "new_failures": [], + "resolved_failures": [ + { + "status": "success", + "name": "Test#sum when a is 1 and b is 2 returns summary", + "execution_time": 0.000411, + "system_output": null, + "stack_trace": null + }, + { + "status": "success", + "name": "Test#sum when a is 100 and b is 200 returns summary", + "execution_time": 7.6e-5, + "system_output": null, + "stack_trace": null + } + ], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [ + { + "status": "success", + "name": "Test#sum when a is 4 and b is 4 returns summary", + "execution_time": 0.00342, + "system_output": null, + "stack_trace": null + }, + { + "status": "success", + "name": "Test#sum when a is 40 and b is 400 returns summary", + "execution_time": 0.0000231, + "system_output": null, + "stack_trace": null + } + ], + "existing_errors": [] + }, + { + "name": "java ant", + "status": "success", + "summary": { "total": 3, "resolved": 0, "errored": 0, "failed": 0 }, + "new_failures": [], + "resolved_failures": [], + "existing_failures": [], + "new_errors": [], + "resolved_errors": [], + "existing_errors": [] + } + ] +} diff --git a/spec/frontend/reports/store/actions_spec.js b/spec/frontend/reports/store/actions_spec.js new file mode 100644 index 00000000000..3f189736922 --- /dev/null +++ b/spec/frontend/reports/store/actions_spec.js @@ -0,0 +1,171 @@ +import MockAdapter from 'axios-mock-adapter'; +import testAction from 'helpers/vuex_action_helper'; +import { TEST_HOST } from 'helpers/test_constants'; +import axios from '~/lib/utils/axios_utils'; +import { + setEndpoint, + requestReports, + fetchReports, + stopPolling, + clearEtagPoll, + receiveReportsSuccess, + receiveReportsError, + openModal, + setModalData, +} from '~/reports/store/actions'; +import state from '~/reports/store/state'; +import * as types from '~/reports/store/mutation_types'; + +describe('Reports Store Actions', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('setEndpoint', () => { + it('should commit SET_ENDPOINT mutation', done => { + testAction( + setEndpoint, + 'endpoint.json', + mockedState, + [{ type: types.SET_ENDPOINT, payload: 'endpoint.json' }], + [], + done, + ); + }); + }); + + describe('requestReports', () => { + it('should commit REQUEST_REPORTS mutation', done => { + testAction(requestReports, null, mockedState, [{ type: types.REQUEST_REPORTS }], [], done); + }); + }); + + describe('fetchReports', () => { + let mock; + + beforeEach(() => { + mockedState.endpoint = `${TEST_HOST}/endpoint.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + stopPolling(); + clearEtagPoll(); + }); + + describe('success', () => { + it('dispatches requestReports and receiveReportsSuccess ', done => { + mock + .onGet(`${TEST_HOST}/endpoint.json`) + .replyOnce(200, { summary: {}, suites: [{ name: 'rspec' }] }); + + testAction( + fetchReports, + null, + mockedState, + [], + [ + { + type: 'requestReports', + }, + { + payload: { data: { summary: {}, suites: [{ name: 'rspec' }] }, status: 200 }, + type: 'receiveReportsSuccess', + }, + ], + done, + ); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500); + }); + + it('dispatches requestReports and receiveReportsError ', done => { + testAction( + fetchReports, + null, + mockedState, + [], + [ + { + type: 'requestReports', + }, + { + type: 'receiveReportsError', + }, + ], + done, + ); + }); + }); + }); + + describe('receiveReportsSuccess', () => { + it('should commit RECEIVE_REPORTS_SUCCESS mutation with 200', done => { + testAction( + receiveReportsSuccess, + { data: { summary: {} }, status: 200 }, + mockedState, + [{ type: types.RECEIVE_REPORTS_SUCCESS, payload: { summary: {} } }], + [], + done, + ); + }); + + it('should not commit RECEIVE_REPORTS_SUCCESS mutation with 204', done => { + testAction( + receiveReportsSuccess, + { data: { summary: {} }, status: 204 }, + mockedState, + [], + [], + done, + ); + }); + }); + + describe('receiveReportsError', () => { + it('should commit RECEIVE_REPORTS_ERROR mutation', done => { + testAction( + receiveReportsError, + null, + mockedState, + [{ type: types.RECEIVE_REPORTS_ERROR }], + [], + done, + ); + }); + }); + + describe('openModal', () => { + it('should dispatch setModalData', done => { + testAction( + openModal, + { name: 'foo' }, + mockedState, + [], + [{ type: 'setModalData', payload: { name: 'foo' } }], + done, + ); + }); + }); + + describe('setModalData', () => { + it('should commit SET_ISSUE_MODAL_DATA', done => { + testAction( + setModalData, + { name: 'foo' }, + mockedState, + [{ type: types.SET_ISSUE_MODAL_DATA, payload: { name: 'foo' } }], + [], + done, + ); + }); + }); +}); diff --git a/spec/frontend/reports/store/mutations_spec.js b/spec/frontend/reports/store/mutations_spec.js new file mode 100644 index 00000000000..9446cd454ab --- /dev/null +++ b/spec/frontend/reports/store/mutations_spec.js @@ -0,0 +1,126 @@ +import state from '~/reports/store/state'; +import mutations from '~/reports/store/mutations'; +import * as types from '~/reports/store/mutation_types'; +import { issue } from '../mock_data/mock_data'; + +describe('Reports Store Mutations', () => { + let stateCopy; + + beforeEach(() => { + stateCopy = state(); + }); + + describe('SET_ENDPOINT', () => { + it('should set endpoint', () => { + mutations[types.SET_ENDPOINT](stateCopy, 'endpoint.json'); + + expect(stateCopy.endpoint).toEqual('endpoint.json'); + }); + }); + + describe('REQUEST_REPORTS', () => { + it('should set isLoading to true', () => { + mutations[types.REQUEST_REPORTS](stateCopy); + + expect(stateCopy.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_REPORTS_SUCCESS', () => { + const mockedResponse = { + summary: { + total: 14, + resolved: 0, + failed: 7, + }, + suites: [ + { + name: 'build:linux', + summary: { + total: 2, + resolved: 0, + failed: 1, + }, + new_failures: [ + { + name: 'StringHelper#concatenate when a is git and b is lab returns summary', + execution_time: 0.0092435, + system_output: "Failure/Error: is_expected.to eq('gitlab')", + }, + ], + resolved_failures: [ + { + name: 'StringHelper#concatenate when a is git and b is lab returns summary', + execution_time: 0.009235, + system_output: "Failure/Error: is_expected.to eq('gitlab')", + }, + ], + existing_failures: [ + { + name: 'StringHelper#concatenate when a is git and b is lab returns summary', + execution_time: 1232.08, + system_output: "Failure/Error: is_expected.to eq('gitlab')", + }, + ], + }, + ], + }; + + beforeEach(() => { + mutations[types.RECEIVE_REPORTS_SUCCESS](stateCopy, mockedResponse); + }); + + it('should reset isLoading', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('should reset hasError', () => { + expect(stateCopy.hasError).toEqual(false); + }); + + it('should set summary counts', () => { + expect(stateCopy.summary.total).toEqual(mockedResponse.summary.total); + expect(stateCopy.summary.resolved).toEqual(mockedResponse.summary.resolved); + expect(stateCopy.summary.failed).toEqual(mockedResponse.summary.failed); + }); + + it('should set reports', () => { + expect(stateCopy.reports).toEqual(mockedResponse.suites); + }); + }); + + describe('RECEIVE_REPORTS_ERROR', () => { + beforeEach(() => { + mutations[types.RECEIVE_REPORTS_ERROR](stateCopy); + }); + + it('should reset isLoading', () => { + expect(stateCopy.isLoading).toEqual(false); + }); + + it('should set hasError to true', () => { + expect(stateCopy.hasError).toEqual(true); + }); + + it('should reset reports', () => { + expect(stateCopy.reports).toEqual([]); + }); + }); + + describe('SET_ISSUE_MODAL_DATA', () => { + beforeEach(() => { + mutations[types.SET_ISSUE_MODAL_DATA](stateCopy, { + issue, + }); + }); + + it('should set modal title', () => { + expect(stateCopy.modal.title).toEqual(issue.name); + }); + + it('should set modal data', () => { + expect(stateCopy.modal.data.execution_time.value).toEqual(issue.execution_time); + expect(stateCopy.modal.data.system_output.value).toEqual(issue.system_output); + }); + }); +}); |