Commit f896f449 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Update related specs

Fix jest base specs

Updates the base vue js specs for
project value stream analytics

Update value stream metrics specs

Added time metric fixtures for project vsa
to generate relevant data

Fix value stream metrics specs
parent 5a549aad
# frozen_string_literal: true # frozen_string_literal: true
class Projects::Analytics::CycleAnalytics::SummaryController < Projects::ApplicationController class Projects::Analytics::CycleAnalytics::SummaryController < Projects::ApplicationController
include CycleAnalyticsParams include CycleAnalyticsParams
......
...@@ -6,6 +6,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper'; ...@@ -6,6 +6,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import BaseComponent from '~/cycle_analytics/components/base.vue'; import BaseComponent from '~/cycle_analytics/components/base.vue';
import PathNavigation from '~/cycle_analytics/components/path_navigation.vue'; import PathNavigation from '~/cycle_analytics/components/path_navigation.vue';
import StageTable from '~/cycle_analytics/components/stage_table.vue'; import StageTable from '~/cycle_analytics/components/stage_table.vue';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants'; import { NOT_ENOUGH_DATA_ERROR } from '~/cycle_analytics/constants';
import initState from '~/cycle_analytics/store/state'; import initState from '~/cycle_analytics/store/state';
import { import {
...@@ -23,6 +24,7 @@ const selectedStageEvents = issueEvents.events; ...@@ -23,6 +24,7 @@ const selectedStageEvents = issueEvents.events;
const noDataSvgPath = 'path/to/no/data'; const noDataSvgPath = 'path/to/no/data';
const noAccessSvgPath = 'path/to/no/access'; const noAccessSvgPath = 'path/to/no/access';
const selectedStageCount = stageCounts[selectedStage.id]; const selectedStageCount = stageCounts[selectedStage.id];
const fullPath = 'full/path/to/foo';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -34,6 +36,7 @@ const defaultState = { ...@@ -34,6 +36,7 @@ const defaultState = {
createdBefore, createdBefore,
createdAfter, createdAfter,
stageCounts, stageCounts,
endpoints: { fullPath },
}; };
function createStore({ initialState = {}, initialGetters = {} }) { function createStore({ initialState = {}, initialGetters = {} }) {
...@@ -45,6 +48,10 @@ function createStore({ initialState = {}, initialGetters = {} }) { ...@@ -45,6 +48,10 @@ function createStore({ initialState = {}, initialGetters = {} }) {
}, },
getters: { getters: {
pathNavigationData: () => transformedProjectStagePathData, pathNavigationData: () => transformedProjectStagePathData,
filterParams: () => ({
created_after: createdAfter,
created_before: createdBefore,
}),
...initialGetters, ...initialGetters,
}, },
}); });
...@@ -67,7 +74,7 @@ function createComponent({ initialState, initialGetters } = {}) { ...@@ -67,7 +74,7 @@ function createComponent({ initialState, initialGetters } = {}) {
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findPathNavigation = () => wrapper.findComponent(PathNavigation); const findPathNavigation = () => wrapper.findComponent(PathNavigation);
const findOverviewMetrics = () => wrapper.findByTestId('vsa-stage-overview-metrics'); const findOverviewMetrics = () => wrapper.findComponent(ValueStreamMetrics);
const findStageTable = () => wrapper.findComponent(StageTable); const findStageTable = () => wrapper.findComponent(StageTable);
const findStageEvents = () => findStageTable().props('stageEvents'); const findStageEvents = () => findStageTable().props('stageEvents');
const findEmptyStageTitle = () => wrapper.findComponent(GlEmptyState).props('title'); const findEmptyStageTitle = () => wrapper.findComponent(GlEmptyState).props('title');
...@@ -121,14 +128,14 @@ describe('Value stream analytics component', () => { ...@@ -121,14 +128,14 @@ describe('Value stream analytics component', () => {
expect(findPathNavigation().props('loading')).toBe(true); expect(findPathNavigation().props('loading')).toBe(true);
}); });
it('does not render the overview metrics', () => {
expect(findOverviewMetrics().exists()).toBe(false);
});
it('does not render the stage table', () => { it('does not render the stage table', () => {
expect(findStageTable().exists()).toBe(false); expect(findStageTable().exists()).toBe(false);
}); });
it('renders the overview metrics', () => {
expect(findOverviewMetrics().exists()).toBe(true);
});
it('renders the loading icon', () => { it('renders the loading icon', () => {
expect(findLoadingIcon().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(true);
}); });
......
...@@ -15,8 +15,11 @@ export const getStageByTitle = (stages, title) => ...@@ -15,8 +15,11 @@ export const getStageByTitle = (stages, title) =>
const fixtureEndpoints = { const fixtureEndpoints = {
customizableCycleAnalyticsStagesAndEvents: 'projects/analytics/value_stream_analytics/stages', customizableCycleAnalyticsStagesAndEvents: 'projects/analytics/value_stream_analytics/stages',
stageEvents: (stage) => `projects/analytics/value_stream_analytics/events/${stage}`, stageEvents: (stage) => `projects/analytics/value_stream_analytics/events/${stage}`,
metricsData: 'projects/analytics/value_stream_analytics/summary',
}; };
export const metricsData = getJSONFixture(fixtureEndpoints.metricsData);
export const customizableStagesAndEvents = getJSONFixture( export const customizableStagesAndEvents = getJSONFixture(
fixtureEndpoints.customizableCycleAnalyticsStagesAndEvents, fixtureEndpoints.customizableCycleAnalyticsStagesAndEvents,
); );
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts'; import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'; import waitForPromises from 'helpers/wait_for_promises';
import Api from 'ee/api'; // TODO: fix this for FOSS
import { group } from 'jest/cycle_analytics/mock_data';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue'; import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { timeMetricsData, recentActivityData } from '../mock_data'; import { group, metricsData } from './mock_data';
const allMetrics = [...timeMetricsData, ...recentActivityData];
jest.mock('~/flash'); jest.mock('~/flash');
describe('ValueStreamMetrics', () => { describe('ValueStreamMetrics', () => {
const { full_path: requestPath } = group;
let wrapper; let wrapper;
let mockGetValueStreamSummaryMetrics;
const { full_path: requestPath } = group;
const fakeReqName = 'Mock metrics';
const createComponent = ({ requestParams = {} } = {}) => { const createComponent = ({ requestParams = {} } = {}) => {
return shallowMount(ValueStreamMetrics, { return shallowMount(ValueStreamMetrics, {
propsData: { propsData: {
requestPath, requestPath,
requestParams, requestParams,
requests: [{ request: mockGetValueStreamSummaryMetrics, name: fakeReqName }],
}, },
}); });
}; };
const findMetrics = () => wrapper.findComponent(GlSingleStat).props('metrics'); const findMetrics = () => wrapper.findAllComponents(GlSingleStat);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
}); });
it('will display a loading icon if `true`', async () => {
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
wrapper = createComponent();
await wrapper.vm.$nextTick();
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
});
describe('with successful requests', () => { describe('with successful requests', () => {
beforeEach(async () => { beforeEach(async () => {
jest.spyOn(Api, 'cycleAnalyticsTimeSummaryData').mockResolvedValue({ data: timeMetricsData }); mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
jest.spyOn(Api, 'cycleAnalyticsSummaryData').mockResolvedValue({ data: recentActivityData });
wrapper = createComponent(); wrapper = createComponent();
await nextTick; await waitForPromises();
}); });
it.each(['cycleAnalyticsTimeSummaryData', 'cycleAnalyticsSummaryData'])( it('fetches data for the `getValueStreamSummaryMetrics` request', () => {
'fetches data for the %s request', expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith(requestPath, {});
(request) => { });
expect(Api[request]).toHaveBeenCalledWith(requestPath, {});
},
);
it.each` it.each`
index | value | title | unit index | value | title | unit
${0} | ${timeMetricsData[0].value} | ${timeMetricsData[0].title} | ${timeMetricsData[0].unit} ${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit}
${1} | ${timeMetricsData[1].value} | ${timeMetricsData[1].title} | ${timeMetricsData[1].unit} ${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit}
${2} | ${recentActivityData[0].value} | ${recentActivityData[0].title} | ${recentActivityData[0].unit} ${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit}
${3} | ${recentActivityData[1].value} | ${recentActivityData[1].title} | ${recentActivityData[1].unit} ${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit}
${4} | ${recentActivityData[2].value} | ${recentActivityData[2].title} | ${recentActivityData[2].unit}
`( `(
'renders a single stat component for the $title with value and unit', 'renders a single stat component for the $title with value and unit',
({ index, value, title, unit }) => { ({ index, value, title, unit }) => {
const metric = findAllMetrics().at(index); const metric = findMetrics().at(index);
const expectedUnit = unit ?? ''; const expectedUnit = unit ?? '';
expect(metric.props('value')).toBe(value); expect(metric.props('value')).toBe(value);
...@@ -71,11 +74,6 @@ describe('ValueStreamMetrics', () => { ...@@ -71,11 +74,6 @@ describe('ValueStreamMetrics', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false); expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
}); });
it('will display a loading icon if `true`', () => {
wrapper = createComponent({ isLoading: true });
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
});
describe('with additional params', () => { describe('with additional params', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper = createComponent({ wrapper = createComponent({
...@@ -86,50 +84,30 @@ describe('ValueStreamMetrics', () => { ...@@ -86,50 +84,30 @@ describe('ValueStreamMetrics', () => {
}, },
}); });
await nextTick; await waitForPromises();
}); });
it.each(['cycleAnalyticsTimeSummaryData', 'cycleAnalyticsSummaryData'])( it('fetches data for the `getValueStreamSummaryMetrics` request', () => {
'sends additional parameters as query paremeters in %s request', expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith(requestPath, {
(request) => { 'project_ids[]': [1],
expect(Api[request]).toHaveBeenCalledWith(requestPath, { created_after: '2020-01-01',
'project_ids[]': [1], created_before: '2020-02-01',
created_after: '2020-01-01',
created_before: '2020-02-01',
});
},
);
});
describe('metrics', () => {
it('sets the metrics component props', () => {
const metricsProps = findMetrics();
allMetrics.forEach((metric, index) => {
const currentProp = metricsProps[index];
expect(currentProp.label).toEqual(metric.title);
expect(currentProp.value).toEqual(metric.value);
expect(currentProp.unit).toEqual(metric.unit);
}); });
}); });
}); });
}); });
describe.each` describe('with a request failing', () => {
metric | failedRequest | succesfulRequest
${'time summary'} | ${'cycleAnalyticsTimeSummaryData'} | ${'cycleAnalyticsSummaryData'}
${'recent activity'} | ${'cycleAnalyticsSummaryData'} | ${'cycleAnalyticsTimeSummaryData'}
`('with the $failedRequest request failing', ({ metric, failedRequest, succesfulRequest }) => {
beforeEach(async () => { beforeEach(async () => {
jest.spyOn(Api, failedRequest).mockRejectedValue(); mockGetValueStreamSummaryMetrics = jest.fn().mockRejectedValue();
jest.spyOn(Api, succesfulRequest).mockResolvedValue(Promise.resolve({}));
wrapper = createComponent(); wrapper = createComponent();
await wrapper.vm.$nextTick(); await waitForPromises();
}); });
it('it should render a error message', () => { it('it should render a error message', () => {
expect(createFlash).toHaveBeenCalledWith({ expect(createFlash).toHaveBeenCalledWith({
message: `There was an error while fetching value stream analytics ${metric} data.`, message: `There was an error while fetching value stream analytics ${fakeReqName} data.`,
}); });
}); });
}); });
......
...@@ -51,4 +51,21 @@ RSpec.describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do ...@@ -51,4 +51,21 @@ RSpec.describe 'Analytics (JavaScript fixtures)', :sidekiq_inline do
end end
end end
end end
describe Projects::Analytics::CycleAnalytics::SummaryController, type: :controller do
render_views
let(:params) { { namespace_id: group, project_id: project, value_stream_id: value_stream_id } }
before do
project.add_developer(user)
sign_in(user)
end
it "projects/analytics/value_stream_analytics/summary" do
get(:show, params: params, format: :json)
expect(response).to be_successful
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment