Commit 6553776f authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'jivanvl-update-dashboard-specs-jest' into 'master'

Move non EE dashboard specs to jest

See merge request gitlab-org/gitlab!21143
parents 9e6edfd3 3f702298
...@@ -8,7 +8,7 @@ import { ...@@ -8,7 +8,7 @@ import {
mockedQueryResultPayload, mockedQueryResultPayload,
environmentData, environmentData,
} from 'spec/monitoring/mock_data'; } from 'spec/monitoring/mock_data';
import propsData from 'spec/monitoring/components/dashboard_spec'; import propsData from 'spec/monitoring/components/dashboard_resize_spec';
import CustomMetricsFormFields from 'ee/custom_metrics/components/custom_metrics_form_fields.vue'; import CustomMetricsFormFields from 'ee/custom_metrics/components/custom_metrics_form_fields.vue';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
......
import Vue from 'vue'; import { shallowMount, createLocalVue, mount } from '@vue/test-utils';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { GlDropdownItem, GlButton, GlToast } from '@gitlab/ui';
import { GlToast } from '@gitlab/ui';
import VueDraggable from 'vuedraggable'; import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue'; import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import { metricStates } from '~/monitoring/constants'; 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 GroupEmptyState from '~/monitoring/components/group_empty_state.vue';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores'; import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils'; import * as types from '~/monitoring/stores/mutation_types';
import * as monitoringUtils from '~/monitoring/utils';
import { setupComponentStore, propsData } from '../init_utils';
import { import {
metricsGroupsAPIResponse, metricsGroupsAPIResponse,
mockedEmptyResult,
mockedQueryResultPayload, mockedQueryResultPayload,
mockedQueryResultPayloadCoresTotal,
mockApiEndpoint, mockApiEndpoint,
environmentData, environmentData,
dashboardGitResponse, dashboardGitResponse,
} from '../mock_data'; } from '../mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
const propsData = { const expectedPanelCount = 2;
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: '',
};
const resetSpy = spy => {
if (spy) {
spy.calls.reset();
}
};
let expectedPanelCount;
function setupComponentStore(component) {
// Load 2 panel groups
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse,
);
// Load 3 panels to the dashboard, one with an empty result
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedEmptyResult,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedQueryResultPayload,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedQueryResultPayloadCoresTotal,
);
expectedPanelCount = 2;
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
}
describe('Dashboard', () => { describe('Dashboard', () => {
let DashboardComponent; let DashboardComponent;
let mock;
let store; let store;
let component;
let wrapper; let wrapper;
let mock;
const createComponentWrapper = (props = {}, options = {}) => { const createShallowWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(localVue.extend(DashboardComponent), { wrapper = shallowMount(localVue.extend(DashboardComponent), {
localVue, localVue,
sync: false, sync: false,
...@@ -95,21 +39,23 @@ describe('Dashboard', () => { ...@@ -95,21 +39,23 @@ describe('Dashboard', () => {
}); });
}; };
beforeEach(() => { const createMountedWrapper = (props = {}, options = {}) => {
setFixtures(` wrapper = mount(localVue.extend(DashboardComponent), {
<div class="prometheus-graphs"></div> localVue,
<div class="layout-page"></div> sync: false,
`); propsData: { ...propsData, ...props },
store,
...options,
});
};
beforeEach(() => {
store = createStore(); store = createStore();
mock = new MockAdapter(axios);
DashboardComponent = localVue.extend(Dashboard); DashboardComponent = localVue.extend(Dashboard);
mock = new MockAdapter(axios);
}); });
afterEach(() => { afterEach(() => {
if (component) {
component.$destroy();
}
if (wrapper) { if (wrapper) {
wrapper.destroy(); wrapper.destroy();
} }
...@@ -118,37 +64,26 @@ describe('Dashboard', () => { ...@@ -118,37 +64,26 @@ describe('Dashboard', () => {
describe('no metrics are available yet', () => { describe('no metrics are available yet', () => {
beforeEach(() => { beforeEach(() => {
component = new DashboardComponent({ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData }, createShallowWrapper({}, { attachToDocument: true });
store,
});
}); });
it('shows a getting started empty state when no metrics are present', () => { afterEach(() => {
expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); wrapper.destroy();
expect(component.emptyState).toEqual('gettingStarted');
}); });
it('shows the environment selector', () => { it('shows the environment selector', () => {
expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
}); });
}); });
describe('no data found', () => { describe('no data found', () => {
it('shows the environment selector dropdown', () => {
createComponentWrapper();
expect(wrapper.find('.js-environments-dropdown').exists()).toBeTruthy();
});
});
describe('cluster health', () => {
beforeEach(done => { beforeEach(done => {
createComponentWrapper({ hasMetrics: true }); mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
createShallowWrapper({}, { attachToDocument: true });
// all_dashboards is not defined in health dashboards
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
...@@ -156,86 +91,109 @@ describe('Dashboard', () => { ...@@ -156,86 +91,109 @@ describe('Dashboard', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('renders correctly', () => { it('shows the environment selector dropdown', () => {
expect(wrapper.isVueInstance()).toBe(true); expect(wrapper.vm.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
expect(wrapper.exists()).toBe(true);
}); });
}); });
describe('requests information to the server', () => { describe('request information to the server', () => {
let spy;
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
}); });
afterEach(() => {
resetSpy(spy);
});
it('shows up a loading state', done => { it('shows up a loading state', done => {
component = new DashboardComponent({ createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true }, wrapper.vm
store, .$nextTick()
}); .then(() => {
expect(wrapper.vm.emptyState).toEqual('loading');
Vue.nextTick(() => {
expect(component.emptyState).toEqual('loading');
done(); done();
}); })
.catch(done.fail);
}); });
it('hides the group panels when showPanels is false', done => { it('hides the group panels when showPanels is false', done => {
component = new DashboardComponent({ createMountedWrapper(
el: document.querySelector('.prometheus-graphs'), { hasMetrics: true, showPanels: false },
propsData: { { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
...propsData, );
hasMetrics: true,
showPanels: false, setupComponentStore(wrapper);
},
store, 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);
}); });
setupComponentStore(component); 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,
);
Vue.nextTick() wrapper.vm
.$nextTick()
.then(() => { .then(() => {
expect(component.showEmptyState).toEqual(false); expect(store.dispatch).toHaveBeenCalled();
expect(component.$el.querySelector('.prometheus-panel')).toEqual(null); expect(getTimeDiffSpy).toHaveBeenCalled();
expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
}); });
});
describe('when all the requests have been commited by the store', () => { describe('when all requests have been commited by the store', () => {
beforeEach(() => { beforeEach(() => {
component = new DashboardComponent({ mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
el: document.querySelector('.prometheus-graphs'),
propsData: { createMountedWrapper(
...propsData, { hasMetrics: true },
hasMetrics: true, { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
}, );
store,
setupComponentStore(wrapper);
}); });
setupComponentStore(component); afterEach(() => {
wrapper.destroy();
}); });
it('renders the environments dropdown with a number of environments', done => { it('renders the environments dropdown with a number of environments', done => {
Vue.nextTick() wrapper.vm
.$nextTick()
.then(() => { .then(() => {
const dropdownMenuEnvironments = component.$el.querySelectorAll( const environmentDropdownItems = wrapper
'.js-environments-dropdown .dropdown-item', .find('.js-environments-dropdown')
); .findAll(GlDropdownItem);
expect(component.environments.length).toEqual(environmentData.length); expect(wrapper.vm.environments.length).toEqual(environmentData.length);
expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); expect(environmentDropdownItems.length).toEqual(wrapper.vm.environments.length);
Array.from(dropdownMenuEnvironments).forEach((value, index) => { environmentDropdownItems.wrappers.forEach((itemWrapper, index) => {
if (environmentData[index].metrics_path) { const anchorEl = itemWrapper.find('a');
expect(value).toHaveAttr('href', environmentData[index].metrics_path); if (anchorEl.exists() && environmentData[index].metrics_path) {
const href = anchorEl.attributes('href');
expect(href).toBe(environmentData[index].metrics_path);
} }
}); });
...@@ -245,13 +203,17 @@ describe('Dashboard', () => { ...@@ -245,13 +203,17 @@ describe('Dashboard', () => {
}); });
it('renders the environments dropdown with a single active element', done => { it('renders the environments dropdown with a single active element', done => {
Vue.nextTick() wrapper.vm
.$nextTick()
.then(() => { .then(() => {
const dropdownItems = component.$el.querySelectorAll( const environmentDropdownItems = wrapper
'.js-environments-dropdown .dropdown-item.active', .find('.js-environments-dropdown')
.findAll(GlDropdownItem);
const activeItem = environmentDropdownItems.wrappers.filter(itemWrapper =>
itemWrapper.find('.active').exists(),
); );
expect(dropdownItems.length).toEqual(1); expect(activeItem.length).toBe(1);
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
...@@ -259,151 +221,56 @@ describe('Dashboard', () => { ...@@ -259,151 +221,56 @@ describe('Dashboard', () => {
}); });
it('hides the environments dropdown list when there is no environments', done => { it('hides the environments dropdown list when there is no environments', done => {
component = new DashboardComponent({ createMountedWrapper(
el: document.querySelector('.prometheus-graphs'), { hasMetrics: true },
propsData: { { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
...propsData, );
hasMetrics: true,
},
store,
});
component.$store.commit( wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse, metricsGroupsAPIResponse,
); );
component.$store.commit( wrapper.vm.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedQueryResultPayload, mockedQueryResultPayload,
); );
Vue.nextTick() wrapper.vm
.$nextTick()
.then(() => { .then(() => {
const dropdownMenuEnvironments = component.$el.querySelectorAll( const environmentDropdownItems = wrapper
'.js-environments-dropdown .dropdown-item', .find('.js-environments-dropdown')
); .findAll(GlDropdownItem);
expect(dropdownMenuEnvironments.length).toEqual(0); expect(environmentDropdownItems.length).toEqual(0);
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
}); });
it('renders the datetimepicker dropdown', done => { it('renders the datetimepicker dropdown', done => {
component = new DashboardComponent({ createMountedWrapper(
el: document.querySelector('.prometheus-graphs'), { hasMetrics: true },
propsData: { { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
...propsData,
hasMetrics: true,
showPanels: false,
},
store,
});
setupComponentStore(component);
Vue.nextTick()
.then(() => {
expect(component.$el.querySelector('.js-time-window-dropdown')).not.toBeNull();
done();
})
.catch(done.fail);
});
it('fetches the metrics data with proper time window', done => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: false,
},
store,
});
spyOn(component.$store, 'dispatch').and.stub();
const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough();
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
); );
component.$mount(); setupComponentStore(wrapper);
Vue.nextTick()
.then(() => {
expect(component.$store.dispatch).toHaveBeenCalled();
expect(getTimeDiffSpy).toHaveBeenCalled();
done();
})
.catch(done.fail);
});
it('shows a specific time window selected from the url params', done => {
const start = '2019-10-01T18:27:47.000Z';
const end = '2019-10-01T18:57:47.000Z';
spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({
start,
end,
});
spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => {
if (param === 'start') return [start];
if (param === 'end') return [end];
return [];
});
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true },
store,
sync: false,
});
setupComponentStore(component);
Vue.nextTick() wrapper.vm
.$nextTick()
.then(() => { .then(() => {
const selectedTimeWindow = component.$el.querySelector( expect(wrapper.find(DateTimePicker).exists()).toBe(true);
'.js-time-window-dropdown .active',
);
expect(selectedTimeWindow.textContent.trim()).toEqual('30 minutes');
done(); done();
}) })
.catch(done.fail); .catch(done.fail);
}); });
it('shows an error message if invalid url parameters are passed', done => {
spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([
'<script>alert("XSS")</script>',
]);
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: { ...propsData, hasMetrics: true },
store,
});
spy = spyOn(component, 'showInvalidDateError');
component.$mount();
component.$nextTick(() => {
expect(component.showInvalidDateError).toHaveBeenCalled();
done();
});
});
});
describe('when one of the metrics is missing', () => { describe('when one of the metrics is missing', () => {
beforeEach(() => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
beforeEach(done => { createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
createComponentWrapper({ hasMetrics: true }); setupComponentStore(wrapper);
setupComponentStore(wrapper.vm);
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
...@@ -432,13 +299,13 @@ describe('Dashboard', () => { ...@@ -432,13 +299,13 @@ describe('Dashboard', () => {
const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); const findRearrangeButton = () => wrapper.find('.js-rearrange-button');
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
}); });
beforeEach(done => { beforeEach(done => {
createComponentWrapper({ hasMetrics: true }, { attachToDocument: true }); createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
setupComponentStore(wrapper.vm); setupComponentStore(wrapper);
wrapper.vm.$nextTick(done); wrapper.vm.$nextTick(done);
}); });
...@@ -527,110 +394,23 @@ describe('Dashboard', () => { ...@@ -527,110 +394,23 @@ describe('Dashboard', () => {
}); });
}); });
// https://gitlab.com/gitlab-org/gitlab-ce/issues/66922 describe('cluster health', () => {
// eslint-disable-next-line jasmine/no-disabled-tests
xdescribe('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 => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(propsData.metricsEndpoint).reply(statusCodes.OK, JSON.stringify({}));
createShallowWrapper({ hasMetrics: true });
createComponentWrapper({ hasMetrics: true, currentDashboard }, { attachToDocument: true });
setTimeout(done); // all_dashboards is not defined in health dashboards
wrapper.vm.$store.commit(`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, undefined);
wrapper.vm.$nextTick(done);
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('adds a copy button to the dropdown', () => { it('renders correctly', () => {
expect(link().text()).toContain('Generate link to chart'); expect(wrapper.isVueInstance()).toBe(true);
}); expect(wrapper.exists()).toBe(true);
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', () => {
spyOn(wrapper.vm.$toast, 'show').and.stub();
link().vm.$emit('click');
expect(wrapper.vm.$toast.show).toHaveBeenCalled();
});
});
describe('responds to window resizes', () => {
let promPanel;
let promGroup;
let panelToggle;
let chart;
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: true,
},
store,
});
setupComponentStore(component);
return Vue.nextTick().then(() => {
[, promPanel] = component.$el.querySelectorAll('.prometheus-panel');
promGroup = promPanel.querySelector('.prometheus-graph-group');
panelToggle = promPanel.querySelector('.js-graph-group-toggle');
chart = promGroup.querySelector('.position-relative svg');
});
});
it('setting chart size to zero when panel group is hidden', () => {
expect(promGroup.style.display).toBe('');
expect(chart.clientWidth).toBeGreaterThan(0);
panelToggle.click();
return Vue.nextTick().then(() => {
expect(promGroup.style.display).toBe('none');
expect(chart.clientWidth).toBe(0);
promPanel.style.width = '500px';
});
});
it('expanding chart panel group after resize displays chart', () => {
panelToggle.click();
expect(chart.clientWidth).toBeGreaterThan(0);
}); });
}); });
...@@ -638,9 +418,9 @@ describe('Dashboard', () => { ...@@ -638,9 +418,9 @@ describe('Dashboard', () => {
const findEditLink = () => wrapper.find('.js-edit-link'); const findEditLink = () => wrapper.find('.js-edit-link');
beforeEach(done => { beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(statusCodes.OK, metricsGroupsAPIResponse);
createComponentWrapper({ hasMetrics: true }, { attachToDocument: true }); createShallowWrapper({ hasMetrics: true }, { attachToDocument: true });
wrapper.vm.$store.commit( wrapper.vm.$store.commit(
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
...@@ -662,11 +442,42 @@ describe('Dashboard', () => { ...@@ -662,11 +442,42 @@ describe('Dashboard', () => {
const currentDashboard = dashboard.path; const currentDashboard = dashboard.path;
wrapper.setProps({ currentDashboard }); wrapper.setProps({ currentDashboard });
wrapper.vm.$nextTick(() => { wrapper.vm
.$nextTick()
.then(() => {
expect(findEditLink().exists()).toBe(true); expect(findEditLink().exists()).toBe(true);
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path); expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
done(); 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);
}); });
}); });
...@@ -674,56 +485,89 @@ describe('Dashboard', () => { ...@@ -674,56 +485,89 @@ describe('Dashboard', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
component = new DashboardComponent({ createMountedWrapper(
el: document.querySelector('.prometheus-graphs'), {
propsData: {
...propsData,
hasMetrics: true, hasMetrics: true,
showPanels: false, showPanels: false,
showTimeWindowDropdown: false, showTimeWindowDropdown: false,
externalDashboardUrl: '/mockUrl', externalDashboardUrl: '/mockUrl',
}, },
store, { attachToDocument: true, stubs: ['graph-group', 'panel-type'] },
}); );
}); });
it('shows the link', done => { it('shows the link', done => {
setTimeout(() => { wrapper.vm
expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain( .$nextTick()
'View full dashboard', .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(); done();
}); })
.catch(done.fail);
}); });
}); });
describe('Dashboard dropdown', () => { // https://gitlab.com/gitlab-org/gitlab-ce/issues/66922
beforeEach(() => { // 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); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
component = new DashboardComponent({ createShallowWrapper({ hasMetrics: true, currentDashboard }, { attachToDocument: true });
el: document.querySelector('.prometheus-graphs'),
propsData: { setTimeout(done);
...propsData,
hasMetrics: true,
showPanels: false,
},
store,
}); });
component.$store.commit( afterEach(() => {
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, wrapper.destroy();
dashboardGitResponse,
);
}); });
it('shows the dashboard dropdown', done => { it('adds a copy button to the dropdown', () => {
setTimeout(() => { expect(link().text()).toContain('Generate link to chart');
const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown'); });
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 });
expect(dashboardDropdown).not.toEqual(null); 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(); 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();
});
}); });
}); });
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);
});
});
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);
});
});
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,
);
};
import Vue from 'vue';
import { createLocalVue } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
import {
metricsGroupsAPIResponse,
mockedEmptyResult,
mockedQueryResultPayload,
mockedQueryResultPayloadCoresTotal,
mockApiEndpoint,
environmentData,
} from '../mock_data';
const localVue = createLocalVue();
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: '',
};
function setupComponentStore(component) {
// Load 2 panel groups
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
metricsGroupsAPIResponse,
);
// Load 3 panels to the dashboard, one with an empty result
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedEmptyResult,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedQueryResultPayload,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`,
mockedQueryResultPayloadCoresTotal,
);
component.$store.commit(
`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
environmentData,
);
}
describe('Dashboard', () => {
let DashboardComponent;
let mock;
let store;
let component;
let wrapper;
beforeEach(() => {
setFixtures(`
<div class="prometheus-graphs"></div>
<div class="layout-page"></div>
`);
store = createStore();
mock = new MockAdapter(axios);
DashboardComponent = localVue.extend(Dashboard);
});
afterEach(() => {
if (component) {
component.$destroy();
}
if (wrapper) {
wrapper.destroy();
}
mock.restore();
});
describe('responds to window resizes', () => {
let promPanel;
let promGroup;
let panelToggle;
let chart;
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
...propsData,
hasMetrics: true,
showPanels: true,
},
store,
});
setupComponentStore(component);
return Vue.nextTick().then(() => {
[, promPanel] = component.$el.querySelectorAll('.prometheus-panel');
promGroup = promPanel.querySelector('.prometheus-graph-group');
panelToggle = promPanel.querySelector('.js-graph-group-toggle');
chart = promGroup.querySelector('.position-relative svg');
});
});
it('setting chart size to zero when panel group is hidden', () => {
expect(promGroup.style.display).toBe('');
expect(chart.clientWidth).toBeGreaterThan(0);
panelToggle.click();
return Vue.nextTick().then(() => {
expect(promGroup.style.display).toBe('none');
expect(chart.clientWidth).toBe(0);
promPanel.style.width = '500px';
});
});
it('expanding chart panel group after resize displays chart', () => {
panelToggle.click();
expect(chart.clientWidth).toBeGreaterThan(0);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment