2021-06-02 05:09:46 -04:00
|
|
|
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
|
|
|
|
import { shallowMount } from '@vue/test-utils';
|
|
|
|
import Vue from 'vue';
|
|
|
|
import Vuex from 'vuex';
|
|
|
|
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
2022-02-04 07:17:40 -05:00
|
|
|
import ValueStreamMetrics from '~/analytics/shared/components/value_stream_metrics.vue';
|
2021-06-02 05:09:46 -04:00
|
|
|
import BaseComponent from '~/cycle_analytics/components/base.vue';
|
|
|
|
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
|
2021-07-20 05:08:43 -04:00
|
|
|
import StageTable from '~/cycle_analytics/components/stage_table.vue';
|
2021-09-07 11:11:06 -04:00
|
|
|
import ValueStreamFilters from '~/cycle_analytics/components/value_stream_filters.vue';
|
2021-07-20 05:08:43 -04:00
|
|
|
import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants';
|
2021-06-02 05:09:46 -04:00
|
|
|
import initState from '~/cycle_analytics/store/state';
|
2021-08-04 14:09:57 -04:00
|
|
|
import {
|
|
|
|
permissions,
|
|
|
|
transformedProjectStagePathData,
|
|
|
|
selectedStage,
|
|
|
|
issueEvents,
|
|
|
|
createdBefore,
|
|
|
|
createdAfter,
|
|
|
|
currentGroup,
|
2021-08-06 14:09:57 -04:00
|
|
|
stageCounts,
|
2021-10-12 11:12:08 -04:00
|
|
|
initialPaginationState as pagination,
|
2021-08-04 14:09:57 -04:00
|
|
|
} from './mock_data';
|
2021-06-02 05:09:46 -04:00
|
|
|
|
2021-07-23 08:09:05 -04:00
|
|
|
const selectedStageEvents = issueEvents.events;
|
2021-06-02 05:09:46 -04:00
|
|
|
const noDataSvgPath = 'path/to/no/data';
|
|
|
|
const noAccessSvgPath = 'path/to/no/access';
|
2021-08-06 14:09:57 -04:00
|
|
|
const selectedStageCount = stageCounts[selectedStage.id];
|
2021-08-16 08:09:17 -04:00
|
|
|
const fullPath = 'full/path/to/foo';
|
2021-06-02 05:09:46 -04:00
|
|
|
|
|
|
|
Vue.use(Vuex);
|
|
|
|
|
|
|
|
let wrapper;
|
|
|
|
|
2021-09-07 11:11:06 -04:00
|
|
|
const { id: groupId, path: groupPath } = currentGroup;
|
2021-08-04 14:09:57 -04:00
|
|
|
const defaultState = {
|
|
|
|
permissions,
|
|
|
|
currentGroup,
|
|
|
|
createdBefore,
|
|
|
|
createdAfter,
|
2021-08-06 14:09:57 -04:00
|
|
|
stageCounts,
|
2021-09-07 11:11:06 -04:00
|
|
|
endpoints: { fullPath, groupId, groupPath },
|
2021-08-04 14:09:57 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
function createStore({ initialState = {}, initialGetters = {} }) {
|
2021-06-02 05:09:46 -04:00
|
|
|
return new Vuex.Store({
|
|
|
|
state: {
|
|
|
|
...initState(),
|
2021-08-04 14:09:57 -04:00
|
|
|
...defaultState,
|
2021-06-02 05:09:46 -04:00
|
|
|
...initialState,
|
|
|
|
},
|
|
|
|
getters: {
|
2021-08-04 14:09:57 -04:00
|
|
|
pathNavigationData: () => transformedProjectStagePathData,
|
2021-08-16 08:09:17 -04:00
|
|
|
filterParams: () => ({
|
|
|
|
created_after: createdAfter,
|
|
|
|
created_before: createdBefore,
|
|
|
|
}),
|
2021-08-04 14:09:57 -04:00
|
|
|
...initialGetters,
|
2021-06-02 05:09:46 -04:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-08-04 14:09:57 -04:00
|
|
|
function createComponent({ initialState, initialGetters } = {}) {
|
2021-06-02 05:09:46 -04:00
|
|
|
return extendedWrapper(
|
|
|
|
shallowMount(BaseComponent, {
|
2021-08-04 14:09:57 -04:00
|
|
|
store: createStore({ initialState, initialGetters }),
|
2021-06-02 05:09:46 -04:00
|
|
|
propsData: {
|
|
|
|
noDataSvgPath,
|
|
|
|
noAccessSvgPath,
|
|
|
|
},
|
2021-07-20 05:08:43 -04:00
|
|
|
stubs: {
|
|
|
|
StageTable,
|
|
|
|
},
|
2021-06-02 05:09:46 -04:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
|
|
|
const findPathNavigation = () => wrapper.findComponent(PathNavigation);
|
2021-09-07 11:11:06 -04:00
|
|
|
const findFilters = () => wrapper.findComponent(ValueStreamFilters);
|
2021-08-16 08:09:17 -04:00
|
|
|
const findOverviewMetrics = () => wrapper.findComponent(ValueStreamMetrics);
|
2021-07-20 05:08:43 -04:00
|
|
|
const findStageTable = () => wrapper.findComponent(StageTable);
|
|
|
|
const findStageEvents = () => findStageTable().props('stageEvents');
|
|
|
|
const findEmptyStageTitle = () => wrapper.findComponent(GlEmptyState).props('title');
|
2021-10-12 11:12:08 -04:00
|
|
|
const findPagination = () => wrapper.findByTestId('vsa-stage-pagination');
|
2021-06-02 05:09:46 -04:00
|
|
|
|
2021-08-16 08:09:17 -04:00
|
|
|
const hasMetricsRequests = (reqs) => {
|
|
|
|
const foundReqs = findOverviewMetrics().props('requests');
|
|
|
|
expect(foundReqs.length).toEqual(reqs.length);
|
|
|
|
expect(foundReqs.map(({ name }) => name)).toEqual(reqs);
|
|
|
|
};
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
describe('Value stream analytics component', () => {
|
|
|
|
beforeEach(() => {
|
2021-10-12 11:12:08 -04:00
|
|
|
wrapper = createComponent({ initialState: { selectedStage, selectedStageEvents, pagination } });
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
wrapper.destroy();
|
|
|
|
wrapper = null;
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the path navigation component', () => {
|
|
|
|
expect(findPathNavigation().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-08-06 14:09:57 -04:00
|
|
|
it('receives the stages formatted for the path navigation', () => {
|
|
|
|
expect(findPathNavigation().props('stages')).toBe(transformedProjectStagePathData);
|
|
|
|
});
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
it('renders the overview metrics', () => {
|
|
|
|
expect(findOverviewMetrics().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-08-16 08:09:17 -04:00
|
|
|
it('passes requests prop to the metrics component', () => {
|
|
|
|
hasMetricsRequests(['recent activity']);
|
|
|
|
});
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
it('renders the stage table', () => {
|
|
|
|
expect(findStageTable().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-08-06 14:09:57 -04:00
|
|
|
it('passes the selected stage count to the stage table', () => {
|
|
|
|
expect(findStageTable().props('stageCount')).toBe(selectedStageCount);
|
|
|
|
});
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
it('renders the stage table events', () => {
|
2021-07-20 05:08:43 -04:00
|
|
|
expect(findStageEvents()).toEqual(selectedStageEvents);
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
2021-09-07 11:11:06 -04:00
|
|
|
it('renders the filters', () => {
|
|
|
|
expect(findFilters().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('displays the date range selector and hides the project selector', () => {
|
|
|
|
expect(findFilters().props()).toMatchObject({
|
|
|
|
hasProjectFilter: false,
|
|
|
|
hasDateRangeFilter: true,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('passes the paths to the filter bar', () => {
|
|
|
|
expect(findFilters().props()).toEqual({
|
|
|
|
groupId,
|
|
|
|
groupPath,
|
|
|
|
endDate: createdBefore,
|
|
|
|
hasDateRangeFilter: true,
|
|
|
|
hasProjectFilter: false,
|
|
|
|
selectedProjects: [],
|
|
|
|
startDate: createdAfter,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
it('does not render the loading icon', () => {
|
|
|
|
expect(findLoadingIcon().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
2021-10-12 11:12:08 -04:00
|
|
|
it('renders pagination', () => {
|
|
|
|
expect(findPagination().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-08-16 08:09:17 -04:00
|
|
|
describe('with `cycleAnalyticsForGroups=true` license', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
wrapper = createComponent({ initialState: { features: { cycleAnalyticsForGroups: true } } });
|
|
|
|
});
|
|
|
|
|
|
|
|
it('passes requests prop to the metrics component', () => {
|
|
|
|
hasMetricsRequests(['time summary', 'recent activity']);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
describe('isLoading = true', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
wrapper = createComponent({
|
|
|
|
initialState: { isLoading: true },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the path navigation component with prop `loading` set to true', () => {
|
2021-08-04 14:09:57 -04:00
|
|
|
expect(findPathNavigation().props('loading')).toBe(true);
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render the stage table', () => {
|
|
|
|
expect(findStageTable().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
2021-08-16 08:09:17 -04:00
|
|
|
it('renders the overview metrics', () => {
|
|
|
|
expect(findOverviewMetrics().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-06-02 05:09:46 -04:00
|
|
|
it('renders the loading icon', () => {
|
|
|
|
expect(findLoadingIcon().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('isLoadingStage = true', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
wrapper = createComponent({
|
|
|
|
initialState: { isLoadingStage: true },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the stage table with a loading icon', () => {
|
|
|
|
const tableWrapper = findStageTable();
|
|
|
|
expect(tableWrapper.exists()).toBe(true);
|
|
|
|
expect(tableWrapper.find(GlLoadingIcon).exists()).toBe(true);
|
|
|
|
});
|
2021-08-04 14:09:57 -04:00
|
|
|
|
|
|
|
it('renders the path navigation loading state', () => {
|
|
|
|
expect(findPathNavigation().props('loading')).toBe(true);
|
|
|
|
});
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('isEmptyStage = true', () => {
|
2021-08-04 14:09:57 -04:00
|
|
|
const emptyStageParams = {
|
|
|
|
isEmptyStage: true,
|
|
|
|
selectedStage: { ...selectedStage, emptyStageText: 'This stage is empty' },
|
|
|
|
};
|
2021-06-02 05:09:46 -04:00
|
|
|
beforeEach(() => {
|
2021-08-04 14:09:57 -04:00
|
|
|
wrapper = createComponent({ initialState: emptyStageParams });
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the empty stage with `Not enough data` message', () => {
|
2021-07-20 05:08:43 -04:00
|
|
|
expect(findEmptyStageTitle()).toBe(NOT_ENOUGH_DATA_ERROR);
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
2021-06-07 05:10:26 -04:00
|
|
|
|
|
|
|
describe('with a selectedStageError', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
wrapper = createComponent({
|
|
|
|
initialState: {
|
2021-08-04 14:09:57 -04:00
|
|
|
...emptyStageParams,
|
2021-06-07 05:10:26 -04:00
|
|
|
selectedStageError: 'There is too much data to calculate',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the empty stage with `There is too much data to calculate` message', () => {
|
2021-07-20 05:08:43 -04:00
|
|
|
expect(findEmptyStageTitle()).toBe('There is too much data to calculate');
|
2021-06-07 05:10:26 -04:00
|
|
|
});
|
|
|
|
});
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
describe('without enough permissions', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
wrapper = createComponent({
|
2021-06-11 11:09:58 -04:00
|
|
|
initialState: {
|
2021-08-04 14:09:57 -04:00
|
|
|
selectedStage,
|
2021-06-11 11:09:58 -04:00
|
|
|
permissions: {
|
2021-08-04 14:09:57 -04:00
|
|
|
...permissions,
|
2021-06-11 11:09:58 -04:00
|
|
|
[selectedStage.id]: false,
|
|
|
|
},
|
|
|
|
},
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2021-07-20 05:08:43 -04:00
|
|
|
it('renders the empty stage with `You need permission.` message', () => {
|
|
|
|
expect(findEmptyStageTitle()).toBe('You need permission.');
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('without a selected stage', () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
wrapper = createComponent({
|
2021-08-04 14:09:57 -04:00
|
|
|
initialGetters: { pathNavigationData: () => [] },
|
2021-06-02 05:09:46 -04:00
|
|
|
initialState: { selectedStage: null, isEmptyStage: true },
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('renders the stage table', () => {
|
|
|
|
expect(findStageTable().exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2021-08-04 14:09:57 -04:00
|
|
|
it('does not render the path navigation', () => {
|
2021-06-02 05:09:46 -04:00
|
|
|
expect(findPathNavigation().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render the stage table events', () => {
|
2021-07-20 05:08:43 -04:00
|
|
|
expect(findStageEvents()).toHaveLength(0);
|
2021-06-02 05:09:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
it('does not render the loading icon', () => {
|
|
|
|
expect(findLoadingIcon().exists()).toBe(false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|