Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/ci/job_details/store')
-rw-r--r--spec/frontend/ci/job_details/store/actions_spec.js502
-rw-r--r--spec/frontend/ci/job_details/store/getters_spec.js245
-rw-r--r--spec/frontend/ci/job_details/store/helpers.js5
-rw-r--r--spec/frontend/ci/job_details/store/mutations_spec.js269
-rw-r--r--spec/frontend/ci/job_details/store/utils_spec.js510
5 files changed, 1531 insertions, 0 deletions
diff --git a/spec/frontend/ci/job_details/store/actions_spec.js b/spec/frontend/ci/job_details/store/actions_spec.js
new file mode 100644
index 00000000000..bb5c1fe32bd
--- /dev/null
+++ b/spec/frontend/ci/job_details/store/actions_spec.js
@@ -0,0 +1,502 @@
+import MockAdapter from 'axios-mock-adapter';
+import { TEST_HOST } from 'helpers/test_constants';
+import testAction from 'helpers/vuex_action_helper';
+import {
+ setJobEndpoint,
+ setJobLogOptions,
+ clearEtagPoll,
+ stopPolling,
+ requestJob,
+ fetchJob,
+ receiveJobSuccess,
+ receiveJobError,
+ scrollTop,
+ scrollBottom,
+ requestJobLog,
+ fetchJobLog,
+ startPollingJobLog,
+ stopPollingJobLog,
+ receiveJobLogSuccess,
+ receiveJobLogError,
+ toggleCollapsibleLine,
+ requestJobsForStage,
+ fetchJobsForStage,
+ receiveJobsForStageSuccess,
+ receiveJobsForStageError,
+ hideSidebar,
+ showSidebar,
+ toggleSidebar,
+} from '~/ci/job_details/store/actions';
+import * as types from '~/ci/job_details/store/mutation_types';
+import state from '~/ci/job_details/store/state';
+import axios from '~/lib/utils/axios_utils';
+import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+
+describe('Job State actions', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('setJobEndpoint', () => {
+ it('should commit SET_JOB_ENDPOINT mutation', () => {
+ return testAction(
+ setJobEndpoint,
+ 'job/872324.json',
+ mockedState,
+ [{ type: types.SET_JOB_ENDPOINT, payload: 'job/872324.json' }],
+ [],
+ );
+ });
+ });
+
+ describe('setJobLogOptions', () => {
+ it('should commit SET_JOB_LOG_OPTIONS mutation', () => {
+ return testAction(
+ setJobLogOptions,
+ { pagePath: 'job/872324/trace.json' },
+ mockedState,
+ [{ type: types.SET_JOB_LOG_OPTIONS, payload: { pagePath: 'job/872324/trace.json' } }],
+ [],
+ );
+ });
+ });
+
+ describe('hideSidebar', () => {
+ it('should commit HIDE_SIDEBAR mutation', () => {
+ return testAction(hideSidebar, null, mockedState, [{ type: types.HIDE_SIDEBAR }], []);
+ });
+ });
+
+ describe('showSidebar', () => {
+ it('should commit SHOW_SIDEBAR mutation', () => {
+ return testAction(showSidebar, null, mockedState, [{ type: types.SHOW_SIDEBAR }], []);
+ });
+ });
+
+ describe('toggleSidebar', () => {
+ describe('when isSidebarOpen is true', () => {
+ it('should dispatch hideSidebar', () => {
+ return testAction(toggleSidebar, null, mockedState, [], [{ type: 'hideSidebar' }]);
+ });
+ });
+
+ describe('when isSidebarOpen is false', () => {
+ it('should dispatch showSidebar', () => {
+ mockedState.isSidebarOpen = false;
+
+ return testAction(toggleSidebar, null, mockedState, [], [{ type: 'showSidebar' }]);
+ });
+ });
+ });
+
+ describe('requestJob', () => {
+ it('should commit REQUEST_JOB mutation', () => {
+ return testAction(requestJob, null, mockedState, [{ type: types.REQUEST_JOB }], []);
+ });
+ });
+
+ describe('fetchJob', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.jobEndpoint = `${TEST_HOST}/endpoint.json`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ stopPolling();
+ clearEtagPoll();
+ });
+
+ describe('success', () => {
+ it('dispatches requestJob and receiveJobSuccess', () => {
+ mock
+ .onGet(`${TEST_HOST}/endpoint.json`)
+ .replyOnce(HTTP_STATUS_OK, { id: 121212, name: 'karma' });
+
+ return testAction(
+ fetchJob,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJob',
+ },
+ {
+ payload: { id: 121212, name: 'karma' },
+ type: 'receiveJobSuccess',
+ },
+ ],
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ });
+
+ it('dispatches requestJob and receiveJobError', () => {
+ return testAction(
+ fetchJob,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJob',
+ },
+ {
+ type: 'receiveJobError',
+ },
+ ],
+ );
+ });
+ });
+ });
+
+ describe('receiveJobSuccess', () => {
+ it('should commit RECEIVE_JOB_SUCCESS mutation', () => {
+ return testAction(
+ receiveJobSuccess,
+ { id: 121232132 },
+ mockedState,
+ [{ type: types.RECEIVE_JOB_SUCCESS, payload: { id: 121232132 } }],
+ [],
+ );
+ });
+ });
+
+ describe('receiveJobError', () => {
+ it('should commit RECEIVE_JOB_ERROR mutation', () => {
+ return testAction(
+ receiveJobError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_JOB_ERROR }],
+ [],
+ );
+ });
+ });
+
+ describe('scrollTop', () => {
+ it('should dispatch toggleScrollButtons action', () => {
+ return testAction(scrollTop, null, mockedState, [], [{ type: 'toggleScrollButtons' }]);
+ });
+ });
+
+ describe('scrollBottom', () => {
+ it('should dispatch toggleScrollButtons action', () => {
+ return testAction(scrollBottom, null, mockedState, [], [{ type: 'toggleScrollButtons' }]);
+ });
+ });
+
+ describe('requestJobLog', () => {
+ it('should commit REQUEST_JOB_LOG mutation', () => {
+ return testAction(requestJobLog, null, mockedState, [{ type: types.REQUEST_JOB_LOG }], []);
+ });
+ });
+
+ describe('fetchJobLog', () => {
+ let mock;
+
+ beforeEach(() => {
+ mockedState.jobLogEndpoint = `${TEST_HOST}/endpoint`;
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ stopPolling();
+ clearEtagPoll();
+ });
+
+ describe('success', () => {
+ it('dispatches requestJobLog, receiveJobLogSuccess and stopPollingJobLog when job is complete', () => {
+ mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, {
+ html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
+ complete: true,
+ });
+
+ return testAction(
+ fetchJobLog,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'toggleScrollisInBottom',
+ payload: true,
+ },
+ {
+ payload: {
+ html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
+ complete: true,
+ },
+ type: 'receiveJobLogSuccess',
+ },
+ {
+ type: 'stopPollingJobLog',
+ },
+ ],
+ );
+ });
+
+ describe('when job is incomplete', () => {
+ let jobLogPayload;
+
+ beforeEach(() => {
+ jobLogPayload = {
+ html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
+ complete: false,
+ };
+
+ mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(HTTP_STATUS_OK, jobLogPayload);
+ });
+
+ it('dispatches startPollingJobLog', () => {
+ return testAction(
+ fetchJobLog,
+ null,
+ mockedState,
+ [],
+ [
+ { type: 'toggleScrollisInBottom', payload: true },
+ { type: 'receiveJobLogSuccess', payload: jobLogPayload },
+ { type: 'startPollingJobLog' },
+ ],
+ );
+ });
+
+ it('does not dispatch startPollingJobLog when timeout is non-empty', () => {
+ mockedState.jobLogTimeout = 1;
+
+ return testAction(
+ fetchJobLog,
+ null,
+ mockedState,
+ [],
+ [
+ { type: 'toggleScrollisInBottom', payload: true },
+ { type: 'receiveJobLogSuccess', payload: jobLogPayload },
+ ],
+ );
+ });
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint/trace.json`).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ });
+
+ it('dispatches requestJobLog and receiveJobLogError', () => {
+ return testAction(
+ fetchJobLog,
+ null,
+ mockedState,
+ [],
+ [
+ {
+ type: 'receiveJobLogError',
+ },
+ ],
+ );
+ });
+ });
+ });
+
+ describe('startPollingJobLog', () => {
+ let dispatch;
+ let commit;
+
+ beforeEach(() => {
+ dispatch = jest.fn();
+ commit = jest.fn();
+
+ startPollingJobLog({ dispatch, commit });
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ });
+
+ it('should save the timeout id but not call fetchJobLog', () => {
+ expect(commit).toHaveBeenCalledWith(types.SET_JOB_LOG_TIMEOUT, expect.any(Number));
+ expect(commit.mock.calls[0][1]).toBeGreaterThan(0);
+
+ expect(dispatch).not.toHaveBeenCalledWith('fetchJobLog');
+ });
+
+ describe('after timeout has passed', () => {
+ beforeEach(() => {
+ jest.advanceTimersByTime(4000);
+ });
+
+ it('should clear the timeout id and fetchJobLog', () => {
+ expect(commit).toHaveBeenCalledWith(types.SET_JOB_LOG_TIMEOUT, 0);
+ expect(dispatch).toHaveBeenCalledWith('fetchJobLog');
+ });
+ });
+ });
+
+ describe('stopPollingJobLog', () => {
+ let origTimeout;
+
+ beforeEach(() => {
+ // Can't use spyOn(window, 'clearTimeout') because this caused unrelated specs to timeout
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23838#note_280277727
+ origTimeout = window.clearTimeout;
+ window.clearTimeout = jest.fn();
+ });
+
+ afterEach(() => {
+ window.clearTimeout = origTimeout;
+ });
+
+ it('should commit STOP_POLLING_JOB_LOG mutation', async () => {
+ const jobLogTimeout = 7;
+
+ await testAction(
+ stopPollingJobLog,
+ null,
+ { ...mockedState, jobLogTimeout },
+ [{ type: types.SET_JOB_LOG_TIMEOUT, payload: 0 }, { type: types.STOP_POLLING_JOB_LOG }],
+ [],
+ );
+ expect(window.clearTimeout).toHaveBeenCalledWith(jobLogTimeout);
+ });
+ });
+
+ describe('receiveJobLogSuccess', () => {
+ it('should commit RECEIVE_JOB_LOG_SUCCESS mutation', () => {
+ return testAction(
+ receiveJobLogSuccess,
+ 'hello world',
+ mockedState,
+ [{ type: types.RECEIVE_JOB_LOG_SUCCESS, payload: 'hello world' }],
+ [],
+ );
+ });
+ });
+
+ describe('receiveJobLogError', () => {
+ it('should commit stop polling job log', () => {
+ return testAction(receiveJobLogError, null, mockedState, [], [{ type: 'stopPollingJobLog' }]);
+ });
+ });
+
+ describe('toggleCollapsibleLine', () => {
+ it('should commit TOGGLE_COLLAPSIBLE_LINE mutation', () => {
+ return testAction(
+ toggleCollapsibleLine,
+ { isClosed: true },
+ mockedState,
+ [{ type: types.TOGGLE_COLLAPSIBLE_LINE, payload: { isClosed: true } }],
+ [],
+ );
+ });
+ });
+
+ describe('requestJobsForStage', () => {
+ it('should commit REQUEST_JOBS_FOR_STAGE mutation', () => {
+ return testAction(
+ requestJobsForStage,
+ { name: 'deploy' },
+ mockedState,
+ [{ type: types.REQUEST_JOBS_FOR_STAGE, payload: { name: 'deploy' } }],
+ [],
+ );
+ });
+ });
+
+ describe('fetchJobsForStage', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('success', () => {
+ it('dispatches requestJobsForStage and receiveJobsForStageSuccess', () => {
+ mock.onGet(`${TEST_HOST}/jobs.json`).replyOnce(HTTP_STATUS_OK, {
+ latest_statuses: [{ id: 121212, name: 'build' }],
+ retried: [],
+ });
+
+ return testAction(
+ fetchJobsForStage,
+ { dropdown_path: `${TEST_HOST}/jobs.json` },
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJobsForStage',
+ payload: { dropdown_path: `${TEST_HOST}/jobs.json` },
+ },
+ {
+ payload: [{ id: 121212, name: 'build' }],
+ type: 'receiveJobsForStageSuccess',
+ },
+ ],
+ );
+ });
+ });
+
+ describe('error', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/jobs.json`).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
+ });
+
+ it('dispatches requestJobsForStage and receiveJobsForStageError', () => {
+ return testAction(
+ fetchJobsForStage,
+ { dropdown_path: `${TEST_HOST}/jobs.json` },
+ mockedState,
+ [],
+ [
+ {
+ type: 'requestJobsForStage',
+ payload: { dropdown_path: `${TEST_HOST}/jobs.json` },
+ },
+ {
+ type: 'receiveJobsForStageError',
+ },
+ ],
+ );
+ });
+ });
+ });
+
+ describe('receiveJobsForStageSuccess', () => {
+ it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation', () => {
+ return testAction(
+ receiveJobsForStageSuccess,
+ [{ id: 121212, name: 'karma' }],
+ mockedState,
+ [{ type: types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, payload: [{ id: 121212, name: 'karma' }] }],
+ [],
+ );
+ });
+ });
+
+ describe('receiveJobsForStageError', () => {
+ it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation', () => {
+ return testAction(
+ receiveJobsForStageError,
+ null,
+ mockedState,
+ [{ type: types.RECEIVE_JOBS_FOR_STAGE_ERROR }],
+ [],
+ );
+ });
+ });
+});
diff --git a/spec/frontend/ci/job_details/store/getters_spec.js b/spec/frontend/ci/job_details/store/getters_spec.js
new file mode 100644
index 00000000000..dfa5f9d4781
--- /dev/null
+++ b/spec/frontend/ci/job_details/store/getters_spec.js
@@ -0,0 +1,245 @@
+import * as getters from '~/ci/job_details/store/getters';
+import state from '~/ci/job_details/store/state';
+
+describe('Job Store Getters', () => {
+ let localState;
+
+ beforeEach(() => {
+ localState = state();
+ });
+
+ describe('headerTime', () => {
+ describe('when the job has started key', () => {
+ it('returns started_at value', () => {
+ const started = '2018-08-31T16:20:49.023Z';
+ const startedAt = '2018-08-31T16:20:49.023Z';
+ localState.job.started_at = startedAt;
+ localState.job.started = started;
+
+ expect(getters.headerTime(localState)).toEqual(startedAt);
+ });
+ });
+
+ describe('when the job does not have started key', () => {
+ it('returns created_at value', () => {
+ const created = '2018-08-31T16:20:49.023Z';
+ localState.job.created_at = created;
+
+ expect(getters.headerTime(localState)).toEqual(created);
+ });
+ });
+ });
+
+ describe('shouldRenderCalloutMessage', () => {
+ describe('with status and callout message', () => {
+ it('returns true', () => {
+ localState.job.callout_message = 'Callout message';
+ localState.job.status = { icon: 'passed' };
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true);
+ });
+ });
+
+ describe('without status & with callout message', () => {
+ it('returns false', () => {
+ localState.job.callout_message = 'Callout message';
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
+ });
+ });
+
+ describe('with status & without callout message', () => {
+ it('returns false', () => {
+ localState.job.status = { icon: 'passed' };
+
+ expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('shouldRenderTriggeredLabel', () => {
+ describe('when started equals null', () => {
+ it('returns false', () => {
+ localState.job.started_at = null;
+
+ expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(false);
+ });
+ });
+
+ describe('when started equals string', () => {
+ it('returns true', () => {
+ localState.job.started_at = '2018-08-31T16:20:49.023Z';
+
+ expect(getters.shouldRenderTriggeredLabel(localState)).toEqual(true);
+ });
+ });
+ });
+
+ describe('hasEnvironment', () => {
+ describe('without `deployment_status`', () => {
+ it('returns false', () => {
+ expect(getters.hasEnvironment(localState)).toEqual(false);
+ });
+ });
+
+ describe('with an empty object for `deployment_status`', () => {
+ it('returns false', () => {
+ localState.job.deployment_status = {};
+
+ expect(getters.hasEnvironment(localState)).toEqual(false);
+ });
+ });
+
+ describe('when `deployment_status` is defined and not empty', () => {
+ it('returns true', () => {
+ localState.job.deployment_status = {
+ status: 'creating',
+ environment: {
+ last_deployment: {},
+ },
+ };
+
+ expect(getters.hasEnvironment(localState)).toEqual(true);
+ });
+ });
+ });
+
+ describe('hasJobLog', () => {
+ describe('when has_trace is true', () => {
+ it('returns true', () => {
+ localState.job.has_trace = true;
+ localState.job.status = {};
+
+ expect(getters.hasJobLog(localState)).toEqual(true);
+ });
+ });
+
+ describe('when job is running', () => {
+ it('returns true', () => {
+ localState.job.has_trace = false;
+ localState.job.status = { group: 'running' };
+
+ expect(getters.hasJobLog(localState)).toEqual(true);
+ });
+ });
+
+ describe('when has_trace is false and job is not running', () => {
+ it('returns false', () => {
+ localState.job.has_trace = false;
+ localState.job.status = { group: 'pending' };
+
+ expect(getters.hasJobLog(localState)).toEqual(false);
+ });
+ });
+ });
+
+ describe('emptyStateIllustration', () => {
+ describe('with defined illustration', () => {
+ it('returns the state illustration object', () => {
+ localState.job.status = {
+ illustration: {
+ path: 'foo',
+ },
+ };
+
+ expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' });
+ });
+ });
+
+ describe('when illustration is not defined', () => {
+ it('returns an empty object', () => {
+ expect(getters.emptyStateIllustration(localState)).toEqual({});
+ });
+ });
+ });
+
+ describe('shouldRenderSharedRunnerLimitWarning', () => {
+ describe('without runners information', () => {
+ it('returns false', () => {
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false);
+ });
+ });
+
+ describe('with runners information', () => {
+ describe('when used quota is less than limit', () => {
+ it('returns false', () => {
+ localState.job.runners = {
+ quota: {
+ used: 33,
+ limit: 2000,
+ },
+ available: true,
+ online: true,
+ };
+
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(false);
+ });
+ });
+
+ describe('when used quota is equal to limit', () => {
+ it('returns true', () => {
+ localState.job.runners = {
+ quota: {
+ used: 2000,
+ limit: 2000,
+ },
+ available: true,
+ online: true,
+ };
+
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true);
+ });
+ });
+
+ describe('when used quota is bigger than limit', () => {
+ it('returns true', () => {
+ localState.job.runners = {
+ quota: {
+ used: 2002,
+ limit: 2000,
+ },
+ available: true,
+ online: true,
+ };
+
+ expect(getters.shouldRenderSharedRunnerLimitWarning(localState)).toEqual(true);
+ });
+ });
+ });
+ });
+
+ describe('hasOfflineRunnersForProject', () => {
+ describe('with available and offline runners', () => {
+ it('returns true', () => {
+ localState.job.runners = {
+ available: true,
+ online: false,
+ };
+
+ expect(getters.hasOfflineRunnersForProject(localState)).toEqual(true);
+ });
+ });
+
+ describe('with non available runners', () => {
+ it('returns false', () => {
+ localState.job.runners = {
+ available: false,
+ online: false,
+ };
+
+ expect(getters.hasOfflineRunnersForProject(localState)).toEqual(false);
+ });
+ });
+
+ describe('with online runners', () => {
+ it('returns false', () => {
+ localState.job.runners = {
+ available: false,
+ online: true,
+ };
+
+ expect(getters.hasOfflineRunnersForProject(localState)).toEqual(false);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/ci/job_details/store/helpers.js b/spec/frontend/ci/job_details/store/helpers.js
new file mode 100644
index 00000000000..6b186e094e7
--- /dev/null
+++ b/spec/frontend/ci/job_details/store/helpers.js
@@ -0,0 +1,5 @@
+import state from '~/ci/job_details/store/state';
+
+export const resetStore = (store) => {
+ store.replaceState(state());
+};
diff --git a/spec/frontend/ci/job_details/store/mutations_spec.js b/spec/frontend/ci/job_details/store/mutations_spec.js
new file mode 100644
index 00000000000..0835c534fb9
--- /dev/null
+++ b/spec/frontend/ci/job_details/store/mutations_spec.js
@@ -0,0 +1,269 @@
+import * as types from '~/ci/job_details/store/mutation_types';
+import mutations from '~/ci/job_details/store/mutations';
+import state from '~/ci/job_details/store/state';
+
+describe('Jobs Store Mutations', () => {
+ let stateCopy;
+
+ const html =
+ 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I';
+
+ beforeEach(() => {
+ stateCopy = state();
+ });
+
+ describe('SET_JOB_ENDPOINT', () => {
+ it('should set jobEndpoint', () => {
+ mutations[types.SET_JOB_ENDPOINT](stateCopy, 'job/21312321.json');
+
+ expect(stateCopy.jobEndpoint).toEqual('job/21312321.json');
+ });
+ });
+
+ describe('HIDE_SIDEBAR', () => {
+ it('should set isSidebarOpen to false', () => {
+ mutations[types.HIDE_SIDEBAR](stateCopy);
+
+ expect(stateCopy.isSidebarOpen).toEqual(false);
+ });
+ });
+
+ describe('SHOW_SIDEBAR', () => {
+ it('should set isSidebarOpen to true', () => {
+ mutations[types.SHOW_SIDEBAR](stateCopy);
+
+ expect(stateCopy.isSidebarOpen).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_JOB_LOG_SUCCESS', () => {
+ describe('when job log has state', () => {
+ it('sets jobLogState', () => {
+ const stateLog =
+ 'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0=';
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
+ state: stateLog,
+ });
+
+ expect(stateCopy.jobLogState).toEqual(stateLog);
+ });
+ });
+
+ describe('when jobLogSize is smaller than the total size', () => {
+ it('sets isJobLogSizeVisible to true', () => {
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, { total: 51184600, size: 1231 });
+
+ expect(stateCopy.isJobLogSizeVisible).toEqual(true);
+ });
+ });
+
+ describe('when jobLogSize is bigger than the total size', () => {
+ it('sets isJobLogSizeVisible to false', () => {
+ const copy = { ...stateCopy, jobLogSize: 5118460, size: 2321312 };
+
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](copy, { total: 511846 });
+
+ expect(copy.isJobLogSizeVisible).toEqual(false);
+ });
+ });
+
+ it('sets job log size and isJobLogComplete', () => {
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
+ append: true,
+ html,
+ size: 511846,
+ complete: true,
+ lines: [],
+ });
+
+ expect(stateCopy.jobLogSize).toEqual(511846);
+ expect(stateCopy.isJobLogComplete).toEqual(true);
+ });
+
+ describe('with new job log', () => {
+ describe('log.lines', () => {
+ describe('when append is true', () => {
+ it('sets the parsed log', () => {
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
+ append: true,
+ size: 511846,
+ complete: true,
+ lines: [
+ {
+ offset: 1,
+ content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
+ },
+ ],
+ });
+
+ expect(stateCopy.jobLog).toEqual([
+ {
+ offset: 1,
+ content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
+ lineNumber: 0,
+ },
+ ]);
+ });
+ });
+
+ describe('when it is defined', () => {
+ it('sets the parsed log', () => {
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
+ append: false,
+ size: 511846,
+ complete: true,
+ lines: [
+ { offset: 0, content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }] },
+ ],
+ });
+
+ expect(stateCopy.jobLog).toEqual([
+ {
+ offset: 0,
+ content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
+ lineNumber: 0,
+ },
+ ]);
+ });
+ });
+
+ describe('when it is null', () => {
+ it('sets the default value', () => {
+ mutations[types.RECEIVE_JOB_LOG_SUCCESS](stateCopy, {
+ append: true,
+ html,
+ size: 511846,
+ complete: false,
+ lines: null,
+ });
+
+ expect(stateCopy.jobLog).toEqual([]);
+ });
+ });
+ });
+ });
+ });
+
+ describe('SET_JOB_LOG_TIMEOUT', () => {
+ it('sets the jobLogTimeout id', () => {
+ const id = 7;
+
+ expect(stateCopy.jobLogTimeout).not.toEqual(id);
+
+ mutations[types.SET_JOB_LOG_TIMEOUT](stateCopy, id);
+
+ expect(stateCopy.jobLogTimeout).toEqual(id);
+ });
+ });
+
+ describe('STOP_POLLING_JOB_LOG', () => {
+ it('sets isJobLogComplete to true', () => {
+ mutations[types.STOP_POLLING_JOB_LOG](stateCopy);
+
+ expect(stateCopy.isJobLogComplete).toEqual(true);
+ });
+ });
+
+ describe('TOGGLE_COLLAPSIBLE_LINE', () => {
+ it('toggles the `isClosed` property of the provided object', () => {
+ const section = { isClosed: true };
+ mutations[types.TOGGLE_COLLAPSIBLE_LINE](stateCopy, section);
+ expect(section.isClosed).toEqual(false);
+ });
+ });
+
+ describe('REQUEST_JOB', () => {
+ it('sets isLoading to true', () => {
+ mutations[types.REQUEST_JOB](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(true);
+ });
+ });
+
+ describe('RECEIVE_JOB_SUCCESS', () => {
+ it('sets is loading to false', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+
+ expect(stateCopy.isLoading).toEqual(false);
+ });
+
+ it('sets hasError to false', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+
+ expect(stateCopy.hasError).toEqual(false);
+ });
+
+ it('sets job data', () => {
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
+
+ expect(stateCopy.job).toEqual({ id: 1312321 });
+ });
+
+ it('sets selectedStage when the selectedStage is empty', () => {
+ expect(stateCopy.selectedStage).toEqual('');
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
+
+ expect(stateCopy.selectedStage).toEqual('deploy');
+ });
+
+ it('does not set selectedStage when the selectedStage is not More', () => {
+ stateCopy.selectedStage = 'notify';
+
+ expect(stateCopy.selectedStage).toEqual('notify');
+ mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321, stage: 'deploy' });
+
+ expect(stateCopy.selectedStage).toEqual('notify');
+ });
+ });
+
+ describe('RECEIVE_JOB_ERROR', () => {
+ it('resets job data', () => {
+ mutations[types.RECEIVE_JOB_ERROR](stateCopy);
+
+ expect(stateCopy.isLoading).toEqual(false);
+ expect(stateCopy.job).toEqual({});
+ });
+ });
+
+ describe('REQUEST_JOBS_FOR_STAGE', () => {
+ it('sets isLoadingJobs to true', () => {
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
+
+ expect(stateCopy.isLoadingJobs).toEqual(true);
+ });
+
+ it('sets selectedStage', () => {
+ mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy, { name: 'deploy' });
+
+ expect(stateCopy.selectedStage).toEqual('deploy');
+ });
+ });
+
+ describe('RECEIVE_JOBS_FOR_STAGE_SUCCESS', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](stateCopy, [{ name: 'karma' }]);
+ });
+
+ it('sets isLoadingJobs to false', () => {
+ expect(stateCopy.isLoadingJobs).toEqual(false);
+ });
+
+ it('sets jobs', () => {
+ expect(stateCopy.jobs).toEqual([{ name: 'karma' }]);
+ });
+ });
+
+ describe('RECEIVE_JOBS_FOR_STAGE_ERROR', () => {
+ beforeEach(() => {
+ mutations[types.RECEIVE_JOBS_FOR_STAGE_ERROR](stateCopy);
+ });
+
+ it('sets isLoadingJobs to false', () => {
+ expect(stateCopy.isLoadingJobs).toEqual(false);
+ });
+
+ it('resets jobs', () => {
+ expect(stateCopy.jobs).toEqual([]);
+ });
+ });
+});
diff --git a/spec/frontend/ci/job_details/store/utils_spec.js b/spec/frontend/ci/job_details/store/utils_spec.js
new file mode 100644
index 00000000000..4ffba35761e
--- /dev/null
+++ b/spec/frontend/ci/job_details/store/utils_spec.js
@@ -0,0 +1,510 @@
+import {
+ logLinesParser,
+ updateIncrementalJobLog,
+ parseHeaderLine,
+ parseLine,
+ addDurationToHeader,
+ isCollapsibleSection,
+ findOffsetAndRemove,
+ getIncrementalLineNumber,
+} from '~/ci/job_details/store/utils';
+import {
+ utilsMockData,
+ originalTrace,
+ regularIncremental,
+ regularIncrementalRepeated,
+ headerTrace,
+ headerTraceIncremental,
+ collapsibleTrace,
+ collapsibleTraceIncremental,
+} from '../components/log/mock_data';
+
+describe('Jobs Store Utils', () => {
+ describe('parseHeaderLine', () => {
+ it('returns a new object with the header keys and the provided line parsed', () => {
+ const headerLine = { content: [{ text: 'foo' }] };
+ const parsedHeaderLine = parseHeaderLine(headerLine, 2);
+
+ expect(parsedHeaderLine).toEqual({
+ isClosed: false,
+ isHeader: true,
+ line: {
+ ...headerLine,
+ lineNumber: 2,
+ },
+ lines: [],
+ });
+ });
+
+ it('pre-closes a section when specified in options', () => {
+ const headerLine = { content: [{ text: 'foo' }], section_options: { collapsed: 'true' } };
+
+ const parsedHeaderLine = parseHeaderLine(headerLine, 2);
+
+ expect(parsedHeaderLine.isClosed).toBe(true);
+ });
+
+ it('expands all pre-closed sections if hash is present', () => {
+ const headerLine = { content: [{ text: 'foo' }], section_options: { collapsed: 'true' } };
+
+ const parsedHeaderLine = parseHeaderLine(headerLine, 2, '#L33');
+
+ expect(parsedHeaderLine.isClosed).toBe(false);
+ });
+ });
+
+ describe('parseLine', () => {
+ it('returns a new object with the lineNumber key added to the provided line object', () => {
+ const line = { content: [{ text: 'foo' }] };
+ const parsed = parseLine(line, 1);
+ expect(parsed.content).toEqual(line.content);
+ expect(parsed.lineNumber).toEqual(1);
+ });
+ });
+
+ describe('addDurationToHeader', () => {
+ const duration = {
+ offset: 106,
+ content: [],
+ section: 'prepare-script',
+ section_duration: '00:03',
+ };
+
+ it('adds the section duration to the correct header', () => {
+ const parsed = [
+ {
+ isClosed: false,
+ isHeader: true,
+ line: {
+ section: 'prepare-script',
+ content: [{ text: 'foo' }],
+ },
+ lines: [],
+ },
+ {
+ isClosed: false,
+ isHeader: true,
+ line: {
+ section: 'foo-bar',
+ content: [{ text: 'foo' }],
+ },
+ lines: [],
+ },
+ ];
+
+ addDurationToHeader(parsed, duration);
+
+ expect(parsed[0].line.section_duration).toEqual(duration.section_duration);
+ expect(parsed[1].line.section_duration).toEqual(undefined);
+ });
+
+ it('does not add the section duration when the headers do not match', () => {
+ const parsed = [
+ {
+ isClosed: false,
+ isHeader: true,
+ line: {
+ section: 'bar-foo',
+ content: [{ text: 'foo' }],
+ },
+ lines: [],
+ },
+ {
+ isClosed: false,
+ isHeader: true,
+ line: {
+ section: 'foo-bar',
+ content: [{ text: 'foo' }],
+ },
+ lines: [],
+ },
+ ];
+ addDurationToHeader(parsed, duration);
+
+ expect(parsed[0].line.section_duration).toEqual(undefined);
+ expect(parsed[1].line.section_duration).toEqual(undefined);
+ });
+
+ it('does not add when content has no headers', () => {
+ const parsed = [
+ {
+ section: 'bar-foo',
+ content: [{ text: 'foo' }],
+ lineNumber: 1,
+ },
+ {
+ section: 'foo-bar',
+ content: [{ text: 'foo' }],
+ lineNumber: 2,
+ },
+ ];
+
+ addDurationToHeader(parsed, duration);
+
+ expect(parsed[0].line).toEqual(undefined);
+ expect(parsed[1].line).toEqual(undefined);
+ });
+ });
+
+ describe('isCollapsibleSection', () => {
+ const header = {
+ isHeader: true,
+ line: {
+ section: 'foo',
+ },
+ };
+ const line = {
+ lineNumber: 1,
+ section: 'foo',
+ content: [],
+ };
+
+ it('returns true when line belongs to the last section', () => {
+ expect(isCollapsibleSection([header], header, { section: 'foo', content: [] })).toEqual(true);
+ });
+
+ it('returns false when last line was not an header', () => {
+ expect(isCollapsibleSection([line], line, { section: 'bar' })).toEqual(false);
+ });
+
+ it('returns false when accumulator is empty', () => {
+ expect(isCollapsibleSection([], { isHeader: true }, { section: 'bar' })).toEqual(false);
+ });
+
+ it('returns false when section_duration is defined', () => {
+ expect(isCollapsibleSection([header], header, { section_duration: '10:00' })).toEqual(false);
+ });
+
+ it('returns false when `section` is not a match', () => {
+ expect(isCollapsibleSection([header], header, { section: 'bar' })).toEqual(false);
+ });
+
+ it('returns false when no parameters are provided', () => {
+ expect(isCollapsibleSection()).toEqual(false);
+ });
+ });
+ describe('logLinesParser', () => {
+ let result;
+
+ beforeEach(() => {
+ result = logLinesParser(utilsMockData);
+ });
+
+ describe('regular line', () => {
+ it('adds a lineNumber property with correct index', () => {
+ expect(result[0].lineNumber).toEqual(0);
+ expect(result[1].line.lineNumber).toEqual(1);
+ });
+ });
+
+ describe('collapsible section', () => {
+ it('adds a `isClosed` property', () => {
+ expect(result[1].isClosed).toEqual(false);
+ });
+
+ it('adds a `isHeader` property', () => {
+ expect(result[1].isHeader).toEqual(true);
+ });
+
+ it('creates a lines array property with the content of the collapsible section', () => {
+ expect(result[1].lines.length).toEqual(2);
+ expect(result[1].lines[0].content).toEqual(utilsMockData[2].content);
+ expect(result[1].lines[1].content).toEqual(utilsMockData[3].content);
+ });
+ });
+
+ describe('section duration', () => {
+ it('adds the section information to the header section', () => {
+ expect(result[1].line.section_duration).toEqual(utilsMockData[4].section_duration);
+ });
+
+ it('does not add section duration as a line', () => {
+ expect(result[1].lines.includes(utilsMockData[4])).toEqual(false);
+ });
+ });
+ });
+
+ describe('findOffsetAndRemove', () => {
+ describe('when last item is header', () => {
+ const existingLog = [
+ {
+ isHeader: true,
+ isClosed: false,
+ line: { content: [{ text: 'bar' }], offset: 10, lineNumber: 1 },
+ },
+ ];
+
+ describe('and matches the offset', () => {
+ it('returns an array with the item removed', () => {
+ const newData = [{ offset: 10, content: [{ text: 'foobar' }] }];
+ const result = findOffsetAndRemove(newData, existingLog);
+
+ expect(result).toEqual([]);
+ });
+ });
+
+ describe('and does not match the offset', () => {
+ it('returns the provided existing log', () => {
+ const newData = [{ offset: 110, content: [{ text: 'foobar' }] }];
+ const result = findOffsetAndRemove(newData, existingLog);
+
+ expect(result).toEqual(existingLog);
+ });
+ });
+ });
+
+ describe('when last item is a regular line', () => {
+ const existingLog = [{ content: [{ text: 'bar' }], offset: 10, lineNumber: 1 }];
+
+ describe('and matches the offset', () => {
+ it('returns an array with the item removed', () => {
+ const newData = [{ offset: 10, content: [{ text: 'foobar' }] }];
+ const result = findOffsetAndRemove(newData, existingLog);
+
+ expect(result).toEqual([]);
+ });
+ });
+
+ describe('and does not match the fofset', () => {
+ it('returns the provided old log', () => {
+ const newData = [{ offset: 101, content: [{ text: 'foobar' }] }];
+ const result = findOffsetAndRemove(newData, existingLog);
+
+ expect(result).toEqual(existingLog);
+ });
+ });
+ });
+
+ describe('when last item is nested', () => {
+ const existingLog = [
+ {
+ isHeader: true,
+ isClosed: false,
+ lines: [{ offset: 101, content: [{ text: 'foobar' }], lineNumber: 2 }],
+ line: {
+ offset: 10,
+ lineNumber: 1,
+ section_duration: '10:00',
+ },
+ },
+ ];
+
+ describe('and matches the offset', () => {
+ it('returns an array with the last nested line item removed', () => {
+ const newData = [{ offset: 101, content: [{ text: 'foobar' }] }];
+
+ const result = findOffsetAndRemove(newData, existingLog);
+ expect(result[0].lines).toEqual([]);
+ });
+ });
+
+ describe('and does not match the offset', () => {
+ it('returns the provided old log', () => {
+ const newData = [{ offset: 120, content: [{ text: 'foobar' }] }];
+
+ const result = findOffsetAndRemove(newData, existingLog);
+ expect(result).toEqual(existingLog);
+ });
+ });
+ });
+
+ describe('when no data is provided', () => {
+ it('returns an empty array', () => {
+ const result = findOffsetAndRemove();
+ expect(result).toEqual([]);
+ });
+ });
+ });
+
+ describe('getIncrementalLineNumber', () => {
+ describe('when last line is 0', () => {
+ it('returns 1', () => {
+ const log = [
+ {
+ content: [],
+ lineNumber: 0,
+ },
+ ];
+
+ expect(getIncrementalLineNumber(log)).toEqual(1);
+ });
+ });
+
+ describe('with unnested line', () => {
+ it('returns the lineNumber of the last item in the array', () => {
+ const log = [
+ {
+ content: [],
+ lineNumber: 10,
+ },
+ {
+ content: [],
+ lineNumber: 101,
+ },
+ ];
+
+ expect(getIncrementalLineNumber(log)).toEqual(102);
+ });
+ });
+
+ describe('when last line is the header section', () => {
+ it('returns the lineNumber of the last item in the array', () => {
+ const log = [
+ {
+ content: [],
+ lineNumber: 10,
+ },
+ {
+ isHeader: true,
+ line: {
+ lineNumber: 101,
+ content: [],
+ },
+ lines: [],
+ },
+ ];
+
+ expect(getIncrementalLineNumber(log)).toEqual(102);
+ });
+ });
+
+ describe('when last line is a nested line', () => {
+ it('returns the lineNumber of the last item in the nested array', () => {
+ const log = [
+ {
+ content: [],
+ lineNumber: 10,
+ },
+ {
+ isHeader: true,
+ line: {
+ lineNumber: 101,
+ content: [],
+ },
+ lines: [
+ {
+ lineNumber: 102,
+ content: [],
+ },
+ { lineNumber: 103, content: [] },
+ ],
+ },
+ ];
+
+ expect(getIncrementalLineNumber(log)).toEqual(104);
+ });
+ });
+ });
+
+ describe('updateIncrementalJobLog', () => {
+ describe('without repeated section', () => {
+ it('concats and parses both arrays', () => {
+ const oldLog = logLinesParser(originalTrace);
+ const result = updateIncrementalJobLog(regularIncremental, oldLog);
+
+ expect(result).toEqual([
+ {
+ offset: 1,
+ content: [
+ {
+ text: 'Downloading',
+ },
+ ],
+ lineNumber: 0,
+ },
+ {
+ offset: 2,
+ content: [
+ {
+ text: 'log line',
+ },
+ ],
+ lineNumber: 1,
+ },
+ ]);
+ });
+ });
+
+ describe('with regular line repeated offset', () => {
+ it('updates the last line and formats with the incremental part', () => {
+ const oldLog = logLinesParser(originalTrace);
+ const result = updateIncrementalJobLog(regularIncrementalRepeated, oldLog);
+
+ expect(result).toEqual([
+ {
+ offset: 1,
+ content: [
+ {
+ text: 'log line',
+ },
+ ],
+ lineNumber: 0,
+ },
+ ]);
+ });
+ });
+
+ describe('with header line repeated', () => {
+ it('updates the header line and formats with the incremental part', () => {
+ const oldLog = logLinesParser(headerTrace);
+ const result = updateIncrementalJobLog(headerTraceIncremental, oldLog);
+
+ expect(result).toEqual([
+ {
+ isClosed: false,
+ isHeader: true,
+ line: {
+ offset: 1,
+ section_header: true,
+ content: [
+ {
+ text: 'updated log line',
+ },
+ ],
+ section: 'section',
+ lineNumber: 0,
+ },
+ lines: [],
+ },
+ ]);
+ });
+ });
+
+ describe('with collapsible line repeated', () => {
+ it('updates the collapsible line and formats with the incremental part', () => {
+ const oldLog = logLinesParser(collapsibleTrace);
+ const result = updateIncrementalJobLog(collapsibleTraceIncremental, oldLog);
+
+ expect(result).toEqual([
+ {
+ isClosed: false,
+ isHeader: true,
+ line: {
+ offset: 1,
+ section_header: true,
+ content: [
+ {
+ text: 'log line',
+ },
+ ],
+ section: 'section',
+ lineNumber: 0,
+ },
+ lines: [
+ {
+ offset: 2,
+ content: [
+ {
+ text: 'updated log line',
+ },
+ ],
+ section: 'section',
+ lineNumber: 1,
+ },
+ ],
+ },
+ ]);
+ });
+ });
+ });
+});