diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-24 18:09:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-24 18:09:05 +0000 |
commit | c2367afbf57ebc65d5b78a743b5d6a91f0aece9f (patch) | |
tree | 165c2c54bf72ab3a3a9417d97f63ece5c9eba9f5 /spec/frontend | |
parent | 51a9512965d86e3094968fa514e4ae8a96d38cf3 (diff) | |
download | gitlab-ce-c2367afbf57ebc65d5b78a743b5d6a91f0aece9f.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
11 files changed, 549 insertions, 438 deletions
diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap index 3d56bef4b33..09977ecc7a3 100644 --- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap +++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap @@ -8,13 +8,13 @@ exports[`grafana integration component default state to match the default snapsh <div class="settings-header" > - <h4 - class="js-section-header" + <h3 + class="js-section-header h4" > Grafana Authentication - </h4> + </h3> <gl-button-stub class="js-settings-toggle" diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 49f2a70a8b2..4dd376faac0 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { setTestTimeout } from 'helpers/timeout'; import { GlLink } from '@gitlab/ui'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; +import { cloneDeep } from 'lodash'; import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; import { chartColorValues } from '~/monitoring/constants'; import { createStore } from '~/monitoring/stores'; @@ -32,501 +33,563 @@ jest.mock('~/lib/utils/icon_utils', () => ({ describe('Time series component', () => { let mockGraphData; - let makeTimeSeriesChart; let store; - beforeEach(() => { - setTestTimeout(1000); - - store = createStore(); - - store.commit( - `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, - metricsDashboardPayload, - ); - - store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); + const makeTimeSeriesChart = (graphData, type) => + shallowMount(TimeSeries, { + propsData: { + graphData: { ...graphData, type }, + deploymentData: store.state.monitoringDashboard.deploymentData, + projectPath: `${mockHost}${mockProjectDir}`, + }, + store, + }); - // Mock data contains 2 panel groups, with 1 and 2 panels respectively - store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedQueryResultPayload, - ); + describe('With a single time series', () => { + beforeEach(() => { + setTestTimeout(1000); - // Pick the second panel group and the first panel in it - [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; + store = createStore(); - makeTimeSeriesChart = (graphData, type) => - shallowMount(TimeSeries, { - propsData: { - graphData: { ...graphData, type }, - deploymentData: store.state.monitoringDashboard.deploymentData, - projectPath: `${mockHost}${mockProjectDir}`, - }, - store, - }); - }); + store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + metricsDashboardPayload, + ); - describe('general functions', () => { - let timeSeriesChart; + store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); - const findChart = () => timeSeriesChart.find({ ref: 'chart' }); + // Mock data contains 2 panel groups, with 1 and 2 panels respectively + store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayload, + ); - beforeEach(done => { - timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); - timeSeriesChart.vm.$nextTick(done); + // Pick the second panel group and the first panel in it + [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; }); - it('allows user to override max value label text using prop', () => { - timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); - - return timeSeriesChart.vm.$nextTick().then(() => { - expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText'); - }); - }); + describe('general functions', () => { + let timeSeriesChart; - it('allows user to override average value label text using prop', () => { - timeSeriesChart.setProps({ legendAverageText: 'averageText' }); + const findChart = () => timeSeriesChart.find({ ref: 'chart' }); - return timeSeriesChart.vm.$nextTick().then(() => { - expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); + beforeEach(done => { + timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); + timeSeriesChart.vm.$nextTick(done); }); - }); - describe('events', () => { - describe('datazoom', () => { - let eChartMock; - let startValue; - let endValue; - - beforeEach(done => { - eChartMock = { - handlers: {}, - getOption: () => ({ - dataZoom: [ - { - startValue, - endValue, - }, - ], - }), - off: jest.fn(eChartEvent => { - delete eChartMock.handlers[eChartEvent]; - }), - on: jest.fn((eChartEvent, fn) => { - eChartMock.handlers[eChartEvent] = fn; - }), - }; - - timeSeriesChart = makeTimeSeriesChart(mockGraphData); - timeSeriesChart.vm.$nextTick(() => { - findChart().vm.$emit('created', eChartMock); - done(); - }); - }); + it('allows user to override max value label text using prop', () => { + timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); - it('handles datazoom event from chart', () => { - startValue = 1577836800000; // 2020-01-01T00:00:00.000Z - endValue = 1577840400000; // 2020-01-01T01:00:00.000Z - eChartMock.handlers.datazoom(); - - expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1); - expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([ - { - start: new Date(startValue).toISOString(), - end: new Date(endValue).toISOString(), - }, - ]); + return timeSeriesChart.vm.$nextTick().then(() => { + expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText'); }); }); - }); - describe('methods', () => { - describe('formatTooltipText', () => { - let mockDate; - let mockCommitUrl; - let generateSeriesData; + it('allows user to override average value label text using prop', () => { + timeSeriesChart.setProps({ legendAverageText: 'averageText' }); - beforeEach(() => { - mockDate = deploymentData[0].created_at; - mockCommitUrl = deploymentData[0].commitUrl; - generateSeriesData = type => ({ - seriesData: [ - { - seriesName: timeSeriesChart.vm.chartData[0].name, - componentSubType: type, - value: [mockDate, 5.55555], - dataIndex: 0, - }, - ], - value: mockDate, - }); + return timeSeriesChart.vm.$nextTick().then(() => { + expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); }); + }); - it('does not throw error if data point is outside the zoom range', () => { - const seriesDataWithoutValue = generateSeriesData('line'); - expect( - timeSeriesChart.vm.formatTooltipText({ - ...seriesDataWithoutValue, - seriesData: seriesDataWithoutValue.seriesData.map(data => ({ - ...data, - value: undefined, - })), - }), - ).toBeUndefined(); - }); + describe('events', () => { + describe('datazoom', () => { + let eChartMock; + let startValue; + let endValue; - describe('when series is of line type', () => { beforeEach(done => { - timeSeriesChart.vm.formatTooltipText(generateSeriesData('line')); - timeSeriesChart.vm.$nextTick(done); - }); + eChartMock = { + handlers: {}, + getOption: () => ({ + dataZoom: [ + { + startValue, + endValue, + }, + ], + }), + off: jest.fn(eChartEvent => { + delete eChartMock.handlers[eChartEvent]; + }), + on: jest.fn((eChartEvent, fn) => { + eChartMock.handlers[eChartEvent] = fn; + }), + }; - it('formats tooltip title', () => { - expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); + timeSeriesChart = makeTimeSeriesChart(mockGraphData); + timeSeriesChart.vm.$nextTick(() => { + findChart().vm.$emit('created', eChartMock); + done(); + }); }); - it('formats tooltip content', () => { - const name = 'Pod average'; - const value = '5.556'; - const dataIndex = 0; - const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); + it('handles datazoom event from chart', () => { + startValue = 1577836800000; // 2020-01-01T00:00:00.000Z + endValue = 1577840400000; // 2020-01-01T01:00:00.000Z + eChartMock.handlers.datazoom(); - expect(seriesLabel.vm.color).toBe(''); - expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); - expect(timeSeriesChart.vm.tooltip.content).toEqual([ - { name, value, dataIndex, color: undefined }, + expect(timeSeriesChart.emitted('datazoom')).toHaveLength(1); + expect(timeSeriesChart.emitted('datazoom')[0]).toEqual([ + { + start: new Date(startValue).toISOString(), + end: new Date(endValue).toISOString(), + }, ]); - - expect( - shallowWrapperContainsSlotText( - timeSeriesChart.find(GlAreaChart), - 'tooltipContent', - value, - ), - ).toBe(true); }); }); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + let mockDate; + let mockCommitUrl; + let generateSeriesData; - describe('when series is of scatter type, for deployments', () => { beforeEach(() => { - timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter')); + mockDate = deploymentData[0].created_at; + mockCommitUrl = deploymentData[0].commitUrl; + generateSeriesData = type => ({ + seriesData: [ + { + seriesName: timeSeriesChart.vm.chartData[0].name, + componentSubType: type, + value: [mockDate, 5.55555], + dataIndex: 0, + }, + ], + value: mockDate, + }); }); - it('formats tooltip title', () => { - expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); + it('does not throw error if data point is outside the zoom range', () => { + const seriesDataWithoutValue = generateSeriesData('line'); + expect( + timeSeriesChart.vm.formatTooltipText({ + ...seriesDataWithoutValue, + seriesData: seriesDataWithoutValue.seriesData.map(data => ({ + ...data, + value: undefined, + })), + }), + ).toBeUndefined(); }); - it('formats tooltip sha', () => { - expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9'); + describe('when series is of line type', () => { + beforeEach(done => { + timeSeriesChart.vm.formatTooltipText(generateSeriesData('line')); + timeSeriesChart.vm.$nextTick(done); + }); + + it('formats tooltip title', () => { + expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); + }); + + it('formats tooltip content', () => { + const name = 'Pod average'; + const value = '5.556'; + const dataIndex = 0; + const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); + + expect(seriesLabel.vm.color).toBe(''); + expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); + expect(timeSeriesChart.vm.tooltip.content).toEqual([ + { name, value, dataIndex, color: undefined }, + ]); + + expect( + shallowWrapperContainsSlotText( + timeSeriesChart.find(GlAreaChart), + 'tooltipContent', + value, + ), + ).toBe(true); + }); }); - it('formats tooltip commit url', () => { - expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); + describe('when series is of scatter type, for deployments', () => { + beforeEach(() => { + timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter')); + }); + + it('formats tooltip title', () => { + expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); + }); + + it('formats tooltip sha', () => { + expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9'); + }); + + it('formats tooltip commit url', () => { + expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); + }); }); }); - }); - describe('setSvg', () => { - const mockSvgName = 'mockSvgName'; + describe('setSvg', () => { + const mockSvgName = 'mockSvgName'; - beforeEach(done => { - timeSeriesChart.vm.setSvg(mockSvgName); - timeSeriesChart.vm.$nextTick(done); - }); + beforeEach(done => { + timeSeriesChart.vm.setSvg(mockSvgName); + timeSeriesChart.vm.$nextTick(done); + }); - it('gets svg path content', () => { - expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName); - }); + it('gets svg path content', () => { + expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName); + }); - it('sets svg path content', () => { - timeSeriesChart.vm.$nextTick(() => { - expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); + it('sets svg path content', () => { + timeSeriesChart.vm.$nextTick(() => { + expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); + }); }); - }); - it('contains an svg object within an array to properly render icon', () => { - timeSeriesChart.vm.$nextTick(() => { - expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ - { - handleIcon: `path://${mockSvgPathContent}`, - }, - ]); + it('contains an svg object within an array to properly render icon', () => { + timeSeriesChart.vm.$nextTick(() => { + expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ + { + handleIcon: `path://${mockSvgPathContent}`, + }, + ]); + }); }); }); - }); - describe('onResize', () => { - const mockWidth = 233; + describe('onResize', () => { + const mockWidth = 233; - beforeEach(() => { - jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ - width: mockWidth, - })); - timeSeriesChart.vm.onResize(); - }); + beforeEach(() => { + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ + width: mockWidth, + })); + timeSeriesChart.vm.onResize(); + }); - it('sets area chart width', () => { - expect(timeSeriesChart.vm.width).toBe(mockWidth); + it('sets area chart width', () => { + expect(timeSeriesChart.vm.width).toBe(mockWidth); + }); }); }); - }); - describe('computed', () => { - const getChartOptions = () => findChart().props('option'); + describe('computed', () => { + const getChartOptions = () => findChart().props('option'); - describe('chartData', () => { - let chartData; - const seriesData = () => chartData[0]; + describe('chartData', () => { + let chartData; + const seriesData = () => chartData[0]; - beforeEach(() => { - ({ chartData } = timeSeriesChart.vm); - }); + beforeEach(() => { + ({ chartData } = timeSeriesChart.vm); + }); - it('utilizes all data points', () => { - const { values } = mockGraphData.metrics[0].result[0]; + it('utilizes all data points', () => { + const { values } = mockGraphData.metrics[0].result[0]; - expect(chartData.length).toBe(1); - expect(seriesData().data.length).toBe(values.length); - }); + expect(chartData.length).toBe(1); + expect(seriesData().data.length).toBe(values.length); + }); - it('creates valid data', () => { - const { data } = seriesData(); + it('creates valid data', () => { + const { data } = seriesData(); - expect( - data.filter( - ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number', - ).length, - ).toBe(data.length); - }); + expect( + data.filter( + ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number', + ).length, + ).toBe(data.length); + }); - it('formats line width correctly', () => { - expect(chartData[0].lineStyle.width).toBe(2); - }); + it('formats line width correctly', () => { + expect(chartData[0].lineStyle.width).toBe(2); + }); - it('formats line color correctly', () => { - expect(chartData[0].lineStyle.color).toBe(chartColorValues[0]); + it('formats line color correctly', () => { + expect(chartData[0].lineStyle.color).toBe(chartColorValues[0]); + }); }); - }); - describe('chartOptions', () => { - describe('are extended by `option`', () => { - const mockSeriesName = 'Extra series 1'; - const mockOption = { - option1: 'option1', - option2: 'option2', - }; - - it('arbitrary options', () => { - timeSeriesChart.setProps({ - option: mockOption, - }); + describe('chartOptions', () => { + describe('are extended by `option`', () => { + const mockSeriesName = 'Extra series 1'; + const mockOption = { + option1: 'option1', + option2: 'option2', + }; - return timeSeriesChart.vm.$nextTick().then(() => { - expect(getChartOptions()).toEqual(expect.objectContaining(mockOption)); - }); - }); + it('arbitrary options', () => { + timeSeriesChart.setProps({ + option: mockOption, + }); - it('additional series', () => { - timeSeriesChart.setProps({ - option: { - series: [ - { - name: mockSeriesName, - }, - ], - }, + return timeSeriesChart.vm.$nextTick().then(() => { + expect(getChartOptions()).toEqual(expect.objectContaining(mockOption)); + }); }); - return timeSeriesChart.vm.$nextTick().then(() => { - const optionSeries = getChartOptions().series; + it('additional series', () => { + timeSeriesChart.setProps({ + option: { + series: [ + { + name: mockSeriesName, + }, + ], + }, + }); + + return timeSeriesChart.vm.$nextTick().then(() => { + const optionSeries = getChartOptions().series; - expect(optionSeries.length).toEqual(2); - expect(optionSeries[0].name).toEqual(mockSeriesName); + expect(optionSeries.length).toEqual(2); + expect(optionSeries[0].name).toEqual(mockSeriesName); + }); }); - }); - it('additional y axis data', () => { - const mockCustomYAxisOption = { - name: 'Custom y axis label', - axisLabel: { - formatter: jest.fn(), - }, - }; + it('additional y axis data', () => { + const mockCustomYAxisOption = { + name: 'Custom y axis label', + axisLabel: { + formatter: jest.fn(), + }, + }; - timeSeriesChart.setProps({ - option: { - yAxis: mockCustomYAxisOption, - }, + timeSeriesChart.setProps({ + option: { + yAxis: mockCustomYAxisOption, + }, + }); + + return timeSeriesChart.vm.$nextTick().then(() => { + const { yAxis } = getChartOptions(); + + expect(yAxis[0]).toMatchObject(mockCustomYAxisOption); + }); }); - return timeSeriesChart.vm.$nextTick().then(() => { - const { yAxis } = getChartOptions(); + it('additional x axis data', () => { + const mockCustomXAxisOption = { + name: 'Custom x axis label', + }; + + timeSeriesChart.setProps({ + option: { + xAxis: mockCustomXAxisOption, + }, + }); + + return timeSeriesChart.vm.$nextTick().then(() => { + const { xAxis } = getChartOptions(); - expect(yAxis[0]).toMatchObject(mockCustomYAxisOption); + expect(xAxis).toMatchObject(mockCustomXAxisOption); + }); }); }); - it('additional x axis data', () => { - const mockCustomXAxisOption = { - name: 'Custom x axis label', - }; + describe('yAxis formatter', () => { + let dataFormatter; + let deploymentFormatter; - timeSeriesChart.setProps({ - option: { - xAxis: mockCustomXAxisOption, - }, + beforeEach(() => { + dataFormatter = getChartOptions().yAxis[0].axisLabel.formatter; + deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter; }); - return timeSeriesChart.vm.$nextTick().then(() => { - const { xAxis } = getChartOptions(); + it('rounds to 3 decimal places', () => { + expect(dataFormatter(0.88888)).toBe('0.889'); + }); - expect(xAxis).toMatchObject(mockCustomXAxisOption); + it('deployment formatter is set as is required to display a tooltip', () => { + expect(deploymentFormatter).toEqual(expect.any(Function)); }); }); }); - describe('yAxis formatter', () => { - let dataFormatter; - let deploymentFormatter; + describe('deploymentSeries', () => { + it('utilizes deployment data', () => { + expect(timeSeriesChart.vm.deploymentSeries.yAxisIndex).toBe(1); // same as deployment y axis + expect(timeSeriesChart.vm.deploymentSeries.data).toEqual([ + ['2019-07-16T10:14:25.589Z', expect.any(Number)], + ['2019-07-16T11:14:25.589Z', expect.any(Number)], + ['2019-07-16T12:14:25.589Z', expect.any(Number)], + ]); - beforeEach(() => { - dataFormatter = getChartOptions().yAxis[0].axisLabel.formatter; - deploymentFormatter = getChartOptions().yAxis[1].axisLabel.formatter; + expect(timeSeriesChart.vm.deploymentSeries.symbolSize).toBe(14); }); + }); - it('rounds to 3 decimal places', () => { - expect(dataFormatter(0.88888)).toBe('0.889'); + describe('yAxisLabel', () => { + it('y axis is configured correctly', () => { + const { yAxis } = getChartOptions(); + + expect(yAxis).toHaveLength(2); + + const [dataAxis, deploymentAxis] = yAxis; + + expect(dataAxis.boundaryGap).toHaveLength(2); + expect(dataAxis.scale).toBe(true); + + expect(deploymentAxis.show).toBe(false); + expect(deploymentAxis.min).toEqual(expect.any(Number)); + expect(deploymentAxis.max).toEqual(expect.any(Number)); + expect(deploymentAxis.min).toBeLessThan(deploymentAxis.max); }); - it('deployment formatter is set as is required to display a tooltip', () => { - expect(deploymentFormatter).toEqual(expect.any(Function)); + it('constructs a label for the chart y-axis', () => { + const { yAxis } = getChartOptions(); + + expect(yAxis[0].name).toBe('Memory Used per Pod'); }); }); }); - describe('deploymentSeries', () => { - it('utilizes deployment data', () => { - expect(timeSeriesChart.vm.deploymentSeries.yAxisIndex).toBe(1); // same as deployment y axis - expect(timeSeriesChart.vm.deploymentSeries.data).toEqual([ - ['2019-07-16T10:14:25.589Z', expect.any(Number)], - ['2019-07-16T11:14:25.589Z', expect.any(Number)], - ['2019-07-16T12:14:25.589Z', expect.any(Number)], - ]); - - expect(timeSeriesChart.vm.deploymentSeries.symbolSize).toBe(14); - }); + afterEach(() => { + timeSeriesChart.destroy(); }); + }); - describe('yAxisLabel', () => { - it('y axis is configured correctly', () => { - const { yAxis } = getChartOptions(); + describe('wrapped components', () => { + const glChartComponents = [ + { + chartType: 'area-chart', + component: GlAreaChart, + }, + { + chartType: 'line-chart', + component: GlLineChart, + }, + ]; - expect(yAxis).toHaveLength(2); + glChartComponents.forEach(dynamicComponent => { + describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { + let timeSeriesAreaChart; + const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component); - const [dataAxis, deploymentAxis] = yAxis; + beforeEach(done => { + timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); + timeSeriesAreaChart.vm.$nextTick(done); + }); - expect(dataAxis.boundaryGap).toHaveLength(2); - expect(dataAxis.scale).toBe(true); + afterEach(() => { + timeSeriesAreaChart.destroy(); + }); - expect(deploymentAxis.show).toBe(false); - expect(deploymentAxis.min).toEqual(expect.any(Number)); - expect(deploymentAxis.max).toEqual(expect.any(Number)); - expect(deploymentAxis.min).toBeLessThan(deploymentAxis.max); - }); + it('is a Vue instance', () => { + expect(findChartComponent().exists()).toBe(true); + expect(findChartComponent().isVueInstance()).toBe(true); + }); - it('constructs a label for the chart y-axis', () => { - const { yAxis } = getChartOptions(); + it('receives data properties needed for proper chart render', () => { + const props = findChartComponent().props(); - expect(yAxis[0].name).toBe('Memory Used per Pod'); - }); - }); - }); + expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); + expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); + expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText); + expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds); + }); - afterEach(() => { - timeSeriesChart.destroy(); - }); - }); + it('recieves a tooltip title', done => { + const mockTitle = 'mockTitle'; + timeSeriesAreaChart.vm.tooltip.title = mockTitle; - describe('wrapped components', () => { - const glChartComponents = [ - { - chartType: 'area-chart', - component: GlAreaChart, - }, - { - chartType: 'line-chart', - component: GlLineChart, - }, - ]; + timeSeriesAreaChart.vm.$nextTick(() => { + expect( + shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle), + ).toBe(true); + done(); + }); + }); - glChartComponents.forEach(dynamicComponent => { - describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { - let timeSeriesAreaChart; - const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component); + describe('when tooltip is showing deployment data', () => { + const mockSha = 'mockSha'; + const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`; - beforeEach(done => { - timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); - timeSeriesAreaChart.vm.$nextTick(done); - }); + beforeEach(done => { + timeSeriesAreaChart.vm.tooltip.isDeployment = true; + timeSeriesAreaChart.vm.$nextTick(done); + }); - afterEach(() => { - timeSeriesAreaChart.destroy(); - }); + it('uses deployment title', () => { + expect( + shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', 'Deployed'), + ).toBe(true); + }); - it('is a Vue instance', () => { - expect(findChartComponent().exists()).toBe(true); - expect(findChartComponent().isVueInstance()).toBe(true); - }); + it('renders clickable commit sha in tooltip content', done => { + timeSeriesAreaChart.vm.tooltip.sha = mockSha; + timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl; - it('receives data properties needed for proper chart render', () => { - const props = findChartComponent().props(); + timeSeriesAreaChart.vm.$nextTick(() => { + const commitLink = timeSeriesAreaChart.find(GlLink); - expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); - expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); - expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText); - expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds); + expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); + expect(commitLink.attributes('href')).toEqual(commitUrl); + done(); + }); + }); + }); }); + }); + }); + }); - it('recieves a tooltip title', done => { - const mockTitle = 'mockTitle'; - timeSeriesAreaChart.vm.tooltip.title = mockTitle; + describe('with multiple time series', () => { + const mockedResultMultipleSeries = []; + const [, , panelData] = metricsDashboardPayload.panel_groups[1].panels; - timeSeriesAreaChart.vm.$nextTick(() => { - expect( - shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', mockTitle), - ).toBe(true); - done(); - }); - }); + for (let i = 0; i < panelData.metrics.length; i += 1) { + mockedResultMultipleSeries.push(cloneDeep(mockedQueryResultPayload)); + mockedResultMultipleSeries[ + i + ].metricId = `${panelData.metrics[i].metric_id}_${panelData.metrics[i].id}`; + } - describe('when tooltip is showing deployment data', () => { - const mockSha = 'mockSha'; - const commitUrl = `${mockProjectDir}/-/commit/${mockSha}`; + beforeEach(() => { + setTestTimeout(1000); - beforeEach(done => { - timeSeriesAreaChart.vm.tooltip.isDeployment = true; - timeSeriesAreaChart.vm.$nextTick(done); - }); + store = createStore(); - it('uses deployment title', () => { - expect( - shallowWrapperContainsSlotText(findChartComponent(), 'tooltipTitle', 'Deployed'), - ).toBe(true); - }); + store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + metricsDashboardPayload, + ); - it('renders clickable commit sha in tooltip content', done => { - timeSeriesAreaChart.vm.tooltip.sha = mockSha; - timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl; + store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); - timeSeriesAreaChart.vm.$nextTick(() => { - const commitLink = timeSeriesAreaChart.find(GlLink); + // Mock data contains the metric_id for a multiple time series panel + for (let i = 0; i < panelData.metrics.length; i += 1) { + store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedResultMultipleSeries[i], + ); + } - expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); - expect(commitLink.attributes('href')).toEqual(commitUrl); - done(); - }); - }); + // Pick the second panel group and the second panel in it + [, , mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; + }); + + describe('General functions', () => { + let timeSeriesChart; + + beforeEach(done => { + timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); + timeSeriesChart.vm.$nextTick(done); + }); + + describe('computed', () => { + let chartData; + + beforeEach(() => { + ({ chartData } = timeSeriesChart.vm); + }); + + it('should contain different colors for each time series', () => { + expect(chartData[0].lineStyle.color).toBe('#1f78d1'); + expect(chartData[1].lineStyle.color).toBe('#1aaa55'); + expect(chartData[2].lineStyle.color).toBe('#fc9403'); + expect(chartData[3].lineStyle.color).toBe('#6d49cb'); + expect(chartData[4].lineStyle.color).toBe('#1f78d1'); }); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 15c82242262..fcf70a1af63 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -22,7 +22,7 @@ import { } from '../mock_data'; const localVue = createLocalVue(); -const expectedPanelCount = 2; +const expectedPanelCount = 3; describe('Dashboard', () => { let store; diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index 4d83933f2b8..bad3962dd8f 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -513,6 +513,48 @@ export const metricsDashboardPayload = { }, ], }, + { + title: 'memories', + type: 'area-chart', + y_label: 'memories', + metrics: [ + { + id: 'metric_of_ages_1000', + label: 'memory_1000', + unit: 'count', + prometheus_endpoint_path: '/root', + metric_id: 20, + }, + { + id: 'metric_of_ages_1001', + label: 'memory_1000', + unit: 'count', + prometheus_endpoint_path: '/root', + metric_id: 21, + }, + { + id: 'metric_of_ages_1002', + label: 'memory_1000', + unit: 'count', + prometheus_endpoint_path: '/root', + metric_id: 22, + }, + { + id: 'metric_of_ages_1003', + label: 'memory_1000', + unit: 'count', + prometheus_endpoint_path: '/root', + metric_id: 23, + }, + { + id: 'metric_of_ages_1004', + label: 'memory_1004', + unit: 'count', + prometheus_endpoint_path: '/root', + metric_id: 24, + }, + ], + }, ], }, ], diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js index d9aebafb9ec..3fb7b84fae5 100644 --- a/spec/frontend/monitoring/store/mutations_spec.js +++ b/spec/frontend/monitoring/store/mutations_spec.js @@ -50,9 +50,10 @@ describe('Monitoring mutations', () => { expect(groups[0].panels).toHaveLength(1); expect(groups[0].panels[0].metrics).toHaveLength(1); - expect(groups[1].panels).toHaveLength(2); + expect(groups[1].panels).toHaveLength(3); expect(groups[1].panels[0].metrics).toHaveLength(1); expect(groups[1].panels[1].metrics).toHaveLength(1); + expect(groups[1].panels[2].metrics).toHaveLength(5); }); it('assigns metrics a metric id', () => { mutations[types.RECEIVE_METRICS_DATA_SUCCESS](stateCopy, payload); diff --git a/spec/frontend/releases/components/app_edit_spec.js b/spec/frontend/releases/components/app_edit_spec.js index cb940facbd6..b2dbb8cc435 100644 --- a/spec/frontend/releases/components/app_edit_spec.js +++ b/spec/frontend/releases/components/app_edit_spec.js @@ -13,7 +13,7 @@ describe('Release edit component', () => { beforeEach(() => { gon.api_version = 'v4'; - releaseClone = JSON.parse(JSON.stringify(convertObjectPropsToCamelCase(release))); + releaseClone = convertObjectPropsToCamelCase(release, { deep: true }); state = { release: releaseClone, diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js index 7b896575965..fb62f4a3bfe 100644 --- a/spec/frontend/releases/components/evidence_block_spec.js +++ b/spec/frontend/releases/components/evidence_block_spec.js @@ -2,12 +2,14 @@ import { mount } from '@vue/test-utils'; import { GlLink } from '@gitlab/ui'; import { truncateSha } from '~/lib/utils/text_utility'; import Icon from '~/vue_shared/components/icon.vue'; -import { release } from '../mock_data'; +import { release as originalRelease } from '../mock_data'; import EvidenceBlock from '~/releases/components/evidence_block.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; describe('Evidence Block', () => { let wrapper; + let release; const factory = (options = {}) => { wrapper = mount(EvidenceBlock, { @@ -16,6 +18,8 @@ describe('Evidence Block', () => { }; beforeEach(() => { + release = convertObjectPropsToCamelCase(originalRelease, { deep: true }); + factory({ propsData: { release, @@ -32,7 +36,7 @@ describe('Evidence Block', () => { }); it('renders the title for the dowload link', () => { - expect(wrapper.find(GlLink).text()).toBe(`${release.tag_name}-evidence.json`); + expect(wrapper.find(GlLink).text()).toBe(`${release.tagName}-evidence.json`); }); it('renders the correct hover text for the download', () => { @@ -40,19 +44,19 @@ describe('Evidence Block', () => { }); it('renders the correct file link for download', () => { - expect(wrapper.find(GlLink).attributes().download).toBe(`${release.tag_name}-evidence.json`); + expect(wrapper.find(GlLink).attributes().download).toBe(`${release.tagName}-evidence.json`); }); describe('sha text', () => { it('renders the short sha initially', () => { - expect(wrapper.find('.js-short').text()).toBe(truncateSha(release.evidence_sha)); + expect(wrapper.find('.js-short').text()).toBe(truncateSha(release.evidenceSha)); }); it('renders the long sha after expansion', () => { wrapper.find('.js-text-expander-prepend').trigger('click'); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.find('.js-expanded').text()).toBe(release.evidence_sha); + expect(wrapper.find('.js-expanded').text()).toBe(release.evidenceSha); }); }); }); @@ -68,7 +72,7 @@ describe('Evidence Block', () => { it('copies the sha', () => { expect(wrapper.find(ClipboardButton).attributes('data-clipboard-text')).toBe( - release.evidence_sha, + release.evidenceSha, ); }); }); diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js index 4125d5c7e74..c63637c4cae 100644 --- a/spec/frontend/releases/components/release_block_footer_spec.js +++ b/spec/frontend/releases/components/release_block_footer_spec.js @@ -24,7 +24,7 @@ describe('Release block footer', () => { const factory = (props = {}) => { wrapper = mount(ReleaseBlockFooter, { propsData: { - ...convertObjectPropsToCamelCase(releaseClone), + ...convertObjectPropsToCamelCase(releaseClone, { deep: true }), ...props, }, }); diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js index 157df15ff3c..78adad13f69 100644 --- a/spec/frontend/releases/components/release_block_header_spec.js +++ b/spec/frontend/releases/components/release_block_header_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { cloneDeep, merge } from 'lodash'; +import { merge } from 'lodash'; import { GlLink } from '@gitlab/ui'; import ReleaseBlockHeader from '~/releases/components/release_block_header.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -18,9 +18,7 @@ describe('Release block header', () => { }; beforeEach(() => { - release = convertObjectPropsToCamelCase(cloneDeep(originalRelease), { - ignoreKeyNames: ['_links'], - }); + release = convertObjectPropsToCamelCase(originalRelease, { deep: true }); }); afterEach(() => { @@ -39,13 +37,13 @@ describe('Release block header', () => { const link = findHeaderLink(); expect(link.text()).toBe(release.name); - expect(link.attributes('href')).toBe(release._links.self); + expect(link.attributes('href')).toBe(release.Links.self); }); }); describe('when _links.self is missing', () => { beforeEach(() => { - factory({ _links: { self: null } }); + factory({ Links: { self: null } }); }); it('renders the title as text', () => { diff --git a/spec/frontend/releases/components/release_block_milestone_info_spec.js b/spec/frontend/releases/components/release_block_milestone_info_spec.js index 5a3204a4ce2..10f5db96b31 100644 --- a/spec/frontend/releases/components/release_block_milestone_info_spec.js +++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js @@ -2,12 +2,13 @@ import { mount } from '@vue/test-utils'; import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui'; import { trimText } from 'helpers/text_helper'; import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue'; -import { milestones } from '../mock_data'; +import { milestones as originalMilestones } from '../mock_data'; import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/constants'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; describe('Release block milestone info', () => { let wrapper; - let milestonesClone; + let milestones; const factory = milestonesProp => { wrapper = mount(ReleaseBlockMilestoneInfo, { @@ -20,7 +21,7 @@ describe('Release block milestone info', () => { }; beforeEach(() => { - milestonesClone = JSON.parse(JSON.stringify(milestones)); + milestones = convertObjectPropsToCamelCase(originalMilestones, { deep: true }); }); afterEach(() => { @@ -32,7 +33,7 @@ describe('Release block milestone info', () => { const issuesContainer = () => wrapper.find('.js-issues-container'); describe('with default props', () => { - beforeEach(() => factory(milestonesClone)); + beforeEach(() => factory(milestones)); it('renders the correct percentage', () => { expect(milestoneProgressBarContainer().text()).toContain('41% complete'); @@ -53,13 +54,13 @@ describe('Release block milestone info', () => { it('renders a list of links to all associated milestones', () => { expect(trimText(milestoneListContainer().text())).toContain('Milestones 13.6 • 13.5'); - milestonesClone.forEach((m, i) => { + milestones.forEach((m, i) => { const milestoneLink = milestoneListContainer() .findAll(GlLink) .at(i); expect(milestoneLink.text()).toBe(m.title); - expect(milestoneLink.attributes('href')).toBe(m.web_url); + expect(milestoneLink.attributes('href')).toBe(m.webUrl); expect(milestoneLink.attributes('title')).toBe(m.description); }); }); @@ -84,7 +85,7 @@ describe('Release block milestone info', () => { beforeEach(() => { lotsOfMilestones = []; - const template = milestonesClone[0]; + const template = milestones[0]; for (let i = 0; i < MAX_MILESTONES_TO_DISPLAY + 10; i += 1) { lotsOfMilestones.push({ @@ -148,16 +149,16 @@ describe('Release block milestone info', () => { /** Ensures we don't have any issues with dividing by zero when computing percentages */ describe('when all issue counts are zero', () => { beforeEach(() => { - milestonesClone = milestonesClone.map(m => ({ + milestones = milestones.map(m => ({ ...m, - issue_stats: { - ...m.issue_stats, + issueStats: { + ...m.issueStats, opened: 0, closed: 0, }, })); - return factory(milestonesClone); + return factory(milestones); }); expectAllZeros(); @@ -165,12 +166,12 @@ describe('Release block milestone info', () => { describe('if the API response is missing the "issue_stats" property', () => { beforeEach(() => { - milestonesClone = milestonesClone.map(m => ({ + milestones = milestones.map(m => ({ ...m, - issue_stats: undefined, + issueStats: undefined, })); - return factory(milestonesClone); + return factory(milestones); }); expectAllZeros(); diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js index aba1b8aff41..5d365b77560 100644 --- a/spec/frontend/releases/components/release_block_spec.js +++ b/spec/frontend/releases/components/release_block_spec.js @@ -5,10 +5,12 @@ import EvidenceBlock from '~/releases/components/evidence_block.vue'; import ReleaseBlock from '~/releases/components/release_block.vue'; import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; -import { release } from '../mock_data'; +import { release as originalRelease } from '../mock_data'; import Icon from '~/vue_shared/components/icon.vue'; import { scrollToElement } from '~/lib/utils/common_utils'; +const { convertObjectPropsToCamelCase } = jest.requireActual('~/lib/utils/common_utils'); + let mockLocationHash; jest.mock('~/lib/utils/url_utility', () => ({ __esModule: true, @@ -22,7 +24,7 @@ jest.mock('~/lib/utils/common_utils', () => ({ describe('Release block', () => { let wrapper; - let releaseClone; + let release; const factory = (releaseProp, featureFlags = {}) => { wrapper = mount(ReleaseBlock, { @@ -45,7 +47,7 @@ describe('Release block', () => { beforeEach(() => { jest.spyOn($.fn, 'renderGFM'); - releaseClone = JSON.parse(JSON.stringify(release)); + release = convertObjectPropsToCamelCase(originalRelease, { deep: true }); }); afterEach(() => { @@ -61,7 +63,7 @@ describe('Release block', () => { it('renders an edit button that links to the "Edit release" page', () => { expect(editButton().exists()).toBe(true); - expect(editButton().attributes('href')).toBe(release._links.edit_url); + expect(editButton().attributes('href')).toBe(release.Links.editUrl); }); it('renders release name', () => { @@ -74,7 +76,7 @@ describe('Release block', () => { }); it('renders release date', () => { - expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.released_at)); + expect(wrapper.text()).toContain(timeagoMixin.methods.timeFormatted(release.releasedAt)); }); it('renders number of assets provided', () => { @@ -129,72 +131,72 @@ describe('Release block', () => { }); it('renders commit sha', () => { - releaseClone.commit_path = '/commit/example'; + release.commitPath = '/commit/example'; - return factory(releaseClone).then(() => { - expect(wrapper.text()).toContain(release.commit.short_id); + return factory(release).then(() => { + expect(wrapper.text()).toContain(release.commit.shortId); expect(wrapper.find('a[href="/commit/example"]').exists()).toBe(true); }); }); it('renders tag name', () => { - releaseClone.tag_path = '/tag/example'; + release.tagPath = '/tag/example'; - return factory(releaseClone).then(() => { - expect(wrapper.text()).toContain(release.tag_name); + return factory(release).then(() => { + expect(wrapper.text()).toContain(release.tagName); expect(wrapper.find('a[href="/tag/example"]').exists()).toBe(true); }); }); - it("does not render an edit button if release._links.edit_url isn't a string", () => { - delete releaseClone._links; + it("does not render an edit button if release.Links.editUrl isn't a string", () => { + delete release.Links; - return factory(releaseClone).then(() => { + return factory(release).then(() => { expect(editButton().exists()).toBe(false); }); }); it('does not render the milestone list if no milestones are associated to the release', () => { - delete releaseClone.milestones; + delete release.milestones; - return factory(releaseClone).then(() => { + return factory(release).then(() => { expect(milestoneListLabel().exists()).toBe(false); }); }); it('renders upcoming release badge', () => { - releaseClone.upcoming_release = true; + release.upcomingRelease = true; - return factory(releaseClone).then(() => { + return factory(release).then(() => { expect(wrapper.text()).toContain('Upcoming Release'); }); }); - it('slugifies the tag_name before setting it as the elements ID', () => { - releaseClone.tag_name = 'a dangerous tag name <script>alert("hello")</script>'; + it('slugifies the tagName before setting it as the elements ID', () => { + release.tagName = 'a dangerous tag name <script>alert("hello")</script>'; - return factory(releaseClone).then(() => { + return factory(release).then(() => { expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script'); }); }); describe('evidence block', () => { it('renders the evidence block when the evidence is available and the feature flag is true', () => - factory(releaseClone, { releaseEvidenceCollection: true }).then(() => + factory(release, { releaseEvidenceCollection: true }).then(() => expect(wrapper.find(EvidenceBlock).exists()).toBe(true), )); it('does not render the evidence block when the evidence is available but the feature flag is false', () => - factory(releaseClone, { releaseEvidenceCollection: true }).then(() => + factory(release, { releaseEvidenceCollection: true }).then(() => expect(wrapper.find(EvidenceBlock).exists()).toBe(true), )); it('does not render the evidence block when there is no evidence', () => { - releaseClone.evidence_sha = null; + release.evidenceSha = null; - return factory(releaseClone).then(() => { + return factory(release).then(() => { expect(wrapper.find(EvidenceBlock).exists()).toBe(false); }); }); @@ -222,7 +224,7 @@ describe('Release block', () => { }); it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => { - mockLocationHash = release.tag_name; + mockLocationHash = release.tagName; return factory(release).then(() => { expect(scrollToElement).toHaveBeenCalledTimes(1); @@ -231,7 +233,7 @@ describe('Release block', () => { }); it('renders with a light blue background if it is the target of the anchor', () => { - mockLocationHash = release.tag_name; + mockLocationHash = release.tagName; return factory(release).then(() => { expect(hasTargetBlueBackground()).toBe(true); @@ -275,16 +277,16 @@ describe('Release block', () => { expect(milestoneLink.text()).toBe(milestone.title); - expect(milestoneLink.attributes('href')).toBe(milestone.web_url); + expect(milestoneLink.attributes('href')).toBe(milestone.webUrl); expect(milestoneLink.attributes('title')).toBe(milestone.description); }); }); it('renders the label as "Milestone" if only a single milestone is passed in', () => { - releaseClone.milestones = releaseClone.milestones.slice(0, 1); + release.milestones = release.milestones.slice(0, 1); - return factory(releaseClone, { releaseIssueSummary: false }).then(() => { + return factory(release, { releaseIssueSummary: false }).then(() => { expect( milestoneListLabel() .find('.js-label-text') |