diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 12:26:25 +0000 |
commit | a09983ae35713f5a2bbb100981116d31ce99826e (patch) | |
tree | 2ee2af7bd104d57086db360a7e6d8c9d5d43667a /spec/frontend/reports/codequality_report/store | |
parent | 18c5ab32b738c0b6ecb4d0df3994000482f34bd8 (diff) | |
download | gitlab-ce-a09983ae35713f5a2bbb100981116d31ce99826e.tar.gz |
Add latest changes from gitlab-org/gitlab@13-2-stable-ee
Diffstat (limited to 'spec/frontend/reports/codequality_report/store')
4 files changed, 465 insertions, 0 deletions
diff --git a/spec/frontend/reports/codequality_report/store/actions_spec.js b/spec/frontend/reports/codequality_report/store/actions_spec.js new file mode 100644 index 00000000000..6c30fdb7871 --- /dev/null +++ b/spec/frontend/reports/codequality_report/store/actions_spec.js @@ -0,0 +1,151 @@ +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; +import * as actions from '~/reports/codequality_report/store/actions'; +import * as types from '~/reports/codequality_report/store/mutation_types'; +import createStore from '~/reports/codequality_report/store'; +import { TEST_HOST } from 'spec/test_constants'; +import testAction from 'helpers/vuex_action_helper'; +import { headIssues, baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../mock_data'; + +// mock codequality comparison worker +jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () => + jest.fn().mockImplementation(() => { + return { + addEventListener: (eventName, callback) => { + callback({ + data: { + newIssues: [mockParsedHeadIssues[0]], + resolvedIssues: [mockParsedBaseIssues[0]], + }, + }); + }, + }; + }), +); + +describe('Codequality Reports actions', () => { + let localState; + let localStore; + + beforeEach(() => { + localStore = createStore(); + localState = localStore.state; + }); + + describe('setPaths', () => { + it('should commit SET_PATHS mutation', done => { + const paths = { + basePath: 'basePath', + headPath: 'headPath', + baseBlobPath: 'baseBlobPath', + headBlobPath: 'headBlobPath', + helpPath: 'codequalityHelpPath', + }; + + testAction( + actions.setPaths, + paths, + localState, + [{ type: types.SET_PATHS, payload: paths }], + [], + done, + ); + }); + }); + + describe('fetchReports', () => { + let mock; + + beforeEach(() => { + localState.headPath = `${TEST_HOST}/head.json`; + localState.basePath = `${TEST_HOST}/base.json`; + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('on success', () => { + it('commits REQUEST_REPORTS and dispatches receiveReportsSuccess', done => { + mock.onGet(`${TEST_HOST}/head.json`).reply(200, headIssues); + mock.onGet(`${TEST_HOST}/base.json`).reply(200, baseIssues); + + testAction( + actions.fetchReports, + null, + localState, + [{ type: types.REQUEST_REPORTS }], + [ + { + payload: { + newIssues: [mockParsedHeadIssues[0]], + resolvedIssues: [mockParsedBaseIssues[0]], + }, + type: 'receiveReportsSuccess', + }, + ], + done, + ); + }); + }); + + describe('on error', () => { + it('commits REQUEST_REPORTS and dispatches receiveReportsError', done => { + mock.onGet(`${TEST_HOST}/head.json`).reply(500); + + testAction( + actions.fetchReports, + null, + localState, + [{ type: types.REQUEST_REPORTS }], + [{ type: 'receiveReportsError' }], + done, + ); + }); + }); + + describe('with no base path', () => { + it('commits REQUEST_REPORTS and dispatches receiveReportsError', done => { + localState.basePath = null; + + testAction( + actions.fetchReports, + null, + localState, + [{ type: types.REQUEST_REPORTS }], + [{ type: 'receiveReportsError' }], + done, + ); + }); + }); + }); + + describe('receiveReportsSuccess', () => { + it('commits RECEIVE_REPORTS_SUCCESS', done => { + const data = { issues: [] }; + + testAction( + actions.receiveReportsSuccess, + data, + localState, + [{ type: types.RECEIVE_REPORTS_SUCCESS, payload: data }], + [], + done, + ); + }); + }); + + describe('receiveReportsError', () => { + it('commits RECEIVE_REPORTS_ERROR', done => { + testAction( + actions.receiveReportsError, + null, + localState, + [{ type: types.RECEIVE_REPORTS_ERROR }], + [], + done, + ); + }); + }); +}); diff --git a/spec/frontend/reports/codequality_report/store/getters_spec.js b/spec/frontend/reports/codequality_report/store/getters_spec.js new file mode 100644 index 00000000000..a641e2fe74f --- /dev/null +++ b/spec/frontend/reports/codequality_report/store/getters_spec.js @@ -0,0 +1,95 @@ +import * as getters from '~/reports/codequality_report/store/getters'; +import createStore from '~/reports/codequality_report/store'; +import { LOADING, ERROR, SUCCESS } from '~/reports/constants'; + +describe('Codequality reports store getters', () => { + let localState; + let localStore; + + beforeEach(() => { + localStore = createStore(); + localState = localStore.state; + }); + + describe('hasCodequalityIssues', () => { + describe('when there are issues', () => { + it('returns true', () => { + localState.newIssues = [{ reason: 'repetitive code' }]; + localState.resolvedIssues = []; + + expect(getters.hasCodequalityIssues(localState)).toEqual(true); + + localState.newIssues = []; + localState.resolvedIssues = [{ reason: 'repetitive code' }]; + + expect(getters.hasCodequalityIssues(localState)).toEqual(true); + }); + }); + + describe('when there are no issues', () => { + it('returns false when there are no issues', () => { + expect(getters.hasCodequalityIssues(localState)).toEqual(false); + }); + }); + }); + + describe('codequalityStatus', () => { + describe('when loading', () => { + it('returns loading status', () => { + localState.isLoading = true; + + expect(getters.codequalityStatus(localState)).toEqual(LOADING); + }); + }); + + describe('on error', () => { + it('returns error status', () => { + localState.hasError = true; + + expect(getters.codequalityStatus(localState)).toEqual(ERROR); + }); + }); + + describe('when successfully loaded', () => { + it('returns error status', () => { + expect(getters.codequalityStatus(localState)).toEqual(SUCCESS); + }); + }); + }); + + describe('codequalityText', () => { + it.each` + resolvedIssues | newIssues | expectedText + ${0} | ${0} | ${'No changes to code quality'} + ${0} | ${1} | ${'Code quality degraded on 1 point'} + ${2} | ${0} | ${'Code quality improved on 2 points'} + ${1} | ${2} | ${'Code quality improved on 1 point and degraded on 2 points'} + `( + 'returns a summary containing $resolvedIssues resolved issues and $newIssues new issues', + ({ newIssues, resolvedIssues, expectedText }) => { + localState.newIssues = new Array(newIssues).fill({ reason: 'Repetitive code' }); + localState.resolvedIssues = new Array(resolvedIssues).fill({ reason: 'Repetitive code' }); + + expect(getters.codequalityText(localState)).toEqual(expectedText); + }, + ); + }); + + describe('codequalityPopover', () => { + describe('when head report is available but base report is not', () => { + it('returns a popover with a documentation link', () => { + localState.headPath = 'head.json'; + localState.basePath = undefined; + localState.helpPath = 'codequality_help.html'; + + expect(getters.codequalityPopover(localState).title).toEqual( + 'Base pipeline codequality artifact not found', + ); + expect(getters.codequalityPopover(localState).content).toContain( + 'Learn more about codequality reports', + 'href="codequality_help.html"', + ); + }); + }); + }); +}); diff --git a/spec/frontend/reports/codequality_report/store/mutations_spec.js b/spec/frontend/reports/codequality_report/store/mutations_spec.js new file mode 100644 index 00000000000..658abf3088c --- /dev/null +++ b/spec/frontend/reports/codequality_report/store/mutations_spec.js @@ -0,0 +1,80 @@ +import mutations from '~/reports/codequality_report/store/mutations'; +import createStore from '~/reports/codequality_report/store'; + +describe('Codequality Reports mutations', () => { + let localState; + let localStore; + + beforeEach(() => { + localStore = createStore(); + localState = localStore.state; + }); + + describe('SET_PATHS', () => { + it('sets paths to given values', () => { + const basePath = 'base.json'; + const headPath = 'head.json'; + const baseBlobPath = 'base/blob/path/'; + const headBlobPath = 'head/blob/path/'; + const helpPath = 'help.html'; + + mutations.SET_PATHS(localState, { + basePath, + headPath, + baseBlobPath, + headBlobPath, + helpPath, + }); + + expect(localState.basePath).toEqual(basePath); + expect(localState.headPath).toEqual(headPath); + expect(localState.baseBlobPath).toEqual(baseBlobPath); + expect(localState.headBlobPath).toEqual(headBlobPath); + expect(localState.helpPath).toEqual(helpPath); + }); + }); + + describe('REQUEST_REPORTS', () => { + it('sets isLoading to true', () => { + mutations.REQUEST_REPORTS(localState); + + expect(localState.isLoading).toEqual(true); + }); + }); + + describe('RECEIVE_REPORTS_SUCCESS', () => { + it('sets isLoading to false', () => { + mutations.RECEIVE_REPORTS_SUCCESS(localState, {}); + + expect(localState.isLoading).toEqual(false); + }); + + it('sets hasError to false', () => { + mutations.RECEIVE_REPORTS_SUCCESS(localState, {}); + + expect(localState.hasError).toEqual(false); + }); + + it('sets newIssues and resolvedIssues from response data', () => { + const data = { newIssues: [{ id: 1 }], resolvedIssues: [{ id: 2 }] }; + mutations.RECEIVE_REPORTS_SUCCESS(localState, data); + + expect(localState.newIssues).toEqual(data.newIssues); + expect(localState.resolvedIssues).toEqual(data.resolvedIssues); + }); + }); + + describe('RECEIVE_REPORTS_ERROR', () => { + it('sets isLoading to false', () => { + mutations.RECEIVE_REPORTS_ERROR(localState); + + expect(localState.isLoading).toEqual(false); + }); + + it('sets hasError to true', () => { + mutations.RECEIVE_REPORTS_ERROR(localState); + + expect(localState.hasError).toEqual(true); + }); + }); +}); diff --git a/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js new file mode 100644 index 00000000000..5dd69d3c4d4 --- /dev/null +++ b/spec/frontend/reports/codequality_report/store/utils/codequality_comparison_spec.js @@ -0,0 +1,139 @@ +import { + parseCodeclimateMetrics, + doCodeClimateComparison, +} from '~/reports/codequality_report/store/utils/codequality_comparison'; +import { baseIssues, mockParsedHeadIssues, mockParsedBaseIssues } from '../../mock_data'; + +jest.mock('~/reports/codequality_report/workers/codequality_comparison_worker', () => { + let mockPostMessageCallback; + return jest.fn().mockImplementation(() => { + return { + addEventListener: (_, callback) => { + mockPostMessageCallback = callback; + }, + postMessage: data => { + if (!data.headIssues) return mockPostMessageCallback({ data: {} }); + if (!data.baseIssues) throw new Error(); + const key = 'fingerprint'; + return mockPostMessageCallback({ + data: { + newIssues: data.headIssues.filter( + item => !data.baseIssues.find(el => el[key] === item[key]), + ), + resolvedIssues: data.baseIssues.filter( + item => !data.headIssues.find(el => el[key] === item[key]), + ), + }, + }); + }, + }; + }); +}); + +describe('Codequality report store utils', () => { + let result; + + describe('parseCodeclimateMetrics', () => { + it('should parse the received issues', () => { + [result] = parseCodeclimateMetrics(baseIssues, 'path'); + + expect(result.name).toEqual(baseIssues[0].check_name); + expect(result.path).toEqual(baseIssues[0].location.path); + expect(result.line).toEqual(baseIssues[0].location.lines.begin); + }); + + describe('when an issue has no location or path', () => { + const issue = { description: 'Insecure Dependency' }; + + beforeEach(() => { + [result] = parseCodeclimateMetrics([issue], 'path'); + }); + + it('is parsed', () => { + expect(result.name).toEqual(issue.description); + }); + }); + + describe('when an issue has a path but no line', () => { + const issue = { description: 'Insecure Dependency', location: { path: 'Gemfile.lock' } }; + + beforeEach(() => { + [result] = parseCodeclimateMetrics([issue], 'path'); + }); + + it('is parsed', () => { + expect(result.name).toEqual(issue.description); + expect(result.path).toEqual(issue.location.path); + expect(result.urlPath).toEqual(`path/${issue.location.path}`); + }); + }); + + describe('when an issue has a line nested in positions', () => { + const issue = { + description: 'Insecure Dependency', + location: { + path: 'Gemfile.lock', + positions: { begin: { line: 84 } }, + }, + }; + + beforeEach(() => { + [result] = parseCodeclimateMetrics([issue], 'path'); + }); + + it('is parsed', () => { + expect(result.name).toEqual(issue.description); + expect(result.path).toEqual(issue.location.path); + expect(result.urlPath).toEqual( + `path/${issue.location.path}#L${issue.location.positions.begin.line}`, + ); + }); + }); + + describe('with an empty issue array', () => { + beforeEach(() => { + result = parseCodeclimateMetrics([], 'path'); + }); + + it('returns an empty array', () => { + expect(result).toEqual([]); + }); + }); + }); + + describe('doCodeClimateComparison', () => { + describe('when the comparison worker finds changed issues', () => { + beforeEach(async () => { + result = await doCodeClimateComparison(mockParsedHeadIssues, mockParsedBaseIssues); + }); + + it('returns the new and resolved issues', () => { + expect(result.resolvedIssues[0]).toEqual(mockParsedBaseIssues[0]); + expect(result.newIssues[0]).toEqual(mockParsedHeadIssues[0]); + }); + }); + + describe('when the comparison worker finds no changed issues', () => { + beforeEach(async () => { + result = await doCodeClimateComparison([], []); + }); + + it('returns the empty issue arrays', () => { + expect(result.newIssues).toEqual([]); + expect(result.resolvedIssues).toEqual([]); + }); + }); + + describe('when the comparison worker is given malformed data', () => { + it('rejects the promise', () => { + return expect(doCodeClimateComparison(null)).rejects.toEqual({}); + }); + }); + + describe('when the comparison worker encounters an error', () => { + it('rejects the promise and throws an error', () => { + return expect(doCodeClimateComparison([], null)).rejects.toThrow(); + }); + }); + }); +}); |