From 48dddcd99b16928f0446a2866696f76f3735c5e6 Mon Sep 17 00:00:00 2001 From: Miguel Rincon Date: Sun, 23 Mar 2025 23:09:36 +0100 Subject: [PATCH 1/3] chore(GlChart): Refactor chart specs and add story --- src/components/charts/chart/chart.spec.js | 186 ++++++++++++------- src/components/charts/chart/chart.stories.js | 82 ++++---- 2 files changed, 160 insertions(+), 108 deletions(-) diff --git a/src/components/charts/chart/chart.spec.js b/src/components/charts/chart/chart.spec.js index 0bb0435f17..1f5359e9bf 100644 --- a/src/components/charts/chart/chart.spec.js +++ b/src/components/charts/chart/chart.spec.js @@ -1,3 +1,4 @@ +import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; import * as echarts from 'echarts'; import { toolboxHeight } from '~/utils/charts/config'; @@ -8,31 +9,42 @@ import Chart from './chart.vue'; const defaultHeight = 400; -jest.mock('echarts', () => ({ - init: jest.fn(() => ({ - setOption: jest.fn(), - resize: jest.fn(), - on: jest.fn(), - off: jest.fn(), - })), - registerTheme: jest.fn(), -})); +jest.mock('echarts', () => { + return { + init: jest.fn(() => { + let opts = null; + return { + setOption: jest.fn((o) => { + opts = o; + }), + getOption: jest.fn(() => opts), + resize: jest.fn(), + on: jest.fn(), + off: jest.fn(), + }; + }), + registerTheme: jest.fn(), + }; +}); describe('chart component', () => { - const options = { - aria: { - enabled: true, - }, - }; - const mountArgs = [Chart, { propsData: { options: {} } }]; const themeName = 'gitlab'; let wrapper; + const createWrapper = (props) => { + wrapper = shallowMount(Chart, { + propsData: { + options: {}, + ...props, + }, + }); + }; + const { trigger: triggerResize } = useMockResizeObserver(); it('initializes chart using $refs.chart', async () => { - wrapper = shallowMount(...mountArgs); - await wrapper.vm.$nextTick(); + createWrapper(); + await nextTick(); expect(echarts.init).toHaveBeenCalledWith( wrapper.findComponent({ ref: 'chart' }).element, @@ -46,8 +58,10 @@ describe('chart component', () => { }); it('does not resize the chart when responsive = false', async () => { - wrapper = shallowMount(Chart, { propsData: { options: {}, responsive: false } }); - await wrapper.vm.$nextTick(); + createWrapper({ + responsive: false, + }); + await nextTick(); // initial call when chart gets created expect(wrapper.vm.chart.resize).toHaveBeenCalledTimes(1); @@ -58,8 +72,9 @@ describe('chart component', () => { }); it('resizes the chart only once per animation frame when responsive = true', async () => { - wrapper = shallowMount(...mountArgs); - await wrapper.vm.$nextTick(); + createWrapper(); + await nextTick(); + expect(wrapper.vm.chart.resize).toHaveBeenCalledTimes(1); triggerResize(wrapper.element); @@ -70,87 +85,128 @@ describe('chart component', () => { }); it('emits "created" after initializing chart', async () => { - wrapper = shallowMount(...mountArgs); - await wrapper.vm.$nextTick(); + createWrapper(); + await nextTick(); + expect(wrapper.emitted('created')).toEqual([[wrapper.vm.chart]]); }); it('uses GitLab theme', () => { - wrapper = shallowMount(...mountArgs); + createWrapper(); + const [firstRegisterThemeCall] = echarts.registerTheme.mock.calls; expect(firstRegisterThemeCall[0]).toBe(themeName); expect(JSON.stringify(firstRegisterThemeCall[1])).toEqual(JSON.stringify(createTheme())); }); it('waits a tick before creating the chart', async () => { - wrapper = shallowMount(...mountArgs); + createWrapper(); expect(wrapper.vm.chart).toBe(null); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.chart).toBeDefined(); }); - describe('custom sizing', () => { - const width = 1234; - const height = 123; - - it('sets chart to custom width and height if specified', async () => { - wrapper = shallowMount(Chart, { propsData: { options: {}, width, height } }); - - await wrapper.vm.$nextTick(); - - expect(wrapper.vm.chart.resize).toHaveBeenCalledWith({ width, height }); + describe('sets chart options', () => { + beforeEach(async () => { + createWrapper({ + options: { + title: { text: 'My chart title' }, + }, + }); + await nextTick(); }); - it('sets chart to custom width if specified', async () => { - wrapper = shallowMount(Chart, { propsData: { options: {}, width } }); - - await wrapper.vm.$nextTick(); + it('inits chart options, adding aria.enabled', async () => { + expect(wrapper.vm.chart.setOption).toHaveBeenCalledTimes(1); + expect(wrapper.vm.chart.setOption).toHaveBeenCalledWith({ + title: { text: 'My chart title' }, + aria: { enabled: true }, + }); - expect(wrapper.vm.chart.resize).toHaveBeenCalledWith({ width, height: defaultHeight }); + expect(wrapper.emitted('updated').length).toEqual(1); }); - it('sets chart to custom height if specified', async () => { - wrapper = shallowMount(Chart, { propsData: { options: {}, height } }); - - await wrapper.vm.$nextTick(); + it('reacts to options changes by merging options', async () => { + wrapper.setProps({ + options: { + title: { text: 'My new chart title' }, + xAxis: { show: true }, + }, + }); + await nextTick(); - expect(wrapper.vm.chart.resize).toHaveBeenCalledWith({ width: 'auto', height }); + expect(wrapper.vm.chart.setOption).toHaveBeenCalledTimes(2); + expect(wrapper.vm.chart.setOption).toHaveBeenLastCalledWith({ + title: { text: 'My new chart title' }, + xAxis: { show: true }, + aria: { enabled: true }, + }); + expect(wrapper.emitted('updated').length).toEqual(2); }); }); - describe('methods', () => { - beforeEach(() => { - wrapper = shallowMount(...mountArgs); - }); + describe('sets chart dimensions', () => { + it('sets dimensions', async () => { + createWrapper(); + await nextTick(); - describe('draw method', () => { - it('sets chart options', () => { - expect(wrapper.vm.chart.setOption).toHaveBeenCalledTimes(1); + expect(wrapper.vm.chart.resize).toHaveBeenCalledTimes(1); + expect(wrapper.vm.chart.resize).toHaveBeenLastCalledWith({ + height: 400, + width: 'auto', + }); + }); - wrapper.vm.draw(); + it('sets custom dimensions', async () => { + const width = 1234; + const height = 123; - expect(wrapper.vm.chart.setOption).toHaveBeenCalledTimes(2); - expect(wrapper.vm.chart.setOption).toHaveBeenCalledWith(options); + createWrapper({ + width, + height, }); - it('emits chart updated after drawing', () => { - wrapper.vm.draw(); - const { chart } = wrapper.vm; + await nextTick(); - expect(wrapper.emitted('updated')).toEqual([[chart], [chart]]); + expect(wrapper.vm.chart.resize).toHaveBeenCalledTimes(1); + expect(wrapper.vm.chart.resize).toHaveBeenLastCalledWith({ + width, + height, }); }); - describe('setChartSize method', () => { - it('sets chart size', () => { - wrapper.vm.setChartSize(); + describe('reacts to dimension changes', () => { + beforeEach(async () => { + createWrapper(); + await nextTick(); + }); + + it('reacts to width changes', async () => { + wrapper.setProps({ + width: 301, + }); + await nextTick(); - expect(wrapper.vm.chart.resize).toHaveBeenCalledWith({ + expect(wrapper.vm.chart.resize).toHaveBeenCalledTimes(2); + expect(wrapper.vm.chart.resize).toHaveBeenLastCalledWith({ + width: 301, height: defaultHeight, + }); + }); + + it('reacts to height changes', async () => { + wrapper.setProps({ + height: 401, + }); + await nextTick(); + + expect(wrapper.vm.chart.resize).toHaveBeenCalledTimes(2); + expect(wrapper.vm.chart.resize).toHaveBeenCalledWith({ width: 'auto', + height: 401, }); }); }); @@ -160,7 +216,7 @@ describe('chart component', () => { it('increases grid top by `toolboxHeight`', async () => { const optionsWithToolbox = { toolbox: { show: true } }; wrapper = shallowMount(Chart, { propsData: { options: optionsWithToolbox } }); - await wrapper.vm.$nextTick(); + await nextTick(); expect(wrapper.vm.chart.setOption).toHaveBeenCalledWith({ ...optionsWithToolbox, diff --git a/src/components/charts/chart/chart.stories.js b/src/components/charts/chart/chart.stories.js index 492c702a1c..ef998678c7 100644 --- a/src/components/charts/chart/chart.stories.js +++ b/src/components/charts/chart/chart.stories.js @@ -3,58 +3,54 @@ import GlTab from '../../base/tabs/tab/tab.vue'; import GlChart from './chart.vue'; import readme from './chart.md'; +const generateProps = ({ options } = {}) => ({ + options: { + xAxis: { + type: 'category', + data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + }, + yAxis: { + type: 'value', + }, + series: [ + { + data: [820, 932, 901, 934, 1290, 1330, 1320], + type: 'bar', + }, + ], + ...options, + }, +}); + const Template = (args, { argTypes = {} }) => ({ + components: { GlChart }, + props: Object.keys(argTypes), + template: '', + ...args, +}); + +export const Default = Template.bind({}); +Default.args = generateProps(); + +export const Tab = (args, { argTypes }) => ({ components: { GlChart, GlTabs, GlTab, }, props: Object.keys(argTypes), - data() { - return { - options: { - xAxis: { - type: 'category', - data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], - }, - yAxis: { - type: 'value', - }, - series: [ - { - data: [820, 932, 901, 934, 1290, 1330, 1320], - type: 'bar', - }, - ], - }, - }; - }, - ...args, + template: ` + + + + + + `, }); +Tab.args = generateProps(); -export const Default = Template.bind( - {}, - { - template: ` - - `, - } -); - -export const Tab = Template.bind( - {}, - { - template: ` - - - - - - `, - } -); +export const WithToolbox = Template.bind({}); +WithToolbox.args = generateProps({ options: { toolbox: { show: true } } }); export const Connected = Template.bind( {}, -- GitLab From af41d2924b526be9c4cb4e9fa309850807f42cf0 Mon Sep 17 00:00:00 2001 From: Miguel Rincon Date: Mon, 24 Mar 2025 12:20:15 +0100 Subject: [PATCH 2/3] chore(GlChart): Refactor mocking of chart instance This change helps consolidate the creation of mock chart instances in our test to avoid repeating mock code. --- src/components/charts/area/area.spec.js | 4 ++-- src/components/charts/bar/bar.spec.js | 4 ++-- src/components/charts/chart/chart.spec.js | 17 ++++++----------- .../charts/column/column_chart.spec.js | 12 ++++++------ .../discrete_scatter/discrete_scatter.spec.js | 4 ++-- src/components/charts/gauge/gauge.spec.js | 4 ++-- src/components/charts/heatmap/heatmap.spec.js | 4 ++-- src/components/charts/legend/legend.spec.js | 4 ++-- src/components/charts/line/line.spec.js | 4 ++-- .../charts/shared/tooltip/tooltip.spec.js | 4 ++-- .../charts/sparkline/sparkline.spec.js | 4 ++-- .../stacked_column/stacked_column.spec.js | 6 +++--- tests/__helpers__/chart_stubs.js | 16 +++++++++++----- 13 files changed, 44 insertions(+), 43 deletions(-) diff --git a/src/components/charts/area/area.spec.js b/src/components/charts/area/area.spec.js index e3477a1d53..3699f071a4 100644 --- a/src/components/charts/area/area.spec.js +++ b/src/components/charts/area/area.spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import { LEGEND_LAYOUT_INLINE, LEGEND_LAYOUT_TABLE } from '~/utils/charts/constants'; import { - createMockChartInstance, + mockCreateChartInstance, ChartTooltipStub, chartTooltipStubData, } from '~helpers/chart_stubs'; @@ -40,7 +40,7 @@ describe('area component', () => { }; beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); }); it('emits `created`, with the chart instance', async () => { diff --git a/src/components/charts/bar/bar.spec.js b/src/components/charts/bar/bar.spec.js index b4229ff274..0446b679c6 100644 --- a/src/components/charts/bar/bar.spec.js +++ b/src/components/charts/bar/bar.spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { createMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import { expectHeightAutoClasses } from '~helpers/chart_height'; import Chart from '../chart/chart.vue'; import ChartTooltip from '../shared/tooltip/tooltip.vue'; @@ -39,7 +39,7 @@ describe('Bar chart component', () => { }; beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); }); describe('when mounted', () => { diff --git a/src/components/charts/chart/chart.spec.js b/src/components/charts/chart/chart.spec.js index 1f5359e9bf..7b80b936a2 100644 --- a/src/components/charts/chart/chart.spec.js +++ b/src/components/charts/chart/chart.spec.js @@ -5,23 +5,16 @@ import { toolboxHeight } from '~/utils/charts/config'; import { createTheme } from '~/utils/charts/theme'; import { waitForAnimationFrame } from '~/utils/test_utils'; import { useMockResizeObserver } from '~helpers/mock_dom_observer'; +import { mockCreateChartInstance as createdMockChartInstance } from '~helpers/chart_stubs'; import Chart from './chart.vue'; -const defaultHeight = 400; +let mockChartInstance; jest.mock('echarts', () => { return { init: jest.fn(() => { - let opts = null; - return { - setOption: jest.fn((o) => { - opts = o; - }), - getOption: jest.fn(() => opts), - resize: jest.fn(), - on: jest.fn(), - off: jest.fn(), - }; + mockChartInstance = createdMockChartInstance(); + return mockChartInstance; }), registerTheme: jest.fn(), }; @@ -29,6 +22,8 @@ jest.mock('echarts', () => { describe('chart component', () => { const themeName = 'gitlab'; + const defaultHeight = 400; + let wrapper; const createWrapper = (props) => { diff --git a/src/components/charts/column/column_chart.spec.js b/src/components/charts/column/column_chart.spec.js index 37e731a4aa..54193a64dc 100644 --- a/src/components/charts/column/column_chart.spec.js +++ b/src/components/charts/column/column_chart.spec.js @@ -1,7 +1,7 @@ import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; import { - createMockChartInstance, + mockCreateChartInstance, ChartTooltipStub, chartTooltipStubData, } from '~helpers/chart_stubs'; @@ -50,7 +50,7 @@ describe('column chart component', () => { beforeEach(() => { factory(); - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); emitChartCreated(); }); @@ -132,7 +132,7 @@ describe('column chart component', () => { 'chart-tooltip': ChartTooltipStub, }, }); - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); emitChartCreated(); await nextTick(); @@ -157,7 +157,7 @@ describe('column chart component', () => { }, }); - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); emitChartCreated(); await nextTick(); @@ -177,7 +177,7 @@ describe('column chart component', () => { }, }); - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); emitChartCreated(); await nextTick(); @@ -200,7 +200,7 @@ describe('column chart component', () => { }, }); - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); emitChartCreated(); await nextTick(); diff --git a/src/components/charts/discrete_scatter/discrete_scatter.spec.js b/src/components/charts/discrete_scatter/discrete_scatter.spec.js index e5bd5cda1b..c2ade4ebff 100644 --- a/src/components/charts/discrete_scatter/discrete_scatter.spec.js +++ b/src/components/charts/discrete_scatter/discrete_scatter.spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import { createMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import { expectHeightAutoClasses } from '~helpers/chart_height'; import Chart from '../chart/chart.vue'; import ChartTooltip from '../shared/tooltip/tooltip.vue'; @@ -41,7 +41,7 @@ describe('column chart component', () => { }; beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); mockChartInstance.convertToPixel.mockReturnValue([mockDataPixel.x, mockDataPixel.y]); }); diff --git a/src/components/charts/gauge/gauge.spec.js b/src/components/charts/gauge/gauge.spec.js index 2dc778bec3..f43e00fcca 100644 --- a/src/components/charts/gauge/gauge.spec.js +++ b/src/components/charts/gauge/gauge.spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { createMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import { gaugeSafeHues, gaugeWarningHue } from '../../../utils/charts/theme'; import Chart from '../chart/chart.vue'; import GlGauge from './gauge.vue'; @@ -37,7 +37,7 @@ describe('gauge component', () => { }; beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); }); it('emits `created`, with the chart instance', () => { diff --git a/src/components/charts/heatmap/heatmap.spec.js b/src/components/charts/heatmap/heatmap.spec.js index fe4270bdf9..273b2e33b7 100644 --- a/src/components/charts/heatmap/heatmap.spec.js +++ b/src/components/charts/heatmap/heatmap.spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; -import { createMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import { expectHeightAutoClasses } from '~helpers/chart_height'; import Chart from '../chart/chart.vue'; import ChartTooltip from '../shared/tooltip/tooltip.vue'; @@ -35,7 +35,7 @@ describe('heatmap component', () => { }; beforeEach(async () => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); }); it('emits `created`, with the chart instance', () => { diff --git a/src/components/charts/legend/legend.spec.js b/src/components/charts/legend/legend.spec.js index aa8813acc3..f5f5918d4f 100644 --- a/src/components/charts/legend/legend.spec.js +++ b/src/components/charts/legend/legend.spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { createMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import Chart from '../chart/chart.vue'; import GlChartSeriesLabel from '../series_label/series_label.vue'; import Legend from './legend.vue'; @@ -71,7 +71,7 @@ describe('chart legend component', () => { }; beforeEach(async () => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); chartWrapper = shallowMount(...chartArgs); await chartWrapper.vm.$nextTick(); diff --git a/src/components/charts/line/line.spec.js b/src/components/charts/line/line.spec.js index 8e2ce32c5e..13b2f41094 100644 --- a/src/components/charts/line/line.spec.js +++ b/src/components/charts/line/line.spec.js @@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils'; import { LEGEND_LAYOUT_INLINE, LEGEND_LAYOUT_TABLE } from '~/utils/charts/constants'; import { - createMockChartInstance, + mockCreateChartInstance, ChartTooltipStub, chartTooltipStubData, } from '~helpers/chart_stubs'; @@ -43,7 +43,7 @@ describe('line component', () => { }; beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); }); it('emits `created`, with the chart instance', async () => { diff --git a/src/components/charts/shared/tooltip/tooltip.spec.js b/src/components/charts/shared/tooltip/tooltip.spec.js index 1f0a09cb62..fcf8b9ae6f 100644 --- a/src/components/charts/shared/tooltip/tooltip.spec.js +++ b/src/components/charts/shared/tooltip/tooltip.spec.js @@ -1,6 +1,6 @@ import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; -import { createMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import GlPopover from '../../../base/popover/popover.vue'; import { popoverPlacements } from '../../../../utils/constants'; import { waitForAnimationFrame } from '../../../../utils/test_utils'; @@ -31,7 +31,7 @@ describe('ChartTooltip', () => { const createWrapper = (props = {}, options) => { mockChartInstance = { - ...createMockChartInstance(), + ...mockCreateChartInstance(), containPixel: mockContainPixel, }; diff --git a/src/components/charts/sparkline/sparkline.spec.js b/src/components/charts/sparkline/sparkline.spec.js index 7cc096670f..d839e0379d 100644 --- a/src/components/charts/sparkline/sparkline.spec.js +++ b/src/components/charts/sparkline/sparkline.spec.js @@ -1,7 +1,7 @@ import { shallowMount } from '@vue/test-utils'; import { waitForAnimationFrame } from '~/utils/test_utils'; import { HEIGHT_AUTO_HORIZONTAL_LAYOUT_CLASSES } from '~/utils/charts/constants'; -import { createMockChartInstance, ChartTooltipStub } from '~helpers/chart_stubs'; +import { mockCreateChartInstance, ChartTooltipStub } from '~helpers/chart_stubs'; import { expectHeightAutoClasses } from '~helpers/chart_height'; import Chart from '../chart/chart.vue'; import SparklineChart from './sparkline.vue'; @@ -72,7 +72,7 @@ describe('sparkline chart component', () => { const emitChartCreated = () => getChart().vm.$emit('created', mockChartInstance); beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); factory(); // needs to run after every mount, or the chart-instance is `null` and `beforeDestroy` throws emitChartCreated(); diff --git a/src/components/charts/stacked_column/stacked_column.spec.js b/src/components/charts/stacked_column/stacked_column.spec.js index f4c11528de..562b7b796f 100644 --- a/src/components/charts/stacked_column/stacked_column.spec.js +++ b/src/components/charts/stacked_column/stacked_column.spec.js @@ -2,7 +2,7 @@ import { nextTick } from 'vue'; import { shallowMount } from '@vue/test-utils'; import { - createMockChartInstance, + mockCreateChartInstance, ChartTooltipStub, chartTooltipStubData, } from '~helpers/chart_stubs'; @@ -58,7 +58,7 @@ describe('stacked column chart component', () => { }; beforeEach(() => { - mockChartInstance = createMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); }); afterEach(() => { @@ -99,7 +99,7 @@ describe('stacked column chart component', () => { describe('legend', () => { beforeEach(() => { mockChartInstance = { - ...createMockChartInstance(), + ...mockCreateChartInstance(), getOption: jest.fn().mockReturnValueOnce({ series: [ { diff --git a/tests/__helpers__/chart_stubs.js b/tests/__helpers__/chart_stubs.js index b57962a641..5251e68637 100644 --- a/tests/__helpers__/chart_stubs.js +++ b/tests/__helpers__/chart_stubs.js @@ -1,6 +1,6 @@ import ChartTooltip from '~/components/charts/shared/tooltip/tooltip.vue'; -export const createMockChartInstance = () => { +export const mockCreateChartInstance = () => { const dom = { addEventListener: jest.fn(), removeEventListener: jest.fn(), @@ -10,20 +10,26 @@ export const createMockChartInstance = () => { on: jest.fn(), off: jest.fn(), }; + let opts = null; return { // temporary workaround to ensure compatibility with @vue/compat __v_isReadonly: true, + setOption: jest.fn((o) => { + opts = o; + }), + getOption: jest.fn(() => ({ + series: [], // mocks empty, but present data as if added by echarts + ...opts, + })), + dispatchAction: jest.fn(), - setOption: jest.fn(), on: jest.fn(), off: jest.fn(), resize: jest.fn(), convertToPixel: jest.fn().mockReturnValue([]), - getOption: jest.fn().mockReturnValue({ - series: [], - }), + getDom: () => { return dom; }, -- GitLab From 4804f8b28c899a4ef810362247fca790d3a73f03 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 24 Mar 2025 08:46:09 +0000 Subject: [PATCH 3/3] chore: update snapshots --- src/components/charts/chart/chart.spec.js | 4 ++-- ...ryshots-charts-chart-with-toolbox-1-snap.png | Bin 0 -> 24914 bytes 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 tests/__image_snapshots__/storyshots-spec-js-image-storyshots-charts-chart-with-toolbox-1-snap.png diff --git a/src/components/charts/chart/chart.spec.js b/src/components/charts/chart/chart.spec.js index 7b80b936a2..cf1744c8af 100644 --- a/src/components/charts/chart/chart.spec.js +++ b/src/components/charts/chart/chart.spec.js @@ -5,7 +5,7 @@ import { toolboxHeight } from '~/utils/charts/config'; import { createTheme } from '~/utils/charts/theme'; import { waitForAnimationFrame } from '~/utils/test_utils'; import { useMockResizeObserver } from '~helpers/mock_dom_observer'; -import { mockCreateChartInstance as createdMockChartInstance } from '~helpers/chart_stubs'; +import { mockCreateChartInstance } from '~helpers/chart_stubs'; import Chart from './chart.vue'; let mockChartInstance; @@ -13,7 +13,7 @@ let mockChartInstance; jest.mock('echarts', () => { return { init: jest.fn(() => { - mockChartInstance = createdMockChartInstance(); + mockChartInstance = mockCreateChartInstance(); return mockChartInstance; }), registerTheme: jest.fn(), diff --git a/tests/__image_snapshots__/storyshots-spec-js-image-storyshots-charts-chart-with-toolbox-1-snap.png b/tests/__image_snapshots__/storyshots-spec-js-image-storyshots-charts-chart-with-toolbox-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..1e4660ce842625ccba5c6147423daf052eb58a76 GIT binary patch literal 24914 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 M)78&qol`;+0AmqC=Kufz literal 0 HcmV?d00001 -- GitLab