summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-20 09:24:38 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-20 09:24:38 +0000
commit898e2cc1dfa88b4ac39cb4b35011f61b37f57b51 (patch)
treec6524edb6c9a43cccf93be05c36883fde1a53ee4 /spec/frontend
parentb5571e6e22cdacc81f78eff5943d68c8ba220fbb (diff)
downloadgitlab-ce-898e2cc1dfa88b4ac39cb4b35011f61b37f57b51.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js573
-rw-r--r--spec/frontend/monitoring/components/dashboard_time_url_spec.js54
-rw-r--r--spec/frontend/monitoring/components/dashboard_time_window_spec.js75
-rw-r--r--spec/frontend/monitoring/components/graph_group_spec.js120
-rw-r--r--spec/frontend/monitoring/init_utils.js56
-rw-r--r--spec/frontend/monitoring/shared/prometheus_header_spec.js26
6 files changed, 904 insertions, 0 deletions
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
new file mode 100644
index 00000000000..8a10857d0ff
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -0,0 +1,573 @@
+import { shallowMount, createLocalVue, mount } from '@vue/test-utils';
+import { GlDropdownItem, GlButton, GlToast } from '@gitlab/ui';
+import VueDraggable from 'vuedraggable';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import statusCodes from '~/lib/utils/http_status';
+import { metricStates } from '~/monitoring/constants';
+import Dashboard from '~/monitoring/components/dashboard.vue';
+import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue';
+import GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
+import { createStore } from '~/monitoring/stores';
+import * as types from '~/monitoring/stores/mutation_types';
+import * as monitoringUtils from '~/monitoring/utils';
+import { setupComponentStore, propsData } from '../init_utils';
+import {
+ metricsGroupsAPIResponse,
+ mockedQueryResultPayload,
+ mockApiEndpoint,
+ environmentData,
+ dashboardGitResponse,
+} from '../mock_data';
+
+const localVue = createLocalVue();
+const expectedPanelCount = 2;
+
+describe('Dashboard', () => {
+ let DashboardComponent;
+ let store;
+ let wrapper;
+ let mock;
+
+ const createShallowWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(localVue.extend(DashboardComponent), {
+ localVue,
+ sync: false,
+ propsData: { ...propsData, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ const createMountedWrapper = (props = {}, options = {}) => {
+ wrapper = mount(localVue.extend(DashboardComponent), {
+ localVue,
+ sync: false,
+ propsData: { ...propsData, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ DashboardComponent = localVue.extend(Dashboard);
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ mock.restore();
+ });
+
+ describe('no metrics are available yet', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
+
+ createShallowWrapper({}, { attachToDocument: true });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('shows the environment selector', () => {
+ expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
+ });
+ });
+
+ describe('no data found', () => {
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
+
+ createShallowWrapper({}, { attachToDocument: true });
+
+ wrapper.vm.$nextTick(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('shows the environment selector dropdown', () => {
+ expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
+ });
+ });
+
+ describe('request information to the server', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+ });
+
+ it('shows up a loading state', done => {
+ createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.vm.emptyState).toEqual('loading');
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('hides the group panels when showPanels is false', done => {
+ createMountedWrapper(
+ { hasMetrics: true, showPanels: false },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ setupComponentStore(wrapper);
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.vm.showEmptyState).toEqual(false);
+ expect(wrapper.vm.$el.querySelector('.prometheus-panel')).toEqual(null);
+ // TODO: The last expectation doesn't belong here, it belongs in a `group_group_spec.js` file
+ // Issue: https://gitlab.com/gitlab-org/gitlab/issues/118780
+ // expect(wrapper.vm.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('fetches the metrics data with proper time window', done => {
+ const getTimeDiffSpy = jest.spyOn(monitoringUtils, 'getTimeDiff');
+ jest.spyOn(store, 'dispatch');
+
+ createMountedWrapper(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(store.dispatch).toHaveBeenCalled();
+ expect(getTimeDiffSpy).toHaveBeenCalled();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('when all requests have been commited by the store', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
+
+ createMountedWrapper(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ setupComponentStore(wrapper);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders the environments dropdown with a number of environments', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const environmentDropdownItems = wrapper
+ .find('.js-environments-dropdown')
+ .findAll(GlDropdownItem);
+
+ expect(wrapper.vm.environments.length).toEqual(environmentData.length);
+ expect(environmentDropdownItems.length).toEqual(wrapper.vm.environments.length);
+
+ environmentDropdownItems.wrappers.forEach((itemWrapper, index) => {
+ const anchorEl = itemWrapper.find('a');
+ if (anchorEl.exists() && environmentData[index].metrics_path) {
+ const href = anchorEl.attributes('href');
+ expect(href).toBe(environmentData[index].metrics_path);
+ }
+ });
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('renders the environments dropdown with a single active element', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const environmentDropdownItems = wrapper
+ .find('.js-environments-dropdown')
+ .findAll(GlDropdownItem);
+ const activeItem = environmentDropdownItems.wrappers.filter(itemWrapper =>
+ itemWrapper.find('.active').exists(),
+ );
+
+ expect(activeItem.length).toBe(1);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ it('hides the environments dropdown list when there is no environments', done => {
+ createMountedWrapper(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ metricsGroupsAPIResponse,
+ );
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
+ mockedQueryResultPayload,
+ );
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const environmentDropdownItems = wrapper
+ .find('.js-environments-dropdown')
+ .findAll(GlDropdownItem);
+
+ expect(environmentDropdownItems.length).toEqual(0);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('renders the datetimepicker dropdown', done => {
+ createMountedWrapper(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ setupComponentStore(wrapper);
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(wrapper.find(DateTimePicker).exists()).toBe(true);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ describe('when one of the metrics is missing', () => {
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
+ setupComponentStore(wrapper);
+
+ wrapper.vm.$nextTick(done);
+ });
+
+ it('shows a group empty area', () => {
+ const emptyGroup = wrapper.findAll({ ref: 'empty-group' });
+
+ expect(emptyGroup).toHaveLength(1);
+ expect(emptyGroup.is(GroupEmptyState)).toBe(true);
+ });
+
+ it('group empty area displays a NO_DATA state', () => {
+ expect(
+ wrapper
+ .findAll({ ref: 'empty-group' })
+ .at(0)
+ .props('selectedState'),
+ ).toEqual(metricStates.NO_DATA);
+ });
+ });
+
+ describe('drag and drop function', () => {
+ const findDraggables = () => wrapper.findAll(VueDraggable);
+ const findEnabledDraggables = () => findDraggables().filter(f => !f.attributes('disabled'));
+ const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel');
+ const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
+
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
+ });
+
+ beforeEach(done => {
+ createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
+
+ setupComponentStore(wrapper);
+
+ wrapper.vm.$nextTick(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('wraps vuedraggable', () => {
+ expect(findDraggablePanels().exists()).toBe(true);
+ expect(findDraggablePanels().length).toEqual(expectedPanelCount);
+ });
+
+ it('is disabled by default', () => {
+ expect(findRearrangeButton().exists()).toBe(false);
+ expect(findEnabledDraggables().length).toBe(0);
+ });
+
+ describe('when rearrange is enabled', () => {
+ beforeEach(done => {
+ wrapper.setProps({ rearrangePanelsAvailable: true });
+ wrapper.vm.$nextTick(done);
+ });
+
+ it('displays rearrange button', () => {
+ expect(findRearrangeButton().exists()).toBe(true);
+ });
+
+ describe('when rearrange button is clicked', () => {
+ const findFirstDraggableRemoveButton = () =>
+ findDraggablePanels()
+ .at(0)
+ .find('.js-draggable-remove');
+
+ beforeEach(done => {
+ findRearrangeButton().vm.$emit('click');
+ wrapper.vm.$nextTick(done);
+ });
+
+ it('it enables draggables', () => {
+ expect(findRearrangeButton().attributes('pressed')).toBeTruthy();
+ expect(findEnabledDraggables()).toEqual(findDraggables());
+ });
+
+ it('metrics can be swapped', done => {
+ const firstDraggable = findDraggables().at(0);
+ const mockMetrics = [...metricsGroupsAPIResponse[1].panels];
+
+ const firstTitle = mockMetrics[0].title;
+ const secondTitle = mockMetrics[1].title;
+
+ // swap two elements and `input` them
+ [mockMetrics[0], mockMetrics[1]] = [mockMetrics[1], mockMetrics[0]];
+ firstDraggable.vm.$emit('input', mockMetrics);
+
+ wrapper.vm.$nextTick(() => {
+ const { panels } = wrapper.vm.dashboard.panel_groups[1];
+
+ expect(panels[1].title).toEqual(firstTitle);
+ expect(panels[0].title).toEqual(secondTitle);
+ done();
+ });
+ });
+
+ it('shows a remove button, which removes a panel', done => {
+ expect(findFirstDraggableRemoveButton().isEmpty()).toBe(false);
+
+ expect(findDraggablePanels().length).toEqual(expectedPanelCount);
+ findFirstDraggableRemoveButton().trigger('click');
+
+ wrapper.vm.$nextTick(() => {
+ expect(findDraggablePanels().length).toEqual(expectedPanelCount - 1);
+ done();
+ });
+ });
+
+ it('it disables draggables when clicked again', done => {
+ findRearrangeButton().vm.$emit('click');
+ wrapper.vm.$nextTick(() => {
+ expect(findRearrangeButton().attributes('pressed')).toBeFalsy();
+ expect(findEnabledDraggables().length).toBe(0);
+ done();
+ });
+ });
+ });
+ });
+ });
+
+ describe('cluster health', () => {
+ beforeEach(done => {
+ mock.onGet(propsData.metricsEndpoint).reply(statusCodes.OK, JSON.stringify({}));
+ createShallowWrapper({ hasMetrics: true });
+
+ // all_dashboards is not defined in health dashboards
+ wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
+ wrapper.vm.$nextTick(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders correctly', () => {
+ expect(wrapper.isVueInstance()).toBe(true);
+ expect(wrapper.exists()).toBe(true);
+ });
+ });
+
+ describe('dashboard edit link', () => {
+ const findEditLink = () => wrapper.find('.js-edit-link');
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
+
+ createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
+
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
+ dashboardGitResponse,
+ );
+ wrapper.vm.$nextTick(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('is not present for the default dashboard', () => {
+ expect(findEditLink().exists()).toBe(false);
+ });
+
+ it('is present for a custom dashboard, and links to its edit_path', done => {
+ const dashboard = dashboardGitResponse[1]; // non-default dashboard
+ const currentDashboard = dashboard.path;
+
+ wrapper.setProps({ currentDashboard });
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(findEditLink().exists()).toBe(true);
+ expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('Dashboard dropdown', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ createMountedWrapper(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
+ dashboardGitResponse,
+ );
+ });
+
+ it('shows the dashboard dropdown', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const dashboardDropdown = wrapper.find('.js-dashboards-dropdown');
+
+ expect(dashboardDropdown.exists()).toBe(true);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('external dashboard link', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ createMountedWrapper(
+ {
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ externalDashboardUrl: '/mockUrl',
+ },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+ });
+
+ it('shows the link', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const externalDashboardButton = wrapper.find('.js-external-dashboard-link');
+
+ expect(externalDashboardButton.exists()).toBe(true);
+ expect(externalDashboardButton.is(GlButton)).toBe(true);
+ expect(externalDashboardButton.text()).toContain('View full dashboard');
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922
+ // eslint-disable-next-line jest/no-disabled-tests
+ describe.skip('link to chart', () => {
+ const currentDashboard = 'TEST_DASHBOARD';
+ localVue.use(GlToast);
+ const link = () => wrapper.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ createShallowWrapper({ hasMetrics: true, currentDashboard }, { attachToDocument: true });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('adds a copy button to the dropdown', () => {
+ expect(link().text()).toContain('Generate link to chart');
+ });
+
+ it('contains a link to the dashboard', () => {
+ expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
+ expect(clipboardText()).toContain(`group=`);
+ expect(clipboardText()).toContain(`title=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ });
+
+ it('undefined parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: undefined });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('null parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: null });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('creates a toast when clicked', () => {
+ jest.spyOn(wrapper.vm.$toast, 'show').and.stub();
+
+ link().vm.$emit('click');
+
+ expect(wrapper.vm.$toast.show).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_time_url_spec.js b/spec/frontend/monitoring/components/dashboard_time_url_spec.js
new file mode 100644
index 00000000000..8dc450cf131
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_time_url_spec.js
@@ -0,0 +1,54 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import createFlash from '~/flash';
+import Dashboard from '~/monitoring/components/dashboard.vue';
+import { createStore } from '~/monitoring/stores';
+import { propsData } from '../init_utils';
+
+const localVue = createLocalVue();
+
+jest.mock('~/flash');
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ getParameterValues: jest.fn().mockReturnValue('<script>alert("XSS")</script>'),
+}));
+
+describe('dashboard invalid url parameters', () => {
+ let store;
+ let wrapper;
+
+ const createMountedWrapper = (props = {}, options = {}) => {
+ wrapper = mount(localVue.extend(Dashboard), {
+ localVue,
+ sync: false,
+ propsData: { ...propsData, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ it('shows an error message if invalid url parameters are passed', done => {
+ createMountedWrapper(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ expect(createFlash).toHaveBeenCalled();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_time_window_spec.js b/spec/frontend/monitoring/components/dashboard_time_window_spec.js
new file mode 100644
index 00000000000..d49af6f84cb
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_time_window_spec.js
@@ -0,0 +1,75 @@
+import { mount, createLocalVue } from '@vue/test-utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import statusCodes from '~/lib/utils/http_status';
+import Dashboard from '~/monitoring/components/dashboard.vue';
+import { createStore } from '~/monitoring/stores';
+import { propsData, setupComponentStore } from '../init_utils';
+import { metricsGroupsAPIResponse, mockApiEndpoint } from '../mock_data';
+
+const localVue = createLocalVue();
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ getParameterValues: jest.fn().mockImplementation(param => {
+ if (param === 'start') return ['2019-10-01T18:27:47.000Z'];
+ if (param === 'end') return ['2019-10-01T18:57:47.000Z'];
+ return [];
+ }),
+ mergeUrlParams: jest.fn().mockReturnValue('#'),
+}));
+
+describe('dashboard time window', () => {
+ let store;
+ let wrapper;
+ let mock;
+
+ const createComponentWrapperMounted = (props = {}, options = {}) => {
+ wrapper = mount(localVue.extend(Dashboard), {
+ localVue,
+ sync: false,
+ propsData: { ...propsData, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ mock.restore();
+ });
+
+ it('shows an error message if invalid url parameters are passed', done => {
+ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
+
+ createComponentWrapperMounted(
+ { hasMetrics: true },
+ { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
+ );
+
+ setupComponentStore(wrapper);
+
+ wrapper.vm
+ .$nextTick()
+ .then(() => {
+ const timeWindowDropdownItems = wrapper
+ .find('.js-time-window-dropdown')
+ .findAll(GlDropdownItem);
+ const activeItem = timeWindowDropdownItems.wrappers.filter(itemWrapper =>
+ itemWrapper.find('.active').exists(),
+ );
+
+ expect(activeItem.length).toBe(1);
+
+ done();
+ })
+ .catch(done.fail);
+ });
+});
diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js
new file mode 100644
index 00000000000..43ca17c3cbc
--- /dev/null
+++ b/spec/frontend/monitoring/components/graph_group_spec.js
@@ -0,0 +1,120 @@
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import GraphGroup from '~/monitoring/components/graph_group.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const localVue = createLocalVue();
+
+describe('Graph group component', () => {
+ let wrapper;
+
+ const findGroup = () => wrapper.find({ ref: 'graph-group' });
+ const findContent = () => wrapper.find({ ref: 'graph-group-content' });
+ const findCaretIcon = () => wrapper.find(Icon);
+
+ const createComponent = propsData => {
+ wrapper = shallowMount(localVue.extend(GraphGroup), {
+ propsData,
+ sync: false,
+ localVue,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('When group is not collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ collapseGroup: false,
+ });
+ });
+
+ it('should show the angle-down caret icon', () => {
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().props('name')).toBe('angle-down');
+ });
+
+ it('should show the angle-right caret icon when the user collapses the group', done => {
+ wrapper.vm.collapse();
+
+ wrapper.vm.$nextTick(() => {
+ expect(findContent().isVisible()).toBe(false);
+ expect(findCaretIcon().props('name')).toBe('angle-right');
+ done();
+ });
+ });
+
+ it('should show the open the group when collapseGroup is set to true', done => {
+ wrapper.setProps({
+ collapseGroup: true,
+ });
+
+ wrapper.vm.$nextTick(() => {
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().props('name')).toBe('angle-down');
+ done();
+ });
+ });
+
+ describe('When group is collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ collapseGroup: true,
+ });
+ });
+
+ it('should show the angle-down caret icon when collapseGroup is true', () => {
+ expect(wrapper.vm.caretIcon).toBe('angle-right');
+ });
+
+ it('should show the angle-right caret icon when collapseGroup is false', () => {
+ wrapper.vm.collapse();
+
+ expect(wrapper.vm.caretIcon).toBe('angle-down');
+ });
+ });
+
+ describe('When groups can not be collapsed', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ showPanels: false,
+ collapseGroup: false,
+ });
+ });
+
+ it('should not have a container when showPanels is false', () => {
+ expect(findGroup().exists()).toBe(false);
+ expect(findContent().exists()).toBe(true);
+ });
+ });
+
+ describe('When group does not show a panel heading', () => {
+ beforeEach(() => {
+ createComponent({
+ name: 'panel',
+ showPanels: false,
+ collapseGroup: false,
+ });
+ });
+
+ it('should collapse the panel content', () => {
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().exists()).toBe(false);
+ });
+
+ it('should show the panel content when clicked', done => {
+ wrapper.vm.collapse();
+
+ wrapper.vm.$nextTick(() => {
+ expect(findContent().isVisible()).toBe(true);
+ expect(findCaretIcon().exists()).toBe(false);
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/init_utils.js b/spec/frontend/monitoring/init_utils.js
new file mode 100644
index 00000000000..10db8b902b5
--- /dev/null
+++ b/spec/frontend/monitoring/init_utils.js
@@ -0,0 +1,56 @@
+import * as types from '~/monitoring/stores/mutation_types';
+import {
+ metricsGroupsAPIResponse,
+ mockedEmptyResult,
+ mockedQueryResultPayload,
+ mockedQueryResultPayloadCoresTotal,
+ mockApiEndpoint,
+ environmentData,
+} from './mock_data';
+
+export const propsData = {
+ hasMetrics: false,
+ documentationPath: '/path/to/docs',
+ settingsPath: '/path/to/settings',
+ clustersPath: '/path/to/clusters',
+ tagsPath: '/path/to/tags',
+ projectPath: '/path/to/project',
+ metricsEndpoint: mockApiEndpoint,
+ deploymentsEndpoint: null,
+ emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
+ emptyLoadingSvgPath: '/path/to/loading.svg',
+ emptyNoDataSvgPath: '/path/to/no-data.svg',
+ emptyNoDataSmallSvgPath: '/path/to/no-data-small.svg',
+ emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+ environmentsEndpoint: '/root/hello-prometheus/environments/35',
+ currentEnvironmentName: 'production',
+ customMetricsAvailable: false,
+ customMetricsPath: '',
+ validateQueryPath: '',
+};
+
+export const setupComponentStore = wrapper => {
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ metricsGroupsAPIResponse,
+ );
+
+ // Load 3 panels to the dashboard, one with an empty result
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
+ mockedEmptyResult,
+ );
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
+ mockedQueryResultPayload,
+ );
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
+ mockedQueryResultPayloadCoresTotal,
+ );
+
+ wrapper.vm.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+};
diff --git a/spec/frontend/monitoring/shared/prometheus_header_spec.js b/spec/frontend/monitoring/shared/prometheus_header_spec.js
new file mode 100644
index 00000000000..b216bfb72d8
--- /dev/null
+++ b/spec/frontend/monitoring/shared/prometheus_header_spec.js
@@ -0,0 +1,26 @@
+import { shallowMount } from '@vue/test-utils';
+import PrometheusHeader from '~/monitoring/components/shared/prometheus_header.vue';
+
+describe('Prometheus Header component', () => {
+ let prometheusHeader;
+
+ beforeEach(() => {
+ prometheusHeader = shallowMount(PrometheusHeader, {
+ propsData: {
+ graphTitle: 'graph header',
+ },
+ });
+ });
+
+ afterEach(() => {
+ prometheusHeader.destroy();
+ });
+
+ describe('Prometheus header component', () => {
+ it('should show a title', () => {
+ const title = prometheusHeader.find({ ref: 'title' }).text();
+
+ expect(title).toBe('graph header');
+ });
+ });
+});