Adds Vuex store for the job log page

This commit is contained in:
🌴🌴 Filipa Lacerda - OOO back on September 17th 🌴🌴 2018-09-03 07:49:52 +00:00 committed by Phil Hughes
parent e550b1abaa
commit 55582b4359
10 changed files with 1224 additions and 7 deletions

View File

@ -0,0 +1,175 @@
import Visibility from 'visibilityjs';
import * as types from './mutation_types';
import axios from '../../lib/utils/axios_utils';
import Poll from '../../lib/utils/poll';
import { setCiStatusFavicon } from '../../lib/utils/common_utils';
import flash from '../../flash';
import { __ } from '../../locale';
export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint);
export const setTraceEndpoint = ({ commit }, endpoint) =>
commit(types.SET_TRACE_ENDPOINT, endpoint);
export const setStagesEndpoint = ({ commit }, endpoint) =>
commit(types.SET_STAGES_ENDPOINT, endpoint);
export const setJobsEndpoint = ({ commit }, endpoint) => commit(types.SET_JOBS_ENDPOINT, endpoint);
let eTagPoll;
export const clearEtagPoll = () => {
eTagPoll = null;
};
export const stopPolling = () => {
if (eTagPoll) eTagPoll.stop();
};
export const restartPolling = () => {
if (eTagPoll) eTagPoll.restart();
};
export const requestJob = ({ commit }) => commit(types.REQUEST_JOB);
export const fetchJob = ({ state, dispatch }) => {
dispatch('requestJob');
eTagPoll = new Poll({
resource: {
getJob(endpoint) {
return axios.get(endpoint);
},
},
data: state.jobEndpoint,
method: 'getJob',
successCallback: ({ data }) => dispatch('receiveJobSuccess', data),
errorCallback: () => dispatch('receiveJobError'),
});
if (!Visibility.hidden()) {
eTagPoll.makeRequest();
} else {
axios
.get(state.jobEndpoint)
.then(({ data }) => dispatch('receiveJobSuccess', data))
.catch(() => dispatch('receiveJobError'));
}
Visibility.change(() => {
if (!Visibility.hidden()) {
dispatch('restartPolling');
} else {
dispatch('stopPolling');
}
});
};
export const receiveJobSuccess = ({ commit }, data) => commit(types.RECEIVE_JOB_SUCCESS, data);
export const receiveJobError = ({ commit }) => {
commit(types.RECEIVE_JOB_ERROR);
flash(__('An error occurred while fetching the job.'));
};
/**
* Job's Trace
*/
export const scrollTop = ({ commit }) => {
commit(types.SCROLL_TO_TOP);
window.scrollTo({ top: 0 });
};
export const scrollBottom = ({ commit }) => {
commit(types.SCROLL_TO_BOTTOM);
window.scrollTo({ top: document.height });
};
export const requestTrace = ({ commit }) => commit(types.REQUEST_TRACE);
let traceTimeout;
export const fetchTrace = ({ dispatch, state }) => {
dispatch('requestTrace');
axios
.get(`${state.traceEndpoint}/trace.json`, {
params: { state: state.traceState },
})
.then(({ data }) => {
if (!state.fetchingStatusFavicon) {
dispatch('fetchFavicon');
}
dispatch('receiveTraceSuccess', data);
if (!data.complete) {
traceTimeout = setTimeout(() => {
dispatch('fetchTrace');
}, 4000);
} else {
dispatch('stopPollingTrace');
}
})
.catch(() => dispatch('receiveTraceError'));
};
export const stopPollingTrace = ({ commit }) => {
commit(types.STOP_POLLING_TRACE);
clearTimeout(traceTimeout);
};
export const receiveTraceSuccess = ({ commit }, log) => commit(types.RECEIVE_TRACE_SUCCESS, log);
export const receiveTraceError = ({ commit }) => {
commit(types.RECEIVE_TRACE_ERROR);
clearTimeout(traceTimeout);
flash(__('An error occurred while fetching the job log.'));
};
export const fetchFavicon = ({ state, dispatch }) => {
dispatch('requestStatusFavicon');
setCiStatusFavicon(`${state.pagePath}/status.json`)
.then(() => dispatch('receiveStatusFaviconSuccess'))
.catch(() => dispatch('requestStatusFaviconError'));
};
export const requestStatusFavicon = ({ commit }) => commit(types.REQUEST_STATUS_FAVICON);
export const receiveStatusFaviconSuccess = ({ commit }) =>
commit(types.RECEIVE_STATUS_FAVICON_SUCCESS);
export const requestStatusFaviconError = ({ commit }) => commit(types.RECEIVE_STATUS_FAVICON_ERROR);
/**
* Stages dropdown on sidebar
*/
export const requestStages = ({ commit }) => commit(types.REQUEST_STAGES);
export const fetchStages = ({ state, dispatch }) => {
dispatch('requestStages');
axios
.get(state.stagesEndpoint)
.then(({ data }) => dispatch('receiveStagesSuccess', data))
.catch(() => dispatch('receiveStagesError'));
};
export const receiveStagesSuccess = ({ commit }, data) =>
commit(types.RECEIVE_STAGES_SUCCESS, data);
export const receiveStagesError = ({ commit }) => {
commit(types.RECEIVE_STAGES_ERROR);
flash(__('An error occurred while fetching stages.'));
};
/**
* Jobs list on sidebar - depend on stages dropdown
*/
export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
export const setSelectedStage = ({ commit }, stage) => commit(types.SET_SELECTED_STAGE, stage);
// On stage click, set selected stage + fetch job
export const fetchJobsForStage = ({ state, dispatch }, stage) => {
dispatch('setSelectedStage', stage);
dispatch('requestJobsForStage');
axios
.get(state.stageJobsEndpoint)
.then(({ data }) => dispatch('receiveJobsForStageSuccess', data))
.catch(() => dispatch('receiveJobsForStageError'));
};
export const receiveJobsForStageSuccess = ({ commit }, data) =>
commit(types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, data);
export const receiveJobsForStageError = ({ commit }) => {
commit(types.RECEIVE_JOBS_FOR_STAGE_ERROR);
flash(__('An error occurred while fetching the jobs.'));
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View File

@ -0,0 +1,13 @@
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import * as actions from './actions';
import mutations from './mutations';
Vue.use(Vuex);
export default () => new Vuex.Store({
actions,
mutations,
state: state(),
});

View File

@ -0,0 +1,29 @@
export const SET_JOB_ENDPOINT = 'SET_JOB_ENDPOINT';
export const SET_TRACE_ENDPOINT = 'SET_TRACE_ENDPOINT';
export const SET_STAGES_ENDPOINT = 'SET_STAGES_ENDPOINT';
export const SET_JOBS_ENDPOINT = 'SET_JOBS_ENDPOINT';
export const SCROLL_TO_TOP = 'SCROLL_TO_TOP';
export const SCROLL_TO_BOTTOM = 'SCROLL_TO_BOTTOM';
export const REQUEST_JOB = 'REQUEST_JOB';
export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS';
export const RECEIVE_JOB_ERROR = 'RECEIVE_JOB_ERROR';
export const REQUEST_TRACE = 'REQUEST_TRACE';
export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE';
export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS';
export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR';
export const REQUEST_STATUS_FAVICON = 'REQUEST_STATUS_FAVICON';
export const RECEIVE_STATUS_FAVICON_SUCCESS = 'RECEIVE_STATUS_FAVICON_SUCCESS';
export const RECEIVE_STATUS_FAVICON_ERROR = 'RECEIVE_STATUS_FAVICON_ERROR';
export const REQUEST_STAGES = 'REQUEST_STAGES';
export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS';
export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR';
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const REQUEST_JOBS_FOR_STAGE = 'REQUEST_JOBS_FOR_STAGE';
export const RECEIVE_JOBS_FOR_STAGE_SUCCESS = 'RECEIVE_JOBS_FOR_STAGE_SUCCESS';
export const RECEIVE_JOBS_FOR_STAGE_ERROR = 'RECEIVE_JOBS_FOR_STAGE_ERROR';

View File

@ -0,0 +1,94 @@
/* eslint-disable no-param-reassign */
import * as types from './mutation_types';
export default {
[types.REQUEST_STATUS_FAVICON](state) {
state.fetchingStatusFavicon = true;
},
[types.RECEIVE_STATUS_FAVICON_SUCCESS](state) {
state.fetchingStatusFavicon = false;
},
[types.RECEIVE_STATUS_FAVICON_ERROR](state) {
state.fetchingStatusFavicon = false;
},
[types.RECEIVE_TRACE_SUCCESS](state, log) {
if (log.state) {
state.traceState = log.state;
}
if (log.append) {
state.trace += log.html;
state.traceSize += log.size;
} else {
state.trace = log.html;
state.traceSize = log.size;
}
if (state.traceSize < log.total) {
state.isTraceSizeVisible = true;
} else {
state.isTraceSizeVisible = false;
}
state.isTraceComplete = log.complete;
state.hasTraceError = false;
},
[types.STOP_POLLING_TRACE](state) {
state.isTraceComplete = true;
},
// todo_fl: check this.
[types.RECEIVE_TRACE_ERROR](state) {
state.isLoadingTrace = false;
state.isTraceComplete = true;
state.hasTraceError = true;
},
[types.REQUEST_JOB](state) {
state.isLoading = true;
},
[types.RECEIVE_JOB_SUCCESS](state, job) {
state.isLoading = false;
state.hasError = false;
state.job = job;
},
[types.RECEIVE_JOB_ERROR](state) {
state.isLoading = false;
state.hasError = true;
state.job = {};
},
[types.SCROLL_TO_TOP](state) {
state.isTraceScrolledToBottom = false;
state.hasBeenScrolled = true;
},
[types.SCROLL_TO_BOTTOM](state) {
state.isTraceScrolledToBottom = true;
state.hasBeenScrolled = true;
},
[types.REQUEST_STAGES](state) {
state.isLoadingStages = true;
},
[types.RECEIVE_STAGES_SUCCESS](state, stages) {
state.isLoadingStages = false;
state.stages = stages;
},
[types.RECEIVE_STAGES_ERROR](state) {
state.isLoadingStages = false;
state.stages = [];
},
[types.REQUEST_JOBS_FOR_STAGE](state) {
state.isLoadingJobs = true;
},
[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) {
state.isLoadingJobs = false;
state.jobs = jobs;
},
[types.RECEIVE_JOBS_FOR_STAGE_ERROR](state) {
state.isLoadingJobs = false;
state.jobs = [];
},
};

View File

@ -0,0 +1,40 @@
export default () => ({
jobEndpoint: null,
traceEndpoint: null,
// dropdown options
stagesEndpoint: null,
// list of jobs on sidebard
stageJobsEndpoint: null,
// job log
isLoading: false,
hasError: false,
job: {},
// trace
isLoadingTrace: false,
hasTraceError: false,
trace: '',
isTraceScrolledToBottom: false,
hasBeenScrolled: false,
isTraceComplete: false,
traceSize: 0, // todo_fl: needs to be converted into human readable format in components
isTraceSizeVisible: false,
fetchingStatusFavicon: false,
// used as a query parameter
traceState: null,
// used to check if we need to redirect the user - todo_fl: check if actually needed
traceStatus: null,
// sidebar dropdown
isLoadingStages: false,
isLoadingJobs: false,
selectedStage: null,
stages: [],
jobs: [],
});

View File

@ -491,7 +491,10 @@ export const setCiStatusFavicon = pageUrl =>
}
return resetFavicon();
})
.catch(resetFavicon);
.catch((error) => {
resetFavicon();
throw error;
});
export const spriteIcon = (icon, className = '') => {
const classAttribute = className.length > 0 ? `class="${className}"` : '';

View File

@ -505,6 +505,18 @@ msgstr ""
msgid "An error occurred while fetching sidebar data"
msgstr ""
msgid "An error occurred while fetching stages."
msgstr ""
msgid "An error occurred while fetching the job log."
msgstr ""
msgid "An error occurred while fetching the job."
msgstr ""
msgid "An error occurred while fetching the jobs."
msgstr ""
msgid "An error occurred while fetching the pipeline."
msgstr ""

View File

@ -0,0 +1,625 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import {
setJobEndpoint,
setTraceEndpoint,
setStagesEndpoint,
setJobsEndpoint,
clearEtagPoll,
stopPolling,
requestJob,
fetchJob,
receiveJobSuccess,
receiveJobError,
scrollTop,
scrollBottom,
requestTrace,
fetchTrace,
stopPollingTrace,
receiveTraceSuccess,
receiveTraceError,
fetchFavicon,
requestStatusFavicon,
receiveStatusFaviconSuccess,
requestStatusFaviconError,
requestStages,
fetchStages,
receiveStagesSuccess,
receiveStagesError,
requestJobsForStage,
setSelectedStage,
fetchJobsForStage,
receiveJobsForStageSuccess,
receiveJobsForStageError,
} from '~/jobs/store/actions';
import state from '~/jobs/store/state';
import * as types from '~/jobs/store/mutation_types';
import testAction from 'spec/helpers/vuex_action_helper';
import { TEST_HOST } from 'spec/test_constants';
describe('Job State actions', () => {
let mockedState;
beforeEach(() => {
mockedState = state();
});
describe('setJobEndpoint', () => {
it('should commit SET_JOB_ENDPOINT mutation', done => {
testAction(
setJobEndpoint,
'job/872324.json',
mockedState,
[{ type: types.SET_JOB_ENDPOINT, payload: 'job/872324.json' }],
[],
done,
);
});
});
describe('setTraceEndpoint', () => {
it('should commit SET_TRACE_ENDPOINT mutation', done => {
testAction(
setTraceEndpoint,
'job/872324/trace.json',
mockedState,
[{ type: types.SET_TRACE_ENDPOINT, payload: 'job/872324/trace.json' }],
[],
done,
);
});
});
describe('setStagesEndpoint', () => {
it('should commit SET_STAGES_ENDPOINT mutation', done => {
testAction(
setStagesEndpoint,
'job/872324/stages.json',
mockedState,
[{ type: types.SET_STAGES_ENDPOINT, payload: 'job/872324/stages.json' }],
[],
done,
);
});
});
describe('setJobsEndpoint', () => {
it('should commit SET_JOBS_ENDPOINT mutation', done => {
testAction(
setJobsEndpoint,
'job/872324/stages/build.json',
mockedState,
[{ type: types.SET_JOBS_ENDPOINT, payload: 'job/872324/stages/build.json' }],
[],
done,
);
});
});
describe('requestJob', () => {
it('should commit REQUEST_JOB mutation', done => {
testAction(requestJob, null, mockedState, [{ type: types.REQUEST_JOB }], [], done);
});
});
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 ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, { id: 121212, name: 'karma' });
testAction(
fetchJob,
null,
mockedState,
[],
[
{
type: 'requestJob',
},
{
payload: { id: 121212, name: 'karma' },
type: 'receiveJobSuccess',
},
],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
it('dispatches requestJob and receiveJobError ', done => {
testAction(
fetchJob,
null,
mockedState,
[],
[
{
type: 'requestJob',
},
{
type: 'receiveJobError',
},
],
done,
);
});
});
});
describe('receiveJobSuccess', () => {
it('should commit RECEIVE_JOB_SUCCESS mutation', done => {
testAction(
receiveJobSuccess,
{ id: 121232132 },
mockedState,
[{ type: types.RECEIVE_JOB_SUCCESS, payload: { id: 121232132 } }],
[],
done,
);
});
});
describe('receiveJobError', () => {
it('should commit RECEIVE_JOB_ERROR mutation', done => {
testAction(receiveJobError, null, mockedState, [{ type: types.RECEIVE_JOB_ERROR }], [], done);
});
});
describe('scrollTop', () => {
it('should commit SCROLL_TO_TOP mutation', done => {
testAction(scrollTop, null, mockedState, [{ type: types.SCROLL_TO_TOP }], [], done);
});
});
describe('scrollBottom', () => {
it('should commit SCROLL_TO_BOTTOM mutation', done => {
testAction(scrollBottom, null, mockedState, [{ type: types.SCROLL_TO_BOTTOM }], [], done);
});
});
describe('requestTrace', () => {
it('should commit REQUEST_TRACE mutation', done => {
testAction(requestTrace, null, mockedState, [{ type: types.REQUEST_TRACE }], [], done);
});
});
describe('fetchTrace', () => {
let mock;
beforeEach(() => {
mockedState.traceEndpoint = `${TEST_HOST}/endpoint`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
stopPolling();
clearEtagPoll();
});
describe('success', () => {
it('dispatches requestTrace, fetchFavicon, receiveTraceSuccess and stopPollingTrace when job is complete', done => {
mock.onGet(`${TEST_HOST}/endpoint/trace.json`).replyOnce(200, {
html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :',
complete: true,
});
testAction(
fetchTrace,
null,
mockedState,
[],
[
{
type: 'requestTrace',
},
{
type: 'fetchFavicon',
},
{
payload: {
html: 'I, [2018-08-17T22:57:45.707325 #1841] INFO -- :', complete: true,
},
type: 'receiveTraceSuccess',
},
{
type: 'stopPollingTrace',
},
],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/endpoint/trace.json`).reply(500);
});
it('dispatches requestTrace and receiveTraceError ', done => {
testAction(
fetchTrace,
null,
mockedState,
[],
[
{
type: 'requestTrace',
},
{
type: 'receiveTraceError',
},
],
done,
);
});
});
});
describe('stopPollingTrace', () => {
it('should commit STOP_POLLING_TRACE mutation ', done => {
testAction(
stopPollingTrace,
null,
mockedState,
[{ type: types.STOP_POLLING_TRACE }],
[],
done,
);
});
});
describe('receiveTraceSuccess', () => {
it('should commit RECEIVE_TRACE_SUCCESS mutation ', done => {
testAction(
receiveTraceSuccess,
'hello world',
mockedState,
[{ type: types.RECEIVE_TRACE_SUCCESS, payload: 'hello world' }],
[],
done,
);
});
});
describe('receiveTraceError', () => {
it('should commit RECEIVE_TRACE_ERROR mutation ', done => {
testAction(
receiveTraceError,
null,
mockedState,
[{ type: types.RECEIVE_TRACE_ERROR }],
[],
done,
);
});
});
describe('fetchFavicon', () => {
let mock;
beforeEach(() => {
mockedState.pagePath = `${TEST_HOST}/endpoint`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestStatusFavicon and receiveStatusFaviconSuccess ', done => {
mock.onGet(`${TEST_HOST}/endpoint/status.json`).replyOnce(200);
testAction(
fetchFavicon,
null,
mockedState,
[],
[
{
type: 'requestStatusFavicon',
},
{
type: 'receiveStatusFaviconSuccess',
},
],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/endpoint/status.json`).replyOnce(500);
});
it('dispatches requestStatusFavicon and requestStatusFaviconError ', done => {
testAction(
fetchFavicon,
null,
mockedState,
[],
[
{
type: 'requestStatusFavicon',
},
{
type: 'requestStatusFaviconError',
},
],
done,
);
});
});
});
describe('requestStatusFavicon', () => {
it('should commit REQUEST_STATUS_FAVICON mutation ', done => {
testAction(
requestStatusFavicon,
null,
mockedState,
[{ type: types.REQUEST_STATUS_FAVICON }],
[],
done,
);
});
});
describe('receiveStatusFaviconSuccess', () => {
it('should commit RECEIVE_STATUS_FAVICON_SUCCESS mutation ', done => {
testAction(
receiveStatusFaviconSuccess,
null,
mockedState,
[{ type: types.RECEIVE_STATUS_FAVICON_SUCCESS }],
[],
done,
);
});
});
describe('requestStatusFaviconError', () => {
it('should commit RECEIVE_STATUS_FAVICON_ERROR mutation ', done => {
testAction(
requestStatusFaviconError,
null,
mockedState,
[{ type: types.RECEIVE_STATUS_FAVICON_ERROR }],
[],
done,
);
});
});
describe('requestStages', () => {
it('should commit REQUEST_STAGES mutation ', done => {
testAction(requestStages, null, mockedState, [{ type: types.REQUEST_STAGES }], [], done);
});
});
describe('fetchStages', () => {
let mock;
beforeEach(() => {
mockedState.stagesEndpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestStages and receiveStagesSuccess ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]);
testAction(
fetchStages,
null,
mockedState,
[],
[
{
type: 'requestStages',
},
{
payload: [{ id: 121212, name: 'build' }],
type: 'receiveStagesSuccess',
},
],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
it('dispatches requestStages and receiveStagesError ', done => {
testAction(
fetchStages,
null,
mockedState,
[],
[
{
type: 'requestStages',
},
{
type: 'receiveStagesError',
},
],
done,
);
});
});
});
describe('receiveStagesSuccess', () => {
it('should commit RECEIVE_STAGES_SUCCESS mutation ', done => {
testAction(
receiveStagesSuccess,
{},
mockedState,
[{ type: types.RECEIVE_STAGES_SUCCESS, payload: {} }],
[],
done,
);
});
});
describe('receiveStagesError', () => {
it('should commit RECEIVE_STAGES_ERROR mutation ', done => {
testAction(
receiveStagesError,
null,
mockedState,
[{ type: types.RECEIVE_STAGES_ERROR }],
[],
done,
);
});
});
describe('requestJobsForStage', () => {
it('should commit REQUEST_JOBS_FOR_STAGE mutation ', done => {
testAction(
requestJobsForStage,
null,
mockedState,
[{ type: types.REQUEST_JOBS_FOR_STAGE }],
[],
done,
);
});
});
describe('setSelectedStage', () => {
it('should commit SET_SELECTED_STAGE mutation ', done => {
testAction(
setSelectedStage,
{ name: 'build' },
mockedState,
[{ type: types.SET_SELECTED_STAGE, payload: { name: 'build' } }],
[],
done,
);
});
});
describe('fetchJobsForStage', () => {
let mock;
beforeEach(() => {
mockedState.stageJobsEndpoint = `${TEST_HOST}/endpoint.json`;
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageSuccess ', done => {
mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, [{ id: 121212, name: 'build' }]);
testAction(
fetchJobsForStage,
null,
mockedState,
[],
[
{
type: 'setSelectedStage',
payload: null,
},
{
type: 'requestJobsForStage',
},
{
payload: [{ id: 121212, name: 'build' }],
type: 'receiveJobsForStageSuccess',
},
],
done,
);
});
});
describe('error', () => {
beforeEach(() => {
mock.onGet(`${TEST_HOST}/endpoint.json`).reply(500);
});
it('dispatches setSelectedStage, requestJobsForStage and receiveJobsForStageError', done => {
testAction(
fetchJobsForStage,
null,
mockedState,
[],
[
{
payload: null,
type: 'setSelectedStage',
},
{
type: 'requestJobsForStage',
},
{
type: 'receiveJobsForStageError',
},
],
done,
);
});
});
});
describe('receiveJobsForStageSuccess', () => {
it('should commit RECEIVE_JOBS_FOR_STAGE_SUCCESS mutation ', done => {
testAction(
receiveJobsForStageSuccess,
[{ id: 121212, name: 'karma' }],
mockedState,
[{ type: types.RECEIVE_JOBS_FOR_STAGE_SUCCESS, payload: [{ id: 121212, name: 'karma' }] }],
[],
done,
);
});
});
describe('receiveJobsForStageError', () => {
it('should commit RECEIVE_JOBS_FOR_STAGE_ERROR mutation ', done => {
testAction(
receiveJobsForStageError,
null,
mockedState,
[{ type: types.RECEIVE_JOBS_FOR_STAGE_ERROR }],
[],
done,
);
});
});
});

View File

@ -0,0 +1,228 @@
import state from '~/jobs/store/state';
import mutations from '~/jobs/store/mutations';
import * as types from '~/jobs/store/mutation_types';
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('REQUEST_STATUS_FAVICON', () => {
it('should set fetchingStatusFavicon to true', () => {
mutations[types.REQUEST_STATUS_FAVICON](stateCopy);
expect(stateCopy.fetchingStatusFavicon).toEqual(true);
});
});
describe('RECEIVE_STATUS_FAVICON_SUCCESS', () => {
it('should set fetchingStatusFavicon to false', () => {
mutations[types.RECEIVE_STATUS_FAVICON_SUCCESS](stateCopy);
expect(stateCopy.fetchingStatusFavicon).toEqual(false);
});
});
describe('RECEIVE_STATUS_FAVICON_ERROR', () => {
it('should set fetchingStatusFavicon to false', () => {
mutations[types.RECEIVE_STATUS_FAVICON_ERROR](stateCopy);
expect(stateCopy.fetchingStatusFavicon).toEqual(false);
});
});
describe('RECEIVE_TRACE_SUCCESS', () => {
describe('when trace has state', () => {
it('sets traceState', () => {
const stateLog =
'eyJvZmZzZXQiOjczNDQ1MSwibl9vcGVuX3RhZ3MiOjAsImZnX2NvbG9yIjpudWxsLCJiZ19jb2xvciI6bnVsbCwic3R5bGVfbWFzayI6MH0=';
mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
state: stateLog,
});
expect(stateCopy.traceState).toEqual(stateLog);
});
});
describe('when traceSize is smaller than the total size', () => {
it('sets isTraceSizeVisible to true', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, { total: 51184600, size: 1231 });
expect(stateCopy.isTraceSizeVisible).toEqual(true);
});
});
describe('when traceSize is bigger than the total size', () => {
it('sets isTraceSizeVisible to false', () => {
const copy = Object.assign({}, stateCopy, { traceSize: 5118460, size: 2321312 });
mutations[types.RECEIVE_TRACE_SUCCESS](copy, { total: 511846 });
expect(copy.isTraceSizeVisible).toEqual(false);
});
});
it('sets trace, trace size and isTraceComplete', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
html,
size: 511846,
complete: true,
});
expect(stateCopy.trace).toEqual(html);
expect(stateCopy.traceSize).toEqual(511846);
expect(stateCopy.isTraceComplete).toEqual(true);
});
});
describe('STOP_POLLING_TRACE', () => {
it('sets isTraceComplete to true', () => {
mutations[types.STOP_POLLING_TRACE](stateCopy);
expect(stateCopy.isTraceComplete).toEqual(true);
});
});
describe('RECEIVE_TRACE_ERROR', () => {
it('resets trace state and sets error to true', () => {
mutations[types.RECEIVE_TRACE_ERROR](stateCopy);
expect(stateCopy.isLoadingTrace).toEqual(false);
expect(stateCopy.isTraceComplete).toEqual(true);
expect(stateCopy.hasTraceError).toEqual(true);
});
});
describe('REQUEST_JOB', () => {
it('sets isLoading to true', () => {
mutations[types.REQUEST_JOB](stateCopy);
expect(stateCopy.isLoading).toEqual(true);
});
});
describe('RECEIVE_JOB_SUCCESS', () => {
beforeEach(() => {
mutations[types.RECEIVE_JOB_SUCCESS](stateCopy, { id: 1312321 });
});
it('sets is loading to false', () => {
expect(stateCopy.isLoading).toEqual(false);
});
it('sets hasError to false', () => {
expect(stateCopy.hasError).toEqual(false);
});
it('sets job data', () => {
expect(stateCopy.job).toEqual({ id: 1312321 });
});
});
describe('RECEIVE_JOB_ERROR', () => {
it('resets job data', () => {
mutations[types.RECEIVE_JOB_ERROR](stateCopy);
expect(stateCopy.isLoading).toEqual(false);
expect(stateCopy.hasError).toEqual(true);
expect(stateCopy.job).toEqual({});
});
});
describe('SCROLL_TO_TOP', () => {
beforeEach(() => {
mutations[types.SCROLL_TO_TOP](stateCopy);
});
it('sets isTraceScrolledToBottom to false', () => {
expect(stateCopy.isTraceScrolledToBottom).toEqual(false);
});
it('sets hasBeenScrolled to true', () => {
expect(stateCopy.hasBeenScrolled).toEqual(true);
});
});
describe('SCROLL_TO_BOTTOM', () => {
beforeEach(() => {
mutations[types.SCROLL_TO_BOTTOM](stateCopy);
});
it('sets isTraceScrolledToBottom to true', () => {
expect(stateCopy.isTraceScrolledToBottom).toEqual(true);
});
it('sets hasBeenScrolled to true', () => {
expect(stateCopy.hasBeenScrolled).toEqual(true);
});
});
describe('REQUEST_STAGES', () => {
it('sets isLoadingStages to true', () => {
mutations[types.REQUEST_STAGES](stateCopy);
expect(stateCopy.isLoadingStages).toEqual(true);
});
});
describe('RECEIVE_STAGES_SUCCESS', () => {
beforeEach(() => {
mutations[types.RECEIVE_STAGES_SUCCESS](stateCopy, [{ name: 'build' }]);
});
it('sets isLoadingStages to false', () => {
expect(stateCopy.isLoadingStages).toEqual(false);
});
it('sets stages', () => {
expect(stateCopy.stages).toEqual([{ name: 'build' }]);
});
});
describe('RECEIVE_STAGES_ERROR', () => {
beforeEach(() => {
mutations[types.RECEIVE_STAGES_ERROR](stateCopy);
});
it('sets isLoadingStages to false', () => {
expect(stateCopy.isLoadingStages).toEqual(false);
});
it('resets stages', () => {
expect(stateCopy.stages).toEqual([]);
});
});
describe('REQUEST_JOBS_FOR_STAGE', () => {
it('sets isLoadingStages to true', () => {
mutations[types.REQUEST_JOBS_FOR_STAGE](stateCopy);
expect(stateCopy.isLoadingJobs).toEqual(true);
});
});
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([]);
});
});
});

View File

@ -403,6 +403,7 @@ describe('common_utils', () => {
afterEach(() => {
document.body.removeChild(document.getElementById('favicon'));
});
it('should set page favicon to provided favicon', () => {
const faviconPath = '//custom_favicon';
commonUtils.setFavicon(faviconPath);
@ -479,17 +480,14 @@ describe('common_utils', () => {
});
it('should reset favicon in case of error', (done) => {
mock.onGet(BUILD_URL).networkError();
mock.onGet(BUILD_URL).replyOnce(500);
commonUtils.setCiStatusFavicon(BUILD_URL)
.then(() => {
.catch(() => {
const favicon = document.getElementById('favicon');
expect(favicon.getAttribute('href')).toEqual(faviconDataUrl);
done();
})
// Error is already caught in catch() block of setCiStatusFavicon,
// It won't throw another error for us to catch
.catch(done.fail);
});
});
it('should set page favicon to CI status favicon based on provided status', (done) => {