diff options
Diffstat (limited to 'spec/frontend/pipelines/graph_shared/links_layer_spec.js')
-rw-r--r-- | spec/frontend/pipelines/graph_shared/links_layer_spec.js | 200 |
1 files changed, 156 insertions, 44 deletions
diff --git a/spec/frontend/pipelines/graph_shared/links_layer_spec.js b/spec/frontend/pipelines/graph_shared/links_layer_spec.js index 5e5365eef30..932a19f2f00 100644 --- a/spec/frontend/pipelines/graph_shared/links_layer_spec.js +++ b/spec/frontend/pipelines/graph_shared/links_layer_spec.js @@ -1,32 +1,33 @@ -import { GlAlert } from '@gitlab/ui'; -import { fireEvent, within } from '@testing-library/dom'; -import { mount, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { + PIPELINES_DETAIL_LINK_DURATION, + PIPELINES_DETAIL_LINKS_TOTAL, + PIPELINES_DETAIL_LINKS_JOB_RATIO, +} from '~/performance/constants'; +import * as perfUtils from '~/performance/utils'; +import * as Api from '~/pipelines/components/graph_shared/api'; import LinksInner from '~/pipelines/components/graph_shared/links_inner.vue'; import LinksLayer from '~/pipelines/components/graph_shared/links_layer.vue'; +import * as sentryUtils from '~/pipelines/utils'; import { generateResponse, mockPipelineResponse } from '../graph/mock_data'; describe('links layer component', () => { let wrapper; - const withinComponent = () => within(wrapper.element); - const findAlert = () => wrapper.find(GlAlert); - const findShowAnyways = () => - withinComponent().getByText(wrapper.vm.$options.i18n.showLinksAnyways); const findLinksInner = () => wrapper.find(LinksInner); const pipeline = generateResponse(mockPipelineResponse, 'root/fungi-xoxo'); const containerId = `pipeline-links-container-${pipeline.id}`; const slotContent = "<div>Ceci n'est pas un graphique</div>"; - const tooManyStages = Array(101) - .fill(0) - .flatMap(() => pipeline.stages); - const defaultProps = { containerId, containerMeasurements: { width: 400, height: 400 }, pipelineId: pipeline.id, pipelineData: pipeline.stages, + showLinks: false, }; const createComponent = ({ mountFn = shallowMount, props = {} } = {}) => { @@ -46,10 +47,9 @@ describe('links layer component', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); - describe('with data under max stages', () => { + describe('with show links off', () => { beforeEach(() => { createComponent(); }); @@ -58,62 +58,174 @@ describe('links layer component', () => { expect(wrapper.html()).toContain(slotContent); }); + it('does not render inner links component', () => { + expect(findLinksInner().exists()).toBe(false); + }); + }); + + describe('with show links on', () => { + beforeEach(() => { + createComponent({ + props: { + showLinks: true, + }, + }); + }); + + it('renders the default slot', () => { + expect(wrapper.html()).toContain(slotContent); + }); + it('renders the inner links component', () => { expect(findLinksInner().exists()).toBe(true); }); }); - describe('with more than the max number of stages', () => { - describe('rendering', () => { - beforeEach(() => { - createComponent({ props: { pipelineData: tooManyStages } }); - }); + describe('with width or height measurement at 0', () => { + beforeEach(() => { + createComponent({ props: { containerMeasurements: { width: 0, height: 100 } } }); + }); - it('renders the default slot', () => { - expect(wrapper.html()).toContain(slotContent); - }); + it('renders the default slot', () => { + expect(wrapper.html()).toContain(slotContent); + }); - it('renders the alert component', () => { - expect(findAlert().exists()).toBe(true); - }); + it('does not render the inner links component', () => { + expect(findLinksInner().exists()).toBe(false); + }); + }); - it('does not render the inner links component', () => { - expect(findLinksInner().exists()).toBe(false); - }); + describe('performance metrics', () => { + const metricsPath = '/root/project/-/ci/prometheus_metrics/histograms.json'; + let markAndMeasure; + let reportToSentry; + let reportPerformance; + let mock; + + beforeEach(() => { + jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb()); + markAndMeasure = jest.spyOn(perfUtils, 'performanceMarkAndMeasure'); + reportToSentry = jest.spyOn(sentryUtils, 'reportToSentry'); + reportPerformance = jest.spyOn(Api, 'reportPerformance'); }); - describe('with width or height measurement at 0', () => { + describe('with no metrics config object', () => { beforeEach(() => { - createComponent({ props: { containerMeasurements: { width: 0, height: 100 } } }); + createComponent(); }); - it('renders the default slot', () => { - expect(wrapper.html()).toContain(slotContent); + it('is not called', () => { + expect(markAndMeasure).not.toHaveBeenCalled(); + expect(reportToSentry).not.toHaveBeenCalled(); + expect(reportPerformance).not.toHaveBeenCalled(); }); + }); - it('does not render the alert component', () => { - expect(findAlert().exists()).toBe(false); + describe('with metrics config set to false', () => { + beforeEach(() => { + createComponent({ + props: { + metricsConfig: { + collectMetrics: false, + metricsPath: '/path/to/metrics', + }, + }, + }); }); - it('does not render the inner links component', () => { - expect(findLinksInner().exists()).toBe(false); + it('is not called', () => { + expect(markAndMeasure).not.toHaveBeenCalled(); + expect(reportToSentry).not.toHaveBeenCalled(); + expect(reportPerformance).not.toHaveBeenCalled(); }); }); - describe('interactions', () => { + describe('with no metrics path', () => { beforeEach(() => { - createComponent({ mountFn: mount, props: { pipelineData: tooManyStages } }); + createComponent({ + props: { + metricsConfig: { + collectMetrics: true, + metricsPath: '', + }, + }, + }); }); - it('renders the disable button', () => { - expect(findShowAnyways()).not.toBe(null); + it('is not called', () => { + expect(markAndMeasure).not.toHaveBeenCalled(); + expect(reportToSentry).not.toHaveBeenCalled(); + expect(reportPerformance).not.toHaveBeenCalled(); + }); + }); + + describe('with metrics path and collect set to true', () => { + const duration = 875; + const numLinks = 7; + const totalGroups = 8; + const metricsData = { + histograms: [ + { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 }, + { name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks }, + { + name: PIPELINES_DETAIL_LINKS_JOB_RATIO, + value: numLinks / totalGroups, + }, + ], + }; + + describe('when no duration is obtained', () => { + beforeEach(() => { + jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => { + return []; + }); + + createComponent({ + props: { + metricsConfig: { + collectMetrics: true, + path: metricsPath, + }, + }, + }); + }); + + it('attempts to collect metrics', () => { + expect(markAndMeasure).toHaveBeenCalled(); + expect(reportPerformance).not.toHaveBeenCalled(); + expect(reportToSentry).not.toHaveBeenCalled(); + }); }); - it('shows links when override is clicked', async () => { - expect(findLinksInner().exists()).toBe(false); - fireEvent(findShowAnyways(), new MouseEvent('click', { bubbles: true })); - await wrapper.vm.$nextTick(); - expect(findLinksInner().exists()).toBe(true); + describe('with duration and no error', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onPost(metricsPath).reply(200, {}); + + jest.spyOn(window.performance, 'getEntriesByName').mockImplementation(() => { + return [{ duration }]; + }); + + createComponent({ + props: { + metricsConfig: { + collectMetrics: true, + path: metricsPath, + }, + }, + }); + }); + + afterEach(() => { + mock.restore(); + }); + + it('it calls reportPerformance with expected arguments', () => { + expect(markAndMeasure).toHaveBeenCalled(); + expect(reportPerformance).toHaveBeenCalled(); + expect(reportPerformance).toHaveBeenCalledWith(metricsPath, metricsData); + expect(reportToSentry).not.toHaveBeenCalled(); + }); }); }); }); |