diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-20 14:34:42 +0000 |
commit | 9f46488805e86b1bc341ea1620b866016c2ce5ed (patch) | |
tree | f9748c7e287041e37d6da49e0a29c9511dc34768 /spec/frontend/monitoring/components | |
parent | dfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff) | |
download | gitlab-ce-9f46488805e86b1bc341ea1620b866016c2ce5ed.tar.gz |
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'spec/frontend/monitoring/components')
14 files changed, 1303 insertions, 231 deletions
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap index 1906ad7c6ed..9be5fa72110 100644 --- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap @@ -16,7 +16,6 @@ exports[`Dashboard template matches the default snapshot 1`] = ` data-qa-selector="dashboards_filter_dropdown" defaultbranch="master" id="monitor-dashboards-dropdown" - selecteddashboard="[object Object]" toggle-class="dropdown-menu-toggle" /> </div> @@ -72,7 +71,7 @@ exports[`Dashboard template matches the default snapshot 1`] = ` <date-time-picker-stub class="flex-grow-1 show-last-dropdown" customenabled="true" - data-qa-selector="show_last_dropdown" + data-qa-selector="range_picker_dropdown" options="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]" value="[object Object]" /> @@ -101,6 +100,26 @@ exports[`Dashboard template matches the default snapshot 1`] = ` <div class="d-sm-flex" > + <div + class="mb-2 mr-2 d-flex" + > + <div + class="flex-grow-1" + title="Star dashboard" + > + <gl-deprecated-button-stub + class="w-100" + size="md" + variant="default" + > + <gl-icon-stub + name="star-o" + size="16" + /> + </gl-deprecated-button-stub> + </div> + </div> + <!----> <!----> @@ -111,6 +130,8 @@ exports[`Dashboard template matches the default snapshot 1`] = ` </div> </div> + <!----> + <empty-state-stub clusterspath="/path/to/clusters" documentationpath="/path/to/docs" diff --git a/spec/frontend/monitoring/components/alert_widget_form_spec.js b/spec/frontend/monitoring/components/alert_widget_form_spec.js new file mode 100644 index 00000000000..a8416216a94 --- /dev/null +++ b/spec/frontend/monitoring/components/alert_widget_form_spec.js @@ -0,0 +1,220 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlLink } from '@gitlab/ui'; +import AlertWidgetForm from '~/monitoring/components/alert_widget_form.vue'; +import ModalStub from '../stubs/modal_stub'; + +describe('AlertWidgetForm', () => { + let wrapper; + + const metricId = '8'; + const alertPath = 'alert'; + const relevantQueries = [{ metricId, alert_path: alertPath, label: 'alert-label' }]; + const dataTrackingOptions = { + create: { action: 'click_button', label: 'create_alert' }, + delete: { action: 'click_button', label: 'delete_alert' }, + update: { action: 'click_button', label: 'update_alert' }, + }; + + const defaultProps = { + disabled: false, + relevantQueries, + modalId: 'alert-modal-1', + }; + + const propsWithAlertData = { + ...defaultProps, + alertsToManage: { + alert: { alert_path: alertPath, operator: '<', threshold: 5, metricId }, + }, + configuredAlert: metricId, + }; + + function createComponent(props = {}) { + const propsData = { + ...defaultProps, + ...props, + }; + + wrapper = shallowMount(AlertWidgetForm, { + propsData, + stubs: { + GlModal: ModalStub, + }, + }); + } + + const modal = () => wrapper.find(ModalStub); + const modalTitle = () => modal().attributes('title'); + const submitButton = () => modal().find(GlLink); + const submitButtonTrackingOpts = () => + JSON.parse(submitButton().attributes('data-tracking-options')); + const e = { + preventDefault: jest.fn(), + }; + + beforeEach(() => { + e.preventDefault.mockReset(); + }); + + afterEach(() => { + if (wrapper) wrapper.destroy(); + }); + + it('disables the form when disabled prop is set', () => { + createComponent({ disabled: true }); + + expect(modal().attributes('ok-disabled')).toBe('true'); + }); + + it('disables the form if no query is selected', () => { + createComponent(); + + expect(modal().attributes('ok-disabled')).toBe('true'); + }); + + it('shows correct title and button text', () => { + expect(modalTitle()).toBe('Add alert'); + expect(submitButton().text()).toBe('Add'); + }); + + it('sets tracking options for create alert', () => { + expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.create); + }); + + it('emits a "create" event when form submitted without existing alert', () => { + createComponent(); + + wrapper.vm.selectQuery('9'); + wrapper.setData({ + threshold: 900, + }); + + wrapper.vm.handleSubmit(e); + + expect(wrapper.emitted().create[0]).toEqual([ + { + alert: undefined, + operator: '>', + threshold: 900, + prometheus_metric_id: '9', + }, + ]); + expect(e.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('resets form when modal is dismissed (hidden)', () => { + createComponent(); + + wrapper.vm.selectQuery('9'); + wrapper.vm.selectQuery('>'); + wrapper.setData({ + threshold: 800, + }); + + modal().vm.$emit('hidden'); + + expect(wrapper.vm.selectedAlert).toEqual({}); + expect(wrapper.vm.operator).toBe(null); + expect(wrapper.vm.threshold).toBe(null); + expect(wrapper.vm.prometheusMetricId).toBe(null); + }); + + it('sets selectedAlert to the provided configuredAlert on modal show', () => { + createComponent(propsWithAlertData); + + modal().vm.$emit('shown'); + + expect(wrapper.vm.selectedAlert).toEqual(propsWithAlertData.alertsToManage[alertPath]); + }); + + it('sets selectedAlert to the first relevantQueries if there is only one option on modal show', () => { + createComponent({ + ...propsWithAlertData, + configuredAlert: '', + }); + + modal().vm.$emit('shown'); + + expect(wrapper.vm.selectedAlert).toEqual(propsWithAlertData.alertsToManage[alertPath]); + }); + + it('does not set selectedAlert to the first relevantQueries if there is more than one option on modal show', () => { + createComponent({ + relevantQueries: [ + { + metricId: '8', + alertPath: 'alert', + label: 'alert-label', + }, + { + metricId: '9', + alertPath: 'alert', + label: 'alert-label', + }, + ], + }); + + modal().vm.$emit('shown'); + + expect(wrapper.vm.selectedAlert).toEqual({}); + }); + + describe('with existing alert', () => { + beforeEach(() => { + createComponent(propsWithAlertData); + + wrapper.vm.selectQuery(metricId); + }); + + it('sets tracking options for delete alert', () => { + expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.delete); + }); + + it('updates button text', () => { + expect(modalTitle()).toBe('Edit alert'); + expect(submitButton().text()).toBe('Delete'); + }); + + it('emits "delete" event when form values unchanged', () => { + wrapper.vm.handleSubmit(e); + + expect(wrapper.emitted().delete[0]).toEqual([ + { + alert: 'alert', + operator: '<', + threshold: 5, + prometheus_metric_id: '8', + }, + ]); + expect(e.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('emits "update" event when form changed', () => { + wrapper.setData({ + threshold: 11, + }); + + wrapper.vm.handleSubmit(e); + + expect(wrapper.emitted().update[0]).toEqual([ + { + alert: 'alert', + operator: '<', + threshold: 11, + prometheus_metric_id: '8', + }, + ]); + expect(e.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('sets tracking options for update alert', () => { + wrapper.setData({ + threshold: 11, + }); + + return wrapper.vm.$nextTick(() => { + expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.update); + }); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js index fb0682d0338..9cc5970da82 100644 --- a/spec/frontend/monitoring/components/charts/single_stat_spec.js +++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import SingleStatChart from '~/monitoring/components/charts/single_stat.vue'; -import { graphDataPrometheusQuery } from '../../mock_data'; +import { singleStatMetricsResult } from '../../mock_data'; describe('Single Stat Chart component', () => { let singleStatChart; @@ -8,7 +8,7 @@ describe('Single Stat Chart component', () => { beforeEach(() => { singleStatChart = shallowMount(SingleStatChart, { propsData: { - graphData: graphDataPrometheusQuery, + graphData: singleStatMetricsResult, }, }); }); @@ -26,7 +26,7 @@ describe('Single Stat Chart component', () => { it('should change the value representation to a percentile one', () => { singleStatChart.setProps({ graphData: { - ...graphDataPrometheusQuery, + ...singleStatMetricsResult, maxValue: 120, }, }); @@ -37,7 +37,7 @@ describe('Single Stat Chart component', () => { it('should display NaN for non numeric maxValue values', () => { singleStatChart.setProps({ graphData: { - ...graphDataPrometheusQuery, + ...singleStatMetricsResult, maxValue: 'not a number', }, }); @@ -48,13 +48,13 @@ describe('Single Stat Chart component', () => { it('should display NaN for missing query values', () => { singleStatChart.setProps({ graphData: { - ...graphDataPrometheusQuery, + ...singleStatMetricsResult, metrics: [ { - ...graphDataPrometheusQuery.metrics[0], + ...singleStatMetricsResult.metrics[0], result: [ { - ...graphDataPrometheusQuery.metrics[0].result[0], + ...singleStatMetricsResult.metrics[0].result[0], value: [''], }, ], diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index 5ac716b0c63..7d5a08bc4a1 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -1,4 +1,4 @@ -import { mount } from '@vue/test-utils'; +import { mount, shallowMount } from '@vue/test-utils'; import { setTestTimeout } from 'helpers/timeout'; import { GlLink } from '@gitlab/ui'; import { TEST_HOST } from 'jest/helpers/test_constants'; @@ -11,6 +11,7 @@ import { import { cloneDeep } from 'lodash'; import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; import { createStore } from '~/monitoring/stores'; +import { panelTypes, chartHeight } from '~/monitoring/constants'; import TimeSeries from '~/monitoring/components/charts/time_series.vue'; import * as types from '~/monitoring/stores/mutation_types'; import { deploymentData, mockProjectDir, annotationsData } from '../../mock_data'; @@ -39,10 +40,10 @@ describe('Time series component', () => { let mockGraphData; let store; - const makeTimeSeriesChart = (graphData, type) => - mount(TimeSeries, { + const createWrapper = (graphData = mockGraphData, mountingMethod = shallowMount) => + mountingMethod(TimeSeries, { propsData: { - graphData: { ...graphData, type }, + graphData, deploymentData: store.state.monitoringDashboard.deploymentData, annotations: store.state.monitoringDashboard.annotations, projectPath: `${TEST_HOST}${mockProjectDir}`, @@ -79,9 +80,9 @@ describe('Time series component', () => { const findChart = () => timeSeriesChart.find({ ref: 'chart' }); - beforeEach(done => { - timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); - timeSeriesChart.vm.$nextTick(done); + beforeEach(() => { + timeSeriesChart = createWrapper(mockGraphData, mount); + return timeSeriesChart.vm.$nextTick(); }); it('allows user to override max value label text using prop', () => { @@ -100,6 +101,21 @@ describe('Time series component', () => { }); }); + it('chart sets a default height', () => { + const wrapper = createWrapper(); + expect(wrapper.props('height')).toBe(chartHeight); + }); + + it('chart has a configurable height', () => { + const mockHeight = 599; + const wrapper = createWrapper(); + + wrapper.setProps({ height: mockHeight }); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.props('height')).toBe(mockHeight); + }); + }); + describe('events', () => { describe('datazoom', () => { let eChartMock; @@ -125,7 +141,7 @@ describe('Time series component', () => { }), }; - timeSeriesChart = makeTimeSeriesChart(mockGraphData); + timeSeriesChart = createWrapper(mockGraphData, mount); timeSeriesChart.vm.$nextTick(() => { findChart().vm.$emit('created', eChartMock); done(); @@ -535,11 +551,11 @@ describe('Time series component', () => { describe('wrapped components', () => { const glChartComponents = [ { - chartType: 'area-chart', + chartType: panelTypes.AREA_CHART, component: GlAreaChart, }, { - chartType: 'line-chart', + chartType: panelTypes.LINE_CHART, component: GlLineChart, }, ]; @@ -550,7 +566,10 @@ describe('Time series component', () => { const findChartComponent = () => timeSeriesAreaChart.find(dynamicComponent.component); beforeEach(done => { - timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); + timeSeriesAreaChart = createWrapper( + { ...mockGraphData, type: dynamicComponent.chartType }, + mount, + ); timeSeriesAreaChart.vm.$nextTick(done); }); @@ -632,7 +651,7 @@ describe('Time series component', () => { Object.assign(metric, { result: metricResultStatus.result }), ); - timeSeriesChart = makeTimeSeriesChart(graphData, 'area-chart'); + timeSeriesChart = createWrapper({ ...graphData, type: 'area-chart' }, mount); timeSeriesChart.vm.$nextTick(done); }); diff --git a/spec/frontend/monitoring/components/panel_type_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js index 819b5235284..f8c9bd56721 100644 --- a/spec/frontend/monitoring/components/panel_type_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js @@ -1,13 +1,13 @@ +import Vuex from 'vuex'; import { shallowMount } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import { setTestTimeout } from 'helpers/timeout'; import invalidUrl from '~/lib/utils/invalid_url'; import axios from '~/lib/utils/axios_utils'; +import { GlDropdownItem } from '@gitlab/ui'; +import AlertWidget from '~/monitoring/components/alert_widget.vue'; -import PanelType from '~/monitoring/components/panel_type.vue'; -import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; -import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue'; -import AnomalyChart from '~/monitoring/components/charts/anomaly.vue'; +import DashboardPanel from '~/monitoring/components/dashboard_panel.vue'; import { anomalyMockGraphData, mockLogsHref, @@ -15,8 +15,23 @@ import { mockNamespace, mockNamespacedData, mockTimeRange, + singleStatMetricsResult, + graphDataPrometheusQueryRangeMultiTrack, + barMockData, + propsData, } from '../mock_data'; +import { panelTypes } from '~/monitoring/constants'; + +import MonitorEmptyChart from '~/monitoring/components/charts/empty_chart.vue'; +import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue'; +import MonitorAnomalyChart from '~/monitoring/components/charts/anomaly.vue'; +import MonitorSingleStatChart from '~/monitoring/components/charts/single_stat.vue'; +import MonitorHeatmapChart from '~/monitoring/components/charts/heatmap.vue'; +import MonitorColumnChart from '~/monitoring/components/charts/column.vue'; +import MonitorBarChart from '~/monitoring/components/charts/bar.vue'; +import MonitorStackedColumnChart from '~/monitoring/components/charts/stacked_column.vue'; + import { graphData, graphDataEmpty } from '../fixture_data'; import { createStore, monitoringDashboard } from '~/monitoring/stores'; import { createStore as createEmbedGroupStore } from '~/monitoring/stores/embed_group'; @@ -29,7 +44,7 @@ const mocks = { }, }; -describe('Panel Type component', () => { +describe('Dashboard Panel', () => { let axiosMock; let store; let state; @@ -38,18 +53,20 @@ describe('Panel Type component', () => { const exampleText = 'example_text'; const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' }); - const findTimeChart = () => wrapper.find({ ref: 'timeChart' }); + const findTimeChart = () => wrapper.find({ ref: 'timeSeriesChart' }); const findTitle = () => wrapper.find({ ref: 'graphTitle' }); const findContextualMenu = () => wrapper.find({ ref: 'contextualMenu' }); - const createWrapper = props => { - wrapper = shallowMount(PanelType, { + const createWrapper = (props, options) => { + wrapper = shallowMount(DashboardPanel, { propsData: { graphData, + settingsPath: propsData.settingsPath, ...props, }, store, mocks, + ...options, }); }; @@ -66,6 +83,22 @@ describe('Panel Type component', () => { axiosMock.reset(); }); + describe('Renders slots', () => { + it('renders "topLeft" slot', () => { + createWrapper( + {}, + { + slots: { + topLeft: `<div class="top-left-content">OK</div>`, + }, + }, + ); + + expect(wrapper.find('.top-left-content').exists()).toBe(true); + expect(wrapper.find('.top-left-content').text()).toBe('OK'); + }); + }); + describe('When no graphData is available', () => { beforeEach(() => { createWrapper({ @@ -77,27 +110,54 @@ describe('Panel Type component', () => { wrapper.destroy(); }); - describe('Empty Chart component', () => { - it('renders the chart title', () => { - expect(findTitle().text()).toBe(graphDataEmpty.title); - }); + it('renders the chart title', () => { + expect(findTitle().text()).toBe(graphDataEmpty.title); + }); - it('renders the no download csv link', () => { - expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false); - }); + it('renders no download csv link', () => { + expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false); + }); - it('does not contain graph widgets', () => { - expect(findContextualMenu().exists()).toBe(false); - }); + it('does not contain graph widgets', () => { + expect(findContextualMenu().exists()).toBe(false); + }); - it('is a Vue instance', () => { - expect(wrapper.find(EmptyChart).exists()).toBe(true); - expect(wrapper.find(EmptyChart).isVueInstance()).toBe(true); + it('The Empty Chart component is rendered and is a Vue instance', () => { + expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true); + expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true); + }); + }); + + describe('When graphData is null', () => { + beforeEach(() => { + createWrapper({ + graphData: null, }); }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders no chart title', () => { + expect(findTitle().text()).toBe(''); + }); + + it('renders no download csv link', () => { + expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false); + }); + + it('does not contain graph widgets', () => { + expect(findContextualMenu().exists()).toBe(false); + }); + + it('The Empty Chart component is rendered and is a Vue instance', () => { + expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true); + expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true); + }); }); - describe('when graph data is available', () => { + describe('When graphData is available', () => { beforeEach(() => { createWrapper(); }); @@ -134,34 +194,54 @@ describe('Panel Type component', () => { }); }); - describe('Time Series Chart panel type', () => { - it('is rendered', () => { - expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true); - expect(wrapper.find(TimeSeriesChart).exists()).toBe(true); - }); + it('includes a default group id', () => { + expect(wrapper.vm.groupId).toBe('dashboard-panel'); + }); + + describe('Supports different panel types', () => { + const dataWithType = type => { + return { + ...graphData, + type, + }; + }; - it('includes a default group id', () => { - expect(wrapper.vm.groupId).toBe('panel-type-chart'); + it('empty chart is rendered for empty results', () => { + createWrapper({ graphData: graphDataEmpty }); + expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true); + expect(wrapper.find(MonitorEmptyChart).isVueInstance()).toBe(true); }); - }); - describe('Anomaly Chart panel type', () => { - beforeEach(() => { - wrapper.setProps({ - graphData: anomalyMockGraphData, - }); - return wrapper.vm.$nextTick(); + it('area chart is rendered by default', () => { + createWrapper(); + expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true); + expect(wrapper.find(MonitorTimeSeriesChart).isVueInstance()).toBe(true); }); - it('is rendered with an anomaly chart', () => { - expect(wrapper.find(AnomalyChart).isVueInstance()).toBe(true); - expect(wrapper.find(AnomalyChart).exists()).toBe(true); + it.each` + data | component + ${dataWithType(panelTypes.AREA_CHART)} | ${MonitorTimeSeriesChart} + ${dataWithType(panelTypes.LINE_CHART)} | ${MonitorTimeSeriesChart} + ${anomalyMockGraphData} | ${MonitorAnomalyChart} + ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart} + ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart} + ${singleStatMetricsResult} | ${MonitorSingleStatChart} + ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart} + ${barMockData} | ${MonitorBarChart} + `('wrapps a $data.type component binding attributes', ({ data, component }) => { + const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' }; + createWrapper({ graphData: data }, { attrs }); + + expect(wrapper.find(component).exists()).toBe(true); + expect(wrapper.find(component).isVueInstance()).toBe(true); + expect(wrapper.find(component).attributes()).toMatchObject(attrs); }); }); }); describe('Edit custom metric dropdown item', () => { const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' }); + const mockEditPath = '/root/kubernetes-gke-project/prometheus/metrics/23/edit'; beforeEach(() => { createWrapper(); @@ -180,7 +260,7 @@ describe('Panel Type component', () => { metrics: [ { ...graphData.metrics[0], - edit_path: '/root/kubernetes-gke-project/prometheus/metrics/23/edit', + edit_path: mockEditPath, }, ], }, @@ -189,10 +269,11 @@ describe('Panel Type component', () => { return wrapper.vm.$nextTick(() => { expect(findEditCustomMetricLink().exists()).toBe(true); expect(findEditCustomMetricLink().text()).toBe('Edit metric'); + expect(findEditCustomMetricLink().attributes('href')).toBe(mockEditPath); }); }); - it('shows an "Edit metrics" link for a panel with multiple metrics', () => { + it('shows an "Edit metrics" link pointing to settingsPath for a panel with multiple metrics', () => { wrapper.setProps({ graphData: { ...graphData, @@ -211,6 +292,7 @@ describe('Panel Type component', () => { return wrapper.vm.$nextTick(() => { expect(findEditCustomMetricLink().text()).toBe('Edit metrics'); + expect(findEditCustomMetricLink().attributes('href')).toBe(propsData.settingsPath); }); }); }); @@ -294,10 +376,6 @@ describe('Panel Type component', () => { }); }); - afterEach(() => { - wrapper.destroy(); - }); - it('sets clipboard text on the dropdown', () => { expect(findCopyLink().exists()).toBe(true); expect(findCopyLink().element.dataset.clipboardText).toBe(clipboardText); @@ -314,11 +392,24 @@ describe('Panel Type component', () => { }); }); + describe('when cliboard data is not available', () => { + it('there is no "copy to clipboard" link for a null value', () => { + createWrapper({ clipboardText: null }); + expect(findCopyLink().exists()).toBe(false); + }); + + it('there is no "copy to clipboard" link for an empty value', () => { + createWrapper({ clipboardText: '' }); + expect(findCopyLink().exists()).toBe(false); + }); + }); + describe('when downloading metrics data as CSV', () => { beforeEach(() => { - wrapper = shallowMount(PanelType, { + wrapper = shallowMount(DashboardPanel, { propsData: { clipboardText: exampleText, + settingsPath: propsData.settingsPath, graphData: { y_label: 'metric', ...graphData, @@ -365,9 +456,10 @@ describe('Panel Type component', () => { store.registerModule(mockNamespace, monitoringDashboard); store.state.embedGroup.modules.push(mockNamespace); - wrapper = shallowMount(PanelType, { + wrapper = shallowMount(DashboardPanel, { propsData: { graphData, + settingsPath: propsData.settingsPath, namespace: mockNamespace, }, store, @@ -401,8 +493,84 @@ describe('Panel Type component', () => { }); it('it renders a time series chart with no errors', () => { - expect(wrapper.find(TimeSeriesChart).isVueInstance()).toBe(true); - expect(wrapper.find(TimeSeriesChart).exists()).toBe(true); + expect(wrapper.find(MonitorTimeSeriesChart).isVueInstance()).toBe(true); + expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true); + }); + }); + + describe('Expand to full screen', () => { + const findExpandBtn = () => wrapper.find({ ref: 'expandBtn' }); + + describe('when there is no @expand listener', () => { + it('does not show `View full screen` option', () => { + createWrapper(); + expect(findExpandBtn().exists()).toBe(false); + }); + }); + + describe('when there is an @expand listener', () => { + beforeEach(() => { + createWrapper({}, { listeners: { expand: () => {} } }); + }); + + it('shows the `expand` option', () => { + expect(findExpandBtn().exists()).toBe(true); + }); + + it('emits the `expand` event', () => { + const preventDefault = jest.fn(); + findExpandBtn().vm.$emit('click', { preventDefault }); + expect(wrapper.emitted('expand')).toHaveLength(1); + expect(preventDefault).toHaveBeenCalled(); + }); + }); + }); + + describe('panel alerts', () => { + const setMetricsSavedToDb = val => + monitoringDashboard.getters.metricsSavedToDb.mockReturnValue(val); + const findAlertsWidget = () => wrapper.find(AlertWidget); + const findMenuItemAlert = () => + wrapper.findAll(GlDropdownItem).filter(i => i.text() === 'Alerts'); + + beforeEach(() => { + jest.spyOn(monitoringDashboard.getters, 'metricsSavedToDb').mockReturnValue([]); + + store = new Vuex.Store({ + modules: { + monitoringDashboard, + }, + }); + + createWrapper(); + }); + + describe.each` + desc | metricsSavedToDb | props | isShown + ${'with permission and no metrics in db'} | ${[]} | ${{}} | ${false} + ${'with permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{}} | ${true} + ${'without permission and related metrics in db'} | ${[graphData.metrics[0].metricId]} | ${{ prometheusAlertsAvailable: false }} | ${false} + ${'with permission and unrelated metrics in db'} | ${['another_metric_id']} | ${{}} | ${false} + `('$desc', ({ metricsSavedToDb, isShown, props }) => { + const showsDesc = isShown ? 'shows' : 'does not show'; + + beforeEach(() => { + setMetricsSavedToDb(metricsSavedToDb); + createWrapper({ + alertsEndpoint: '/endpoint', + prometheusAlertsAvailable: true, + ...props, + }); + return wrapper.vm.$nextTick(); + }); + + it(`${showsDesc} alert widget`, () => { + expect(findAlertsWidget().exists()).toBe(isShown); + }); + + it(`${showsDesc} alert configuration`, () => { + expect(findMenuItemAlert().exists()).toBe(isShown); + }); }); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 8b6ee9b3bf6..b2c9fe93cde 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -1,6 +1,8 @@ import { shallowMount, mount } from '@vue/test-utils'; import Tracking from '~/tracking'; -import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui'; +import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys'; +import { GlModal, GlDropdownItem, GlDeprecatedButton, GlIcon } from '@gitlab/ui'; +import { objectToQuery } from '~/lib/utils/url_utility'; import VueDraggable from 'vuedraggable'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; @@ -11,13 +13,23 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue'; import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue'; +import EmptyState from '~/monitoring/components/empty_state.vue'; import GroupEmptyState from '~/monitoring/components/group_empty_state.vue'; -import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; +import DashboardPanel from '~/monitoring/components/dashboard_panel.vue'; import { createStore } from '~/monitoring/stores'; import * as types from '~/monitoring/stores/mutation_types'; -import { setupStoreWithDashboard, setMetricResult, setupStoreWithData } from '../store_utils'; +import { + setupAllDashboards, + setupStoreWithDashboard, + setMetricResult, + setupStoreWithData, + setupStoreWithVariable, +} from '../store_utils'; import { environmentData, dashboardGitResponse, propsData } from '../mock_data'; import { metricsDashboardViewModel, metricsDashboardPanelCount } from '../fixture_data'; +import createFlash from '~/flash'; + +jest.mock('~/flash'); describe('Dashboard', () => { let store; @@ -27,15 +39,12 @@ describe('Dashboard', () => { const findEnvironmentsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' }); const findAllEnvironmentsDropdownItems = () => findEnvironmentsDropdown().findAll(GlDropdownItem); const setSearchTerm = searchTerm => { - wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm); + store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm); }; const createShallowWrapper = (props = {}, options = {}) => { wrapper = shallowMount(Dashboard, { propsData: { ...propsData, ...props }, - methods: { - fetchData: jest.fn(), - }, store, ...options, }); @@ -44,10 +53,8 @@ describe('Dashboard', () => { const createMountedWrapper = (props = {}, options = {}) => { wrapper = mount(Dashboard, { propsData: { ...propsData, ...props }, - methods: { - fetchData: jest.fn(), - }, store, + stubs: ['graph-group', 'dashboard-panel'], ...options, }); }; @@ -55,19 +62,18 @@ describe('Dashboard', () => { beforeEach(() => { store = createStore(); mock = new MockAdapter(axios); + jest.spyOn(store, 'dispatch').mockResolvedValue(); }); afterEach(() => { - if (wrapper) { - wrapper.destroy(); - wrapper = null; - } mock.restore(); + if (store.dispatch.mockReset) { + store.dispatch.mockReset(); + } }); describe('no metrics are available yet', () => { beforeEach(() => { - jest.spyOn(store, 'dispatch'); createShallowWrapper(); }); @@ -103,9 +109,7 @@ describe('Dashboard', () => { describe('request information to the server', () => { it('calls to set time range and fetch data', () => { - jest.spyOn(store, 'dispatch'); - - createShallowWrapper({ hasMetrics: true }, { methods: {} }); + createShallowWrapper({ hasMetrics: true }); return wrapper.vm.$nextTick().then(() => { expect(store.dispatch).toHaveBeenCalledWith( @@ -118,20 +122,20 @@ describe('Dashboard', () => { }); it('shows up a loading state', () => { - createShallowWrapper({ hasMetrics: true }, { methods: {} }); + store.state.monitoringDashboard.emptyState = 'loading'; + + createShallowWrapper({ hasMetrics: true }); return wrapper.vm.$nextTick().then(() => { - expect(wrapper.vm.emptyState).toEqual('loading'); + expect(wrapper.find(EmptyState).exists()).toBe(true); + expect(wrapper.find(EmptyState).props('selectedState')).toBe('loading'); }); }); it('hides the group panels when showPanels is false', () => { - createMountedWrapper( - { hasMetrics: true, showPanels: false }, - { stubs: ['graph-group', 'panel-type'] }, - ); + createMountedWrapper({ hasMetrics: true, showPanels: false }); - setupStoreWithData(wrapper.vm.$store); + setupStoreWithData(store); return wrapper.vm.$nextTick().then(() => { expect(wrapper.vm.showEmptyState).toEqual(false); @@ -142,9 +146,9 @@ describe('Dashboard', () => { it('fetches the metrics data with proper time window', () => { jest.spyOn(store, 'dispatch'); - createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); + createMountedWrapper({ hasMetrics: true }); - wrapper.vm.$store.commit( + store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData, ); @@ -155,11 +159,176 @@ describe('Dashboard', () => { }); }); + describe('when the URL contains a reference to a panel', () => { + let location; + + const setSearch = search => { + window.location = { ...location, search }; + }; + + beforeEach(() => { + location = window.location; + delete window.location; + }); + + afterEach(() => { + window.location = location; + }); + + it('when the URL points to a panel it expands', () => { + const panelGroup = metricsDashboardViewModel.panelGroups[0]; + const panel = panelGroup.panels[0]; + + setSearch( + objectToQuery({ + group: panelGroup.group, + title: panel.title, + y_label: panel.y_label, + }), + ); + + createMountedWrapper({ hasMetrics: true }); + setupStoreWithData(store); + + return wrapper.vm.$nextTick().then(() => { + expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', { + group: panelGroup.group, + panel: expect.objectContaining({ + title: panel.title, + y_label: panel.y_label, + }), + }); + }); + }); + + it('when the URL does not link to any panel, no panel is expanded', () => { + setSearch(''); + + createMountedWrapper({ hasMetrics: true }); + setupStoreWithData(store); + + return wrapper.vm.$nextTick().then(() => { + expect(store.dispatch).not.toHaveBeenCalledWith( + 'monitoringDashboard/setExpandedPanel', + expect.anything(), + ); + }); + }); + + it('when the URL points to an incorrect panel it shows an error', () => { + const panelGroup = metricsDashboardViewModel.panelGroups[0]; + const panel = panelGroup.panels[0]; + + setSearch( + objectToQuery({ + group: panelGroup.group, + title: 'incorrect', + y_label: panel.y_label, + }), + ); + + createMountedWrapper({ hasMetrics: true }); + setupStoreWithData(store); + + return wrapper.vm.$nextTick().then(() => { + expect(createFlash).toHaveBeenCalled(); + expect(store.dispatch).not.toHaveBeenCalledWith( + 'monitoringDashboard/setExpandedPanel', + expect.anything(), + ); + }); + }); + }); + + describe('when the panel is expanded', () => { + let group; + let panel; + + const expandPanel = (mockGroup, mockPanel) => { + store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, { + group: mockGroup, + panel: mockPanel, + }); + }; + + beforeEach(() => { + setupStoreWithData(store); + + const { panelGroups } = store.state.monitoringDashboard.dashboard; + group = panelGroups[0].group; + [panel] = panelGroups[0].panels; + + jest.spyOn(window.history, 'pushState').mockImplementation(); + }); + + afterEach(() => { + window.history.pushState.mockRestore(); + }); + + it('URL is updated with panel parameters', () => { + createMountedWrapper({ hasMetrics: true }); + expandPanel(group, panel); + + const expectedSearch = objectToQuery({ + group, + title: panel.title, + y_label: panel.y_label, + }); + + return wrapper.vm.$nextTick(() => { + expect(window.history.pushState).toHaveBeenCalledTimes(1); + expect(window.history.pushState).toHaveBeenCalledWith( + expect.anything(), // state + expect.any(String), // document title + expect.stringContaining(`${expectedSearch}`), + ); + }); + }); + + it('URL is updated with panel parameters and custom dashboard', () => { + const dashboard = 'dashboard.yml'; + + createMountedWrapper({ hasMetrics: true, currentDashboard: dashboard }); + expandPanel(group, panel); + + const expectedSearch = objectToQuery({ + dashboard, + group, + title: panel.title, + y_label: panel.y_label, + }); + + return wrapper.vm.$nextTick(() => { + expect(window.history.pushState).toHaveBeenCalledTimes(1); + expect(window.history.pushState).toHaveBeenCalledWith( + expect.anything(), // state + expect.any(String), // document title + expect.stringContaining(`${expectedSearch}`), + ); + }); + }); + + it('URL is updated with no parameters', () => { + expandPanel(group, panel); + createMountedWrapper({ hasMetrics: true }); + expandPanel(null, null); + + return wrapper.vm.$nextTick(() => { + expect(window.history.pushState).toHaveBeenCalledTimes(1); + expect(window.history.pushState).toHaveBeenCalledWith( + expect.anything(), // state + expect.any(String), // document title + expect.not.stringMatching(/group|title|y_label/), // no panel params + ); + }); + }); + }); + describe('when all requests have been commited by the store', () => { beforeEach(() => { - createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); + createMountedWrapper({ hasMetrics: true }); - setupStoreWithData(wrapper.vm.$store); + setupStoreWithData(store); return wrapper.vm.$nextTick(); }); @@ -185,10 +354,89 @@ describe('Dashboard', () => { }); }); + describe('star dashboards', () => { + const findToggleStar = () => wrapper.find({ ref: 'toggleStarBtn' }); + const findToggleStarIcon = () => findToggleStar().find(GlIcon); + + beforeEach(() => { + createShallowWrapper(); + setupAllDashboards(store); + }); + + it('toggle star button is shown', () => { + expect(findToggleStar().exists()).toBe(true); + expect(findToggleStar().props('disabled')).toBe(false); + }); + + it('toggle star button is disabled when starring is taking place', () => { + store.commit(`monitoringDashboard/${types.REQUEST_DASHBOARD_STARRING}`); + + return wrapper.vm.$nextTick(() => { + expect(findToggleStar().exists()).toBe(true); + expect(findToggleStar().props('disabled')).toBe(true); + }); + }); + + describe('when the dashboard list is loaded', () => { + // Tooltip element should wrap directly + const getToggleTooltip = () => findToggleStar().element.parentElement.getAttribute('title'); + + beforeEach(() => { + setupAllDashboards(store); + jest.spyOn(store, 'dispatch'); + }); + + it('dispatches a toggle star action', () => { + findToggleStar().vm.$emit('click'); + + return wrapper.vm.$nextTick().then(() => { + expect(store.dispatch).toHaveBeenCalledWith( + 'monitoringDashboard/toggleStarredValue', + undefined, + ); + }); + }); + + describe('when dashboard is not starred', () => { + beforeEach(() => { + store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, { + currentDashboard: dashboardGitResponse[0].path, + }); + return wrapper.vm.$nextTick(); + }); + + it('toggle star button shows "Star dashboard"', () => { + expect(getToggleTooltip()).toBe('Star dashboard'); + }); + + it('toggle star button shows an unstarred state', () => { + expect(findToggleStarIcon().attributes('name')).toBe('star-o'); + }); + }); + + describe('when dashboard is starred', () => { + beforeEach(() => { + store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, { + currentDashboard: dashboardGitResponse[1].path, + }); + return wrapper.vm.$nextTick(); + }); + + it('toggle star button shows "Star dashboard"', () => { + expect(getToggleTooltip()).toBe('Unstar dashboard'); + }); + + it('toggle star button shows a starred state', () => { + expect(findToggleStarIcon().attributes('name')).toBe('star'); + }); + }); + }); + }); + it('hides the environments dropdown list when there is no environments', () => { - createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); + createMountedWrapper({ hasMetrics: true }); - setupStoreWithDashboard(wrapper.vm.$store); + setupStoreWithDashboard(store); return wrapper.vm.$nextTick().then(() => { expect(findAllEnvironmentsDropdownItems()).toHaveLength(0); @@ -196,9 +444,9 @@ describe('Dashboard', () => { }); it('renders the datetimepicker dropdown', () => { - createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); + createMountedWrapper({ hasMetrics: true }); - setupStoreWithData(wrapper.vm.$store); + setupStoreWithData(store); return wrapper.vm.$nextTick().then(() => { expect(wrapper.find(DateTimePicker).exists()).toBe(true); @@ -206,9 +454,9 @@ describe('Dashboard', () => { }); it('renders the refresh dashboard button', () => { - createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); + createMountedWrapper({ hasMetrics: true }); - setupStoreWithData(wrapper.vm.$store); + setupStoreWithData(store); return wrapper.vm.$nextTick().then(() => { const refreshBtn = wrapper.findAll({ ref: 'refreshDashboardBtn' }); @@ -218,14 +466,135 @@ describe('Dashboard', () => { }); }); - describe('when one of the metrics is missing', () => { + describe('variables section', () => { beforeEach(() => { createShallowWrapper({ hasMetrics: true }); + setupStoreWithData(store); + setupStoreWithVariable(store); + + return wrapper.vm.$nextTick(); + }); + + it('shows the variables section', () => { + expect(wrapper.vm.shouldShowVariablesSection).toBe(true); + }); + }); + + describe('single panel expands to "full screen" mode', () => { + const findExpandedPanel = () => wrapper.find({ ref: 'expandedPanel' }); - const { $store } = wrapper.vm; + describe('when the panel is not expanded', () => { + beforeEach(() => { + createShallowWrapper({ hasMetrics: true }); + setupStoreWithData(store); + return wrapper.vm.$nextTick(); + }); + + it('expanded panel is not visible', () => { + expect(findExpandedPanel().isVisible()).toBe(false); + }); + + it('can set a panel as expanded', () => { + const panel = wrapper.findAll(DashboardPanel).at(1); + + jest.spyOn(store, 'dispatch'); + + panel.vm.$emit('expand'); + + const groupData = metricsDashboardViewModel.panelGroups[0]; + + expect(store.dispatch).toHaveBeenCalledWith('monitoringDashboard/setExpandedPanel', { + group: groupData.group, + panel: expect.objectContaining({ + id: groupData.panels[0].id, + }), + }); + }); + }); + + describe('when the panel is expanded', () => { + let group; + let panel; + + const mockKeyup = key => window.dispatchEvent(new KeyboardEvent('keyup', { key })); + + const MockPanel = { + template: `<div><slot name="topLeft"/></div>`, + }; + + beforeEach(() => { + createShallowWrapper({ hasMetrics: true }, { stubs: { DashboardPanel: MockPanel } }); + setupStoreWithData(store); + + const { panelGroups } = store.state.monitoringDashboard.dashboard; + + group = panelGroups[0].group; + [panel] = panelGroups[0].panels; + + store.commit(`monitoringDashboard/${types.SET_EXPANDED_PANEL}`, { + group, + panel, + }); + + jest.spyOn(store, 'dispatch'); + + return wrapper.vm.$nextTick(); + }); - setupStoreWithDashboard($store); - setMetricResult({ $store, result: [], panel: 2 }); + it('displays a single panel and others are hidden', () => { + const panels = wrapper.findAll(MockPanel); + const visiblePanels = panels.filter(w => w.isVisible()); + + expect(findExpandedPanel().isVisible()).toBe(true); + // v-show for hiding panels is more performant than v-if + // check for panels to be hidden. + expect(panels.length).toBe(metricsDashboardPanelCount + 1); + expect(visiblePanels.length).toBe(1); + }); + + it('sets a link to the expanded panel', () => { + const searchQuery = + '?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=System%20metrics%20(Kubernetes)&title=Memory%20Usage%20(Total)&y_label=Total%20Memory%20Used%20(GB)'; + + expect(findExpandedPanel().attributes('clipboard-text')).toEqual( + expect.stringContaining(searchQuery), + ); + }); + + it('restores full dashboard by clicking `back`', () => { + wrapper.find({ ref: 'goBackBtn' }).vm.$emit('click'); + + expect(store.dispatch).toHaveBeenCalledWith( + 'monitoringDashboard/clearExpandedPanel', + undefined, + ); + }); + + it('restores dashboard from full screen by typing the Escape key', () => { + mockKeyup(ESC_KEY); + expect(store.dispatch).toHaveBeenCalledWith( + `monitoringDashboard/clearExpandedPanel`, + undefined, + ); + }); + + it('restores dashboard from full screen by typing the Escape key on IE11', () => { + mockKeyup(ESC_KEY_IE11); + + expect(store.dispatch).toHaveBeenCalledWith( + `monitoringDashboard/clearExpandedPanel`, + undefined, + ); + }); + }); + }); + + describe('when one of the metrics is missing', () => { + beforeEach(() => { + createShallowWrapper({ hasMetrics: true }); + + setupStoreWithDashboard(store); + setMetricResult({ store, result: [], panel: 2 }); return wrapper.vm.$nextTick(); }); @@ -249,19 +618,17 @@ describe('Dashboard', () => { describe('searchable environments dropdown', () => { beforeEach(() => { - createMountedWrapper( - { hasMetrics: true }, - { - attachToDocument: true, - stubs: ['graph-group', 'panel-type'], - }, - ); + createMountedWrapper({ hasMetrics: true }, { attachToDocument: true }); - setupStoreWithData(wrapper.vm.$store); + setupStoreWithData(store); return wrapper.vm.$nextTick(); }); + afterEach(() => { + wrapper.destroy(); + }); + it('renders a search input', () => { expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownSearch' }).exists()).toBe(true); }); @@ -304,7 +671,7 @@ describe('Dashboard', () => { }); it('shows loading element when environments fetch is still loading', () => { - wrapper.vm.$store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`); + store.commit(`monitoringDashboard/${types.REQUEST_ENVIRONMENTS_DATA}`); return wrapper.vm .$nextTick() @@ -312,7 +679,7 @@ describe('Dashboard', () => { expect(wrapper.find({ ref: 'monitorEnvironmentsDropdownLoading' }).exists()).toBe(true); }) .then(() => { - wrapper.vm.$store.commit( + store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData, ); @@ -330,9 +697,11 @@ describe('Dashboard', () => { const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); beforeEach(() => { - createShallowWrapper({ hasMetrics: true }); + // call original dispatch + store.dispatch.mockRestore(); - setupStoreWithData(wrapper.vm.$store); + createShallowWrapper({ hasMetrics: true }); + setupStoreWithData(store); return wrapper.vm.$nextTick(); }); @@ -420,7 +789,7 @@ describe('Dashboard', () => { createShallowWrapper({ hasMetrics: true, showHeader: false }); // all_dashboards is not defined in health dashboards - wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined); + store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined); return wrapper.vm.$nextTick(); }); @@ -440,10 +809,7 @@ describe('Dashboard', () => { beforeEach(() => { createShallowWrapper({ hasMetrics: true }); - wrapper.vm.$store.commit( - `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, - dashboardGitResponse, - ); + setupAllDashboards(store); return wrapper.vm.$nextTick(); }); @@ -452,10 +818,11 @@ describe('Dashboard', () => { }); it('is present for a custom dashboard, and links to its edit_path', () => { - const dashboard = dashboardGitResponse[1]; // non-default dashboard - const currentDashboard = dashboard.path; + const dashboard = dashboardGitResponse[1]; + store.commit(`monitoringDashboard/${types.SET_INITIAL_STATE}`, { + currentDashboard: dashboard.path, + }); - wrapper.setProps({ currentDashboard }); return wrapper.vm.$nextTick().then(() => { expect(findEditLink().exists()).toBe(true); expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path); @@ -465,13 +832,8 @@ describe('Dashboard', () => { describe('Dashboard dropdown', () => { beforeEach(() => { - createMountedWrapper({ hasMetrics: true }, { stubs: ['graph-group', 'panel-type'] }); - - wrapper.vm.$store.commit( - `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, - dashboardGitResponse, - ); - + createMountedWrapper({ hasMetrics: true }); + setupAllDashboards(store); return wrapper.vm.$nextTick(); }); @@ -484,15 +846,12 @@ describe('Dashboard', () => { describe('external dashboard link', () => { beforeEach(() => { - createMountedWrapper( - { - hasMetrics: true, - showPanels: false, - showTimeWindowDropdown: false, - externalDashboardUrl: '/mockUrl', - }, - { stubs: ['graph-group', 'panel-type'] }, - ); + createMountedWrapper({ + hasMetrics: true, + showPanels: false, + showTimeWindowDropdown: false, + externalDashboardUrl: '/mockUrl', + }); return wrapper.vm.$nextTick(); }); @@ -507,45 +866,29 @@ describe('Dashboard', () => { }); describe('Clipboard text in panels', () => { - const currentDashboard = 'TEST_DASHBOARD'; + const currentDashboard = dashboardGitResponse[1].path; + const panelIndex = 1; // skip expanded panel - const getClipboardTextAt = i => + const getClipboardTextFirstPanel = () => wrapper - .findAll(PanelType) - .at(i) + .findAll(DashboardPanel) + .at(panelIndex) .props('clipboardText'); beforeEach(() => { + setupStoreWithData(store); createShallowWrapper({ hasMetrics: true, currentDashboard }); - setupStoreWithData(wrapper.vm.$store); - return wrapper.vm.$nextTick(); }); it('contains a link to the dashboard', () => { - expect(getClipboardTextAt(0)).toContain(`dashboard=${currentDashboard}`); - expect(getClipboardTextAt(0)).toContain(`group=`); - expect(getClipboardTextAt(0)).toContain(`title=`); - expect(getClipboardTextAt(0)).toContain(`y_label=`); - }); - - it('strips the undefined parameter', () => { - wrapper.setProps({ currentDashboard: undefined }); - - return wrapper.vm.$nextTick(() => { - expect(getClipboardTextAt(0)).not.toContain(`dashboard=`); - expect(getClipboardTextAt(0)).toContain(`y_label=`); - }); - }); + const dashboardParam = `dashboard=${encodeURIComponent(currentDashboard)}`; - it('null parameter is stripped', () => { - wrapper.setProps({ currentDashboard: null }); - - return wrapper.vm.$nextTick(() => { - expect(getClipboardTextAt(0)).not.toContain(`dashboard=`); - expect(getClipboardTextAt(0)).toContain(`y_label=`); - }); + expect(getClipboardTextFirstPanel()).toContain(dashboardParam); + expect(getClipboardTextFirstPanel()).toContain(`group=`); + expect(getClipboardTextFirstPanel()).toContain(`title=`); + expect(getClipboardTextFirstPanel()).toContain(`y_label=`); }); }); @@ -572,7 +915,7 @@ describe('Dashboard', () => { customMetricsPath: '/endpoint', customMetricsAvailable: true, }); - setupStoreWithData(wrapper.vm.$store); + setupStoreWithData(store); origPage = document.body.dataset.page; document.body.dataset.page = 'projects:environments:metrics'; diff --git a/spec/frontend/monitoring/components/dashboard_template_spec.js b/spec/frontend/monitoring/components/dashboard_template_spec.js index d1790df4189..cc0ac348b11 100644 --- a/spec/frontend/monitoring/components/dashboard_template_spec.js +++ b/spec/frontend/monitoring/components/dashboard_template_spec.js @@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import Dashboard from '~/monitoring/components/dashboard.vue'; import { createStore } from '~/monitoring/stores'; +import { setupAllDashboards } from '../store_utils'; import { propsData } from '../mock_data'; jest.mock('~/lib/utils/url_utility'); @@ -15,24 +16,16 @@ describe('Dashboard template', () => { beforeEach(() => { store = createStore(); mock = new MockAdapter(axios); + + setupAllDashboards(store); }); afterEach(() => { - if (wrapper) { - wrapper.destroy(); - wrapper = null; - } mock.restore(); }); it('matches the default snapshot', () => { - wrapper = shallowMount(Dashboard, { - propsData: { ...propsData }, - methods: { - fetchData: jest.fn(), - }, - store, - }); + wrapper = shallowMount(Dashboard, { propsData: { ...propsData }, store }); expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js index 65e9d036d1a..9bba5280007 100644 --- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js +++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js @@ -27,7 +27,7 @@ describe('dashboard invalid url parameters', () => { wrapper = mount(Dashboard, { propsData: { ...propsData, ...props }, store, - stubs: ['graph-group', 'panel-type'], + stubs: ['graph-group', 'dashboard-panel'], ...options, }); }; diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js index 0bcfabe6415..b29d86cbc5b 100644 --- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js +++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert } from '@gitlab/ui'; +import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert, GlIcon } from '@gitlab/ui'; import waitForPromises from 'helpers/wait_for_promises'; import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue'; @@ -9,36 +9,48 @@ import { dashboardGitResponse } from '../mock_data'; const defaultBranch = 'master'; -function createComponent(props, opts = {}) { - const storeOpts = { - methods: { - duplicateSystemDashboard: jest.fn(), - }, - computed: { - allDashboards: () => dashboardGitResponse, - }, - }; - - return shallowMount(DashboardsDropdown, { - propsData: { - ...props, - defaultBranch, - }, - sync: false, - ...storeOpts, - ...opts, - }); -} +const starredDashboards = dashboardGitResponse.filter(({ starred }) => starred); +const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starred); describe('DashboardsDropdown', () => { let wrapper; + let mockDashboards; + let mockSelectedDashboard; + + function createComponent(props, opts = {}) { + const storeOpts = { + methods: { + duplicateSystemDashboard: jest.fn(), + }, + computed: { + allDashboards: () => mockDashboards, + selectedDashboard: () => mockSelectedDashboard, + }, + }; + + return shallowMount(DashboardsDropdown, { + propsData: { + ...props, + defaultBranch, + }, + sync: false, + ...storeOpts, + ...opts, + }); + } const findItems = () => wrapper.findAll(GlDropdownItem); const findItemAt = i => wrapper.findAll(GlDropdownItem).at(i); const findSearchInput = () => wrapper.find({ ref: 'monitorDashboardsDropdownSearch' }); const findNoItemsMsg = () => wrapper.find({ ref: 'monitorDashboardsDropdownMsg' }); + const findStarredListDivider = () => wrapper.find({ ref: 'starredListDivider' }); const setSearchTerm = searchTerm => wrapper.setData({ searchTerm }); + beforeEach(() => { + mockDashboards = dashboardGitResponse; + mockSelectedDashboard = null; + }); + describe('when it receives dashboards data', () => { beforeEach(() => { wrapper = createComponent(); @@ -48,10 +60,14 @@ describe('DashboardsDropdown', () => { expect(findItems().length).toEqual(dashboardGitResponse.length); }); - it('displays items with the dashboard display name', () => { - expect(findItemAt(0).text()).toBe(dashboardGitResponse[0].display_name); - expect(findItemAt(1).text()).toBe(dashboardGitResponse[1].display_name); - expect(findItemAt(2).text()).toBe(dashboardGitResponse[2].display_name); + it('displays items with the dashboard display name, with starred dashboards first', () => { + expect(findItemAt(0).text()).toBe(starredDashboards[0].display_name); + expect(findItemAt(1).text()).toBe(notStarredDashboards[0].display_name); + expect(findItemAt(2).text()).toBe(notStarredDashboards[1].display_name); + }); + + it('displays separator between starred and not starred dashboards', () => { + expect(findStarredListDivider().exists()).toBe(true); }); it('displays a search input', () => { @@ -81,18 +97,71 @@ describe('DashboardsDropdown', () => { }); }); + describe('when the dashboard is missing a display name', () => { + beforeEach(() => { + mockDashboards = dashboardGitResponse.map(d => ({ ...d, display_name: undefined })); + wrapper = createComponent(); + }); + + it('displays items with the dashboard path, with starred dashboards first', () => { + expect(findItemAt(0).text()).toBe(starredDashboards[0].path); + expect(findItemAt(1).text()).toBe(notStarredDashboards[0].path); + expect(findItemAt(2).text()).toBe(notStarredDashboards[1].path); + }); + }); + + describe('when it receives starred dashboards', () => { + beforeEach(() => { + mockDashboards = starredDashboards; + wrapper = createComponent(); + }); + + it('displays an item for each dashboard', () => { + expect(findItems().length).toEqual(starredDashboards.length); + }); + + it('displays a star icon', () => { + const star = findItemAt(0).find(GlIcon); + expect(star.exists()).toBe(true); + expect(star.attributes('name')).toBe('star'); + }); + + it('displays no separator between starred and not starred dashboards', () => { + expect(findStarredListDivider().exists()).toBe(false); + }); + }); + + describe('when it receives only not-starred dashboards', () => { + beforeEach(() => { + mockDashboards = notStarredDashboards; + wrapper = createComponent(); + }); + + it('displays an item for each dashboard', () => { + expect(findItems().length).toEqual(notStarredDashboards.length); + }); + + it('displays no star icon', () => { + const star = findItemAt(0).find(GlIcon); + expect(star.exists()).toBe(false); + }); + + it('displays no separator between starred and not starred dashboards', () => { + expect(findStarredListDivider().exists()).toBe(false); + }); + }); + describe('when a system dashboard is selected', () => { let duplicateDashboardAction; let modalDirective; beforeEach(() => { + [mockSelectedDashboard] = dashboardGitResponse; modalDirective = jest.fn(); duplicateDashboardAction = jest.fn().mockResolvedValue(); wrapper = createComponent( - { - selectedDashboard: dashboardGitResponse[0], - }, + {}, { directives: { GlModal: modalDirective, @@ -260,7 +329,7 @@ describe('DashboardsDropdown', () => { expect(wrapper.emitted().selectDashboard).toBeTruthy(); }); it('emits a "selectDashboard" event with dashboard information', () => { - expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[1]]); + expect(wrapper.emitted().selectDashboard[0]).toEqual([dashboardGitResponse[0]]); }); }); }); diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js index 10fd58f749d..216ec345552 100644 --- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js +++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js @@ -81,7 +81,8 @@ describe('DuplicateDashboardForm', () => { it('with the inital form values', () => { expect(wrapper.emitted().change).toHaveLength(1); - expect(lastChange()).resolves.toEqual({ + + return expect(lastChange()).resolves.toEqual({ branch: '', commitMessage: expect.any(String), dashboard: dashboardGitResponse[0].path, @@ -92,7 +93,7 @@ describe('DuplicateDashboardForm', () => { it('containing an inputted file name', () => { setValue('fileName', 'my_dashboard.yml'); - expect(lastChange()).resolves.toMatchObject({ + return expect(lastChange()).resolves.toMatchObject({ fileName: 'my_dashboard.yml', }); }); @@ -100,7 +101,7 @@ describe('DuplicateDashboardForm', () => { it('containing a default commit message when no message is set', () => { setValue('commitMessage', ''); - expect(lastChange()).resolves.toMatchObject({ + return expect(lastChange()).resolves.toMatchObject({ commitMessage: expect.stringContaining('Create custom dashboard'), }); }); @@ -108,7 +109,7 @@ describe('DuplicateDashboardForm', () => { it('containing an inputted commit message', () => { setValue('commitMessage', 'My commit message'); - expect(lastChange()).resolves.toMatchObject({ + return expect(lastChange()).resolves.toMatchObject({ commitMessage: expect.stringContaining('My commit message'), }); }); @@ -116,7 +117,7 @@ describe('DuplicateDashboardForm', () => { it('containing an inputted branch name', () => { setValue('branchName', 'a-new-branch'); - expect(lastChange()).resolves.toMatchObject({ + return expect(lastChange()).resolves.toMatchObject({ branch: 'a-new-branch', }); }); @@ -125,13 +126,14 @@ describe('DuplicateDashboardForm', () => { setChecked(wrapper.vm.$options.radioVals.DEFAULT); setValue('branchName', 'a-new-branch'); - expect(lastChange()).resolves.toMatchObject({ - branch: defaultBranch, - }); - - return wrapper.vm.$nextTick(() => { - expect(findByRef('branchName').isVisible()).toBe(false); - }); + return Promise.all([ + expect(lastChange()).resolves.toMatchObject({ + branch: defaultBranch, + }), + wrapper.vm.$nextTick(() => { + expect(findByRef('branchName').isVisible()).toBe(false); + }), + ]); }); it('when `new` branch option is chosen, focuses on the branch name input', () => { diff --git a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js index b829cd53479..f23823ccad6 100644 --- a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js +++ b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js @@ -1,6 +1,6 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import Vuex from 'vuex'; -import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue'; +import DashboardPanel from '~/monitoring/components/dashboard_panel.vue'; import { TEST_HOST } from 'helpers/test_constants'; import MetricEmbed from '~/monitoring/components/embeds/metric_embed.vue'; import { groups, initialState, metricsData, metricsWithData } from './mock_data'; @@ -62,7 +62,7 @@ describe('MetricEmbed', () => { it('shows an empty state when no metrics are present', () => { expect(wrapper.find('.metrics-embed').exists()).toBe(true); - expect(wrapper.find(PanelType).exists()).toBe(false); + expect(wrapper.find(DashboardPanel).exists()).toBe(false); }); }); @@ -90,12 +90,12 @@ describe('MetricEmbed', () => { it('shows a chart when metrics are present', () => { expect(wrapper.find('.metrics-embed').exists()).toBe(true); - expect(wrapper.find(PanelType).exists()).toBe(true); - expect(wrapper.findAll(PanelType).length).toBe(2); + expect(wrapper.find(DashboardPanel).exists()).toBe(true); + expect(wrapper.findAll(DashboardPanel).length).toBe(2); }); it('includes groupId with dashboardUrl', () => { - expect(wrapper.find(PanelType).props('groupId')).toBe(TEST_HOST); + expect(wrapper.find(DashboardPanel).props('groupId')).toBe(TEST_HOST); }); }); }); diff --git a/spec/frontend/monitoring/components/variables/custom_variable_spec.js b/spec/frontend/monitoring/components/variables/custom_variable_spec.js new file mode 100644 index 00000000000..5a2b26219b6 --- /dev/null +++ b/spec/frontend/monitoring/components/variables/custom_variable_spec.js @@ -0,0 +1,52 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import CustomVariable from '~/monitoring/components/variables/custom_variable.vue'; + +describe('Custom variable component', () => { + let wrapper; + const propsData = { + name: 'env', + label: 'Select environment', + value: 'Production', + options: [{ text: 'Production', value: 'prod' }, { text: 'Canary', value: 'canary' }], + }; + const createShallowWrapper = () => { + wrapper = shallowMount(CustomVariable, { + propsData, + }); + }; + + const findDropdown = () => wrapper.find(GlDropdown); + const findDropdownItems = () => wrapper.findAll(GlDropdownItem); + + it('renders dropdown element when all necessary props are passed', () => { + createShallowWrapper(); + + expect(findDropdown()).toExist(); + }); + + it('renders dropdown element with a text', () => { + createShallowWrapper(); + + expect(findDropdown().attributes('text')).toBe(propsData.value); + }); + + it('renders all the dropdown items', () => { + createShallowWrapper(); + + expect(findDropdownItems()).toHaveLength(propsData.options.length); + }); + + it('changing dropdown items triggers update', () => { + createShallowWrapper(); + jest.spyOn(wrapper.vm, '$emit'); + + findDropdownItems() + .at(1) + .vm.$emit('click'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'env', 'canary'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/variables/text_variable_spec.js b/spec/frontend/monitoring/components/variables/text_variable_spec.js new file mode 100644 index 00000000000..f01584ae8bc --- /dev/null +++ b/spec/frontend/monitoring/components/variables/text_variable_spec.js @@ -0,0 +1,59 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlFormInput } from '@gitlab/ui'; +import TextVariable from '~/monitoring/components/variables/text_variable.vue'; + +describe('Text variable component', () => { + let wrapper; + const propsData = { + name: 'pod', + label: 'Select pod', + value: 'test-pod', + }; + const createShallowWrapper = () => { + wrapper = shallowMount(TextVariable, { + propsData, + }); + }; + + const findInput = () => wrapper.find(GlFormInput); + + it('renders a text input when all props are passed', () => { + createShallowWrapper(); + + expect(findInput()).toExist(); + }); + + it('always has a default value', () => { + createShallowWrapper(); + + return wrapper.vm.$nextTick(() => { + expect(findInput().attributes('value')).toBe(propsData.value); + }); + }); + + it('triggers keyup enter', () => { + createShallowWrapper(); + jest.spyOn(wrapper.vm, '$emit'); + + findInput().element.value = 'prod-pod'; + findInput().trigger('input'); + findInput().trigger('keyup.enter'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'prod-pod'); + }); + }); + + it('triggers blur enter', () => { + createShallowWrapper(); + jest.spyOn(wrapper.vm, '$emit'); + + findInput().element.value = 'canary-pod'; + findInput().trigger('input'); + findInput().trigger('blur'); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.vm.$emit).toHaveBeenCalledWith('onUpdate', 'pod', 'canary-pod'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js new file mode 100644 index 00000000000..095d89c9231 --- /dev/null +++ b/spec/frontend/monitoring/components/variables_section_spec.js @@ -0,0 +1,126 @@ +import { shallowMount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import VariablesSection from '~/monitoring/components/variables_section.vue'; +import CustomVariable from '~/monitoring/components/variables/custom_variable.vue'; +import TextVariable from '~/monitoring/components/variables/text_variable.vue'; +import { updateHistory, mergeUrlParams } from '~/lib/utils/url_utility'; +import { createStore } from '~/monitoring/stores'; +import { convertVariablesForURL } from '~/monitoring/utils'; +import * as types from '~/monitoring/stores/mutation_types'; +import { mockTemplatingDataResponses } from '../mock_data'; + +jest.mock('~/lib/utils/url_utility', () => ({ + updateHistory: jest.fn(), + mergeUrlParams: jest.fn(), +})); + +describe('Metrics dashboard/variables section component', () => { + let store; + let wrapper; + const sampleVariables = { + label1: mockTemplatingDataResponses.simpleText.simpleText, + label2: mockTemplatingDataResponses.advText.advText, + label3: mockTemplatingDataResponses.simpleCustom.simpleCustom, + }; + + const createShallowWrapper = () => { + wrapper = shallowMount(VariablesSection, { + store, + }); + }; + + const findTextInput = () => wrapper.findAll(TextVariable); + const findCustomInput = () => wrapper.findAll(CustomVariable); + + beforeEach(() => { + store = createStore(); + + store.state.monitoringDashboard.showEmptyState = false; + }); + + it('does not show the variables section', () => { + createShallowWrapper(); + const allInputs = findTextInput().length + findCustomInput().length; + + expect(allInputs).toBe(0); + }); + + it('shows the variables section', () => { + createShallowWrapper(); + store.commit(`monitoringDashboard/${types.SET_VARIABLES}`, sampleVariables); + + return wrapper.vm.$nextTick(() => { + const allInputs = findTextInput().length + findCustomInput().length; + + expect(allInputs).toBe(Object.keys(sampleVariables).length); + }); + }); + + describe('when changing the variable inputs', () => { + const fetchDashboardData = jest.fn(); + const updateVariableValues = jest.fn(); + + beforeEach(() => { + store = new Vuex.Store({ + modules: { + monitoringDashboard: { + namespaced: true, + state: { + showEmptyState: false, + promVariables: sampleVariables, + }, + actions: { + fetchDashboardData, + updateVariableValues, + }, + }, + }, + }); + + createShallowWrapper(); + }); + + it('merges the url params and refreshes the dashboard when a text-based variables inputs are updated', () => { + const firstInput = findTextInput().at(0); + + firstInput.vm.$emit('onUpdate', 'label1', 'test'); + + return wrapper.vm.$nextTick(() => { + expect(updateVariableValues).toHaveBeenCalled(); + expect(mergeUrlParams).toHaveBeenCalledWith( + convertVariablesForURL(sampleVariables), + window.location.href, + ); + expect(updateHistory).toHaveBeenCalled(); + expect(fetchDashboardData).toHaveBeenCalled(); + }); + }); + + it('merges the url params and refreshes the dashboard when a custom-based variables inputs are updated', () => { + const firstInput = findCustomInput().at(0); + + firstInput.vm.$emit('onUpdate', 'label1', 'test'); + + return wrapper.vm.$nextTick(() => { + expect(updateVariableValues).toHaveBeenCalled(); + expect(mergeUrlParams).toHaveBeenCalledWith( + convertVariablesForURL(sampleVariables), + window.location.href, + ); + expect(updateHistory).toHaveBeenCalled(); + expect(fetchDashboardData).toHaveBeenCalled(); + }); + }); + + it('does not merge the url params and refreshes the dashboard if the value entered is not different that is what currently stored', () => { + const firstInput = findTextInput().at(0); + + firstInput.vm.$emit('onUpdate', 'label1', 'Simple text'); + + expect(updateVariableValues).not.toHaveBeenCalled(); + expect(mergeUrlParams).not.toHaveBeenCalled(); + expect(updateHistory).not.toHaveBeenCalled(); + expect(fetchDashboardData).not.toHaveBeenCalled(); + }); + }); +}); |