summaryrefslogtreecommitdiff
path: root/spec/frontend
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-26 21:06:38 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-26 21:06:38 +0000
commit59a349817c0869e8e0d750884b34256ca4faefca (patch)
treeabb8297dd313e550184f1933a9dc7795a3749c0a /spec/frontend
parent581c10e344d85729e77fce78513819d159289dc5 (diff)
downloadgitlab-ce-59a349817c0869e8e0d750884b34256ca4faefca.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/cycle_analytics/limit_warning_component_spec.js43
-rw-r--r--spec/frontend/monitoring/store/actions_spec.js199
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js86
3 files changed, 185 insertions, 143 deletions
diff --git a/spec/frontend/cycle_analytics/limit_warning_component_spec.js b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
index 13e9fe00a00..5041ebe1a8b 100644
--- a/spec/frontend/cycle_analytics/limit_warning_component_spec.js
+++ b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
@@ -1,42 +1,43 @@
import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import Translate from '~/vue_shared/translate';
-import limitWarningComp from '~/cycle_analytics/components/limit_warning_component.vue';
+import LimitWarningComponent from '~/cycle_analytics/components/limit_warning_component.vue';
Vue.use(Translate);
+const createComponent = props =>
+ shallowMount(LimitWarningComponent, {
+ propsData: {
+ ...props,
+ },
+ sync: false,
+ attachToDocument: true,
+ });
+
describe('Limit warning component', () => {
let component;
- let LimitWarningComponent;
beforeEach(() => {
- LimitWarningComponent = Vue.extend(limitWarningComp);
+ component = null;
+ });
+
+ afterEach(() => {
+ component.destroy();
});
it('should not render if count is not exactly than 50', () => {
- component = new LimitWarningComponent({
- propsData: {
- count: 5,
- },
- }).$mount();
+ component = createComponent({ count: 5 });
- expect(component.$el.textContent.trim()).toBe('');
+ expect(component.text().trim()).toBe('');
- component = new LimitWarningComponent({
- propsData: {
- count: 55,
- },
- }).$mount();
+ component = createComponent({ count: 55 });
- expect(component.$el.textContent.trim()).toBe('');
+ expect(component.text().trim()).toBe('');
});
it('should render if count is exactly 50', () => {
- component = new LimitWarningComponent({
- propsData: {
- count: 50,
- },
- }).$mount();
+ component = createComponent({ count: 50 });
- expect(component.$el.textContent.trim()).toBe('Showing 50 events');
+ expect(component.text().trim()).toBe('Showing 50 events');
});
});
diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js
index 467e0445a90..83dac6552a2 100644
--- a/spec/frontend/monitoring/store/actions_spec.js
+++ b/spec/frontend/monitoring/store/actions_spec.js
@@ -1,15 +1,14 @@
import MockAdapter from 'axios-mock-adapter';
import Tracking from '~/tracking';
-import { TEST_HOST } from 'helpers/test_constants';
import testAction from 'helpers/vuex_action_helper';
import axios from '~/lib/utils/axios_utils';
import statusCodes from '~/lib/utils/http_status';
import { backOff } from '~/lib/utils/common_utils';
+import createFlash from '~/flash';
import store from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import {
- backOffRequest,
fetchDashboard,
receiveMetricsDashboardSuccess,
receiveMetricsDashboardFailure,
@@ -17,7 +16,6 @@ import {
fetchEnvironmentsData,
fetchPrometheusMetrics,
fetchPrometheusMetric,
- requestMetricsData,
setEndpoints,
setGettingStartedEmptyState,
} from '~/monitoring/stores/actions';
@@ -31,6 +29,7 @@ import {
} from '../mock_data';
jest.mock('~/lib/utils/common_utils');
+jest.mock('~/flash');
const resetStore = str => {
str.replaceState({
@@ -40,71 +39,36 @@ const resetStore = str => {
});
};
-const MAX_REQUESTS = 3;
-
-describe('Monitoring store helpers', () => {
+describe('Monitoring store actions', () => {
let mock;
-
- // Mock underlying `backOff` function to remove in-built delay.
- backOff.mockImplementation(
- callback =>
- new Promise((resolve, reject) => {
- const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
- const next = () => callback(next, stop);
- callback(next, stop);
- }),
- );
-
beforeEach(() => {
mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('backOffRequest', () => {
- it('returns immediately when recieving a 200 status code', () => {
- mock.onGet(TEST_HOST).reply(200);
-
- return backOffRequest(() => axios.get(TEST_HOST)).then(() => {
- expect(mock.history.get.length).toBe(1);
- });
- });
- it(`repeats the network call ${MAX_REQUESTS} times when receiving a 204 response`, done => {
- mock.onGet(TEST_HOST).reply(statusCodes.NO_CONTENT, {});
+ // Mock `backOff` function to remove exponential algorithm delay.
+ jest.useFakeTimers();
- backOffRequest(() => axios.get(TEST_HOST))
- .then(done.fail)
- .catch(() => {
- expect(mock.history.get.length).toBe(MAX_REQUESTS);
- done();
+ backOff.mockImplementation(callback => {
+ const q = new Promise((resolve, reject) => {
+ const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
+ const next = () => callback(next, stop);
+ // Define a timeout based on a mock timer
+ setTimeout(() => {
+ callback(next, stop);
});
+ });
+ // Run all resolved promises in chain
+ jest.runOnlyPendingTimers();
+ return q;
});
});
-});
-
-describe('Monitoring store actions', () => {
- let mock;
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
afterEach(() => {
resetStore(store);
- mock.restore();
- });
- describe('requestMetricsData', () => {
- it('sets emptyState to loading', () => {
- const commit = jest.fn();
- const { state } = store;
- requestMetricsData({
- state,
- commit,
- });
- expect(commit).toHaveBeenCalledWith(types.REQUEST_METRICS_DATA);
- });
+ mock.reset();
+
+ backOff.mockReset();
+ createFlash.mockReset();
});
+
describe('fetchDeploymentsData', () => {
it('commits RECEIVE_DEPLOYMENTS_DATA_SUCCESS on error', done => {
const dispatch = jest.fn();
@@ -362,17 +326,11 @@ describe('Monitoring store actions', () => {
it('commits empty state when state.groups is empty', done => {
const state = storeState();
const params = {};
- fetchPrometheusMetrics(
- {
- state,
- commit,
- dispatch,
- },
- params,
- )
+ fetchPrometheusMetrics({ state, commit, dispatch }, params)
.then(() => {
expect(commit).toHaveBeenCalledWith(types.SET_NO_DATA_EMPTY_STATE);
expect(dispatch).not.toHaveBeenCalled();
+ expect(createFlash).not.toHaveBeenCalled();
done();
})
.catch(done.fail);
@@ -382,20 +340,42 @@ describe('Monitoring store actions', () => {
const state = storeState();
state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups;
const metric = state.dashboard.panel_groups[0].panels[0].metrics[0];
- fetchPrometheusMetrics(
- {
- state,
- commit,
- dispatch,
- },
- params,
- )
+ fetchPrometheusMetrics({ state, commit, dispatch }, params)
+ .then(() => {
+ expect(dispatch).toHaveBeenCalledTimes(3);
+ expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
+ metric,
+ params,
+ });
+
+ expect(createFlash).not.toHaveBeenCalled();
+
+ done();
+ })
+ .catch(done.fail);
+ done();
+ });
+
+ it('dispatches fetchPrometheusMetric for each panel query, handles an error', done => {
+ const params = {};
+ const state = storeState();
+ state.dashboard.panel_groups = metricsDashboardResponse.dashboard.panel_groups;
+ const metric = state.dashboard.panel_groups[0].panels[0].metrics[0];
+
+ // Mock having one out of three metrics failing
+ dispatch.mockRejectedValueOnce(new Error('Error fetching this metric'));
+ dispatch.mockResolvedValue();
+
+ fetchPrometheusMetrics({ state, commit, dispatch }, params)
.then(() => {
expect(dispatch).toHaveBeenCalledTimes(3);
expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetric', {
metric,
params,
});
+
+ expect(createFlash).toHaveBeenCalledTimes(1);
+
done();
})
.catch(done.fail);
@@ -403,28 +383,75 @@ describe('Monitoring store actions', () => {
});
});
describe('fetchPrometheusMetric', () => {
- it('commits prometheus query result', done => {
- const commit = jest.fn();
- const params = {
- start: '2019-08-06T12:40:02.184Z',
- end: '2019-08-06T20:40:02.184Z',
- };
- const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
- const state = storeState();
- const data = metricsGroupsAPIResponse[0].panels[0].metrics[0];
- const response = {
- data,
- };
- mock.onGet('http://test').reply(200, response);
+ const params = {
+ start: '2019-08-06T12:40:02.184Z',
+ end: '2019-08-06T20:40:02.184Z',
+ };
+ let commit;
+ let metric;
+ let state;
+ let data;
+
+ beforeEach(() => {
+ commit = jest.fn();
+ state = storeState();
+ [metric] = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics;
+ [data] = metricsGroupsAPIResponse[0].panels[0].metrics;
+ });
+
+ it('commits result', done => {
+ mock.onGet('http://test').reply(200, { data }); // One attempt
+
fetchPrometheusMetric({ state, commit }, { metric, params })
.then(() => {
expect(commit).toHaveBeenCalledWith(types.SET_QUERY_RESULT, {
metricId: metric.metric_id,
result: data.result,
});
+
+ expect(mock.history.get).toHaveLength(1);
done();
})
.catch(done.fail);
});
+
+ it('commits result, when waiting for results', done => {
+ // Mock multiple attempts while the cache is filling up
+ mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
+ mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
+ mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
+ mock.onGet('http://test').reply(200, { data }); // 4th attempt
+
+ const fetch = fetchPrometheusMetric({ state, commit }, { metric, params });
+
+ fetch
+ .then(() => {
+ expect(commit).toHaveBeenCalledWith(types.SET_QUERY_RESULT, {
+ metricId: metric.metric_id,
+ result: data.result,
+ });
+ expect(mock.history.get).toHaveLength(4);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('commits failure, when waiting for results and getting a server error', done => {
+ // Mock multiple attempts while the cache is filling up and fails
+ mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
+ mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
+ mock.onGet('http://test').replyOnce(statusCodes.NO_CONTENT);
+ mock.onGet('http://test').reply(500); // 4th attempt
+
+ fetchPrometheusMetric({ state, commit }, { metric, params })
+ .then(() => {
+ done.fail();
+ })
+ .catch(() => {
+ expect(commit).not.toHaveBeenCalled();
+ expect(mock.history.get).toHaveLength(4);
+ done();
+ });
+ });
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index 1c4247fb5f0..1014fbf0308 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -1,18 +1,35 @@
-import Vue from 'vue';
+import { shallowMount } from '@vue/test-utils';
import $ from 'jquery';
-import headerComponent from '~/vue_shared/components/markdown/header.vue';
+import HeaderComponent from '~/vue_shared/components/markdown/header.vue';
+import ToolbarButton from '~/vue_shared/components/markdown/toolbar_button.vue';
describe('Markdown field header component', () => {
- let vm;
+ let wrapper;
- beforeEach(() => {
- const Component = Vue.extend(headerComponent);
-
- vm = new Component({
+ const createWrapper = props => {
+ wrapper = shallowMount(HeaderComponent, {
propsData: {
previewMarkdown: false,
+ ...props,
},
- }).$mount();
+ sync: false,
+ attachToDocument: true,
+ });
+ };
+
+ const findToolbarButtons = () => wrapper.findAll(ToolbarButton);
+ const findToolbarButtonByProp = (prop, value) =>
+ findToolbarButtons()
+ .filter(button => button.props(prop) === value)
+ .at(0);
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
});
it('renders markdown header buttons', () => {
@@ -29,75 +46,72 @@ describe('Markdown field header component', () => {
'Add a table',
'Go full screen',
];
- const elements = vm.$el.querySelectorAll('.toolbar-btn');
+ const elements = findToolbarButtons();
- elements.forEach((buttonEl, index) => {
- expect(buttonEl.getAttribute('data-original-title')).toBe(buttons[index]);
+ elements.wrappers.forEach((buttonEl, index) => {
+ expect(buttonEl.props('buttonTitle')).toBe(buttons[index]);
});
});
it('renders `write` link as active when previewMarkdown is false', () => {
- expect(vm.$el.querySelector('li:nth-child(1)').classList.contains('active')).toBeTruthy();
+ expect(wrapper.find('li:nth-child(1)').classes()).toContain('active');
});
it('renders `preview` link as active when previewMarkdown is true', () => {
- vm.previewMarkdown = true;
+ createWrapper({ previewMarkdown: true });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('li:nth-child(2)').classList.contains('active')).toBeTruthy();
- });
+ expect(wrapper.find('li:nth-child(2)').classes()).toContain('active');
});
it('emits toggle markdown event when clicking preview', () => {
- jest.spyOn(vm, '$emit').mockImplementation();
-
- vm.$el.querySelector('.js-preview-link').click();
+ wrapper.find('.js-preview-link').trigger('click');
- expect(vm.$emit).toHaveBeenCalledWith('preview-markdown');
+ expect(wrapper.emitted('preview-markdown').length).toEqual(1);
- vm.$el.querySelector('.js-write-link').click();
+ wrapper.find('.js-write-link').trigger('click');
- expect(vm.$emit).toHaveBeenCalledWith('write-markdown');
+ expect(wrapper.emitted('write-markdown').length).toEqual(1);
});
it('does not emit toggle markdown event when triggered from another form', () => {
- jest.spyOn(vm, '$emit').mockImplementation();
-
$(document).triggerHandler('markdown-preview:show', [
$(
'<form><div class="js-vue-markdown-field"><textarea class="markdown-area"></textarea></div></form>',
),
]);
- expect(vm.$emit).not.toHaveBeenCalled();
+ expect(wrapper.emitted('preview-markdown')).toBeFalsy();
+ expect(wrapper.emitted('write-markdown')).toBeFalsy();
});
it('blurs preview link after click', () => {
- const link = vm.$el.querySelector('li:nth-child(2) button');
+ const link = wrapper.find('li:nth-child(2) button');
jest.spyOn(HTMLElement.prototype, 'blur').mockImplementation();
- link.click();
+ link.trigger('click');
- expect(link.blur).toHaveBeenCalled();
+ expect(link.element.blur).toHaveBeenCalled();
});
it('renders markdown table template', () => {
- expect(vm.mdTable).toEqual(
+ const tableButton = findToolbarButtonByProp('icon', 'table');
+
+ expect(tableButton.props('tag')).toEqual(
'| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |',
);
});
it('renders suggestion template', () => {
- vm.lineContent = 'Some content';
-
- expect(vm.mdSuggestion).toEqual('```suggestion:-0+0\n{text}\n```');
+ expect(findToolbarButtonByProp('buttonTitle', 'Insert suggestion').props('tag')).toEqual(
+ '```suggestion:-0+0\n{text}\n```',
+ );
});
it('does not render suggestion button if `canSuggest` is set to false', () => {
- vm.canSuggest = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-suggestion-btn')).toBe(null);
+ createWrapper({
+ canSuggest: false,
});
+
+ expect(wrapper.find('.js-suggestion-btn').exists()).toBe(false);
});
});