Commit 9373e032 authored by Paul Slaughter's avatar Paul Slaughter

Merge branch 'vs-ee-migrate-approvals-to-jest' into 'master'

Migrate ee/approvals to Jest

Closes #194266

See merge request gitlab-org/gitlab!32119
parents 2b0eb8b5 25dc9f11
import { createLocalVue, mount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import $ from 'jquery'; import $ from 'jquery';
import Api from 'ee/api'; import Api from 'ee/api';
import ApproversSelect from 'ee/approvals/components/approvers_select.vue'; import ApproversSelect from 'ee/approvals/components/approvers_select.vue';
import { TYPE_USER, TYPE_GROUP } from 'ee/approvals/constants'; import { TYPE_USER, TYPE_GROUP } from 'ee/approvals/constants';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
const DEBOUNCE_TIME = 250;
const TEST_PROJECT_ID = '17'; const TEST_PROJECT_ID = '17';
const TEST_GROUP_AVATAR = `${TEST_HOST}/group-avatar.png`; const TEST_GROUP_AVATAR = `${TEST_HOST}/group-avatar.png`;
const TEST_USER_AVATAR = `${TEST_HOST}/user-avatar.png`; const TEST_USER_AVATAR = `${TEST_HOST}/user-avatar.png`;
...@@ -26,7 +25,8 @@ const TEST_USERS = [ ...@@ -26,7 +25,8 @@ const TEST_USERS = [
const localVue = createLocalVue(); const localVue = createLocalVue();
const waitForEvent = ($input, event) => new Promise(resolve => $input.one(event, resolve)); const waitForEvent = ($input, event) => new Promise(resolve => $input.one(event, resolve));
const parseAvatar = element => (element.classList.contains('identicon') ? null : element.src); const parseAvatar = element =>
element.classList.contains('identicon') ? null : element.getAttribute('src');
const select2Container = () => document.querySelector('.select2-container'); const select2Container = () => document.querySelector('.select2-container');
const select2DropdownOptions = () => document.querySelectorAll('#select2-drop .user-result'); const select2DropdownOptions = () => document.querySelectorAll('#select2-drop .user-result');
const select2DropdownItems = () => const select2DropdownItems = () =>
...@@ -57,7 +57,7 @@ describe('Approvals ApproversSelect', () => { ...@@ -57,7 +57,7 @@ describe('Approvals ApproversSelect', () => {
...options.propsData, ...options.propsData,
}; };
wrapper = mount(localVue.extend(ApproversSelect), { wrapper = shallowMount(ApproversSelect, {
...options, ...options,
propsData, propsData,
localVue, localVue,
...@@ -68,18 +68,15 @@ describe('Approvals ApproversSelect', () => { ...@@ -68,18 +68,15 @@ describe('Approvals ApproversSelect', () => {
}; };
const search = (term = '') => { const search = (term = '') => {
$input.select2('search', term); $input.select2('search', term);
jasmine.clock().mockDate(); jest.runOnlyPendingTimers();
jasmine.clock().tick(DEBOUNCE_TIME);
}; };
beforeEach(() => { beforeEach(() => {
jasmine.clock().install(); jest.spyOn(Api, 'groups').mockResolvedValue(TEST_GROUPS);
spyOn(Api, 'groups').and.returnValue(Promise.resolve(TEST_GROUPS)); jest.spyOn(Api, 'projectUsers').mockReturnValue(Promise.resolve(TEST_USERS));
spyOn(Api, 'projectUsers').and.returnValue(Promise.resolve(TEST_USERS));
}); });
afterEach(() => { afterEach(() => {
jasmine.clock().uninstall();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -117,6 +114,7 @@ describe('Approvals ApproversSelect', () => { ...@@ -117,6 +114,7 @@ describe('Approvals ApproversSelect', () => {
factory(); factory();
waitForEvent($input, 'select2-loaded') waitForEvent($input, 'select2-loaded')
.then(jest.runOnlyPendingTimers)
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
...@@ -147,6 +145,7 @@ describe('Approvals ApproversSelect', () => { ...@@ -147,6 +145,7 @@ describe('Approvals ApproversSelect', () => {
}); });
waitForEvent($input, 'select2-loaded') waitForEvent($input, 'select2-loaded')
.then(jest.runOnlyPendingTimers)
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
...@@ -185,10 +184,12 @@ describe('Approvals ApproversSelect', () => { ...@@ -185,10 +184,12 @@ describe('Approvals ApproversSelect', () => {
$(options[TEST_GROUPS.length]).trigger('mouseup'); $(options[TEST_GROUPS.length]).trigger('mouseup');
$(options[0]).trigger('mouseup'); $(options[0]).trigger('mouseup');
}) })
.then(jest.runOnlyPendingTimers)
.then(done) .then(done)
.catch(done.fail); .catch(done.fail);
waitForEvent($input, 'change') waitForEvent($input, 'change')
.then(jest.runOnlyPendingTimers)
.then(() => { .then(() => {
expect(wrapper.emittedByOrder()).toEqual(expected); expect(wrapper.emittedByOrder()).toEqual(expected);
}) })
......
import { mount, createLocalVue } from '@vue/test-utils'; import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { createStoreOptions } from 'ee/approvals/stores'; import { createStoreOptions } from 'ee/approvals/stores';
import MREditModule from 'ee/approvals/stores/modules/mr_edit'; import MREditModule from 'ee/approvals/stores/modules/mr_edit';
import MREditApp from 'ee/approvals/components/mr_edit/app.vue'; import MREditApp from 'ee/approvals/components/mr_edit/app.vue';
...@@ -12,21 +14,26 @@ localVue.use(Vuex); ...@@ -12,21 +14,26 @@ localVue.use(Vuex);
describe('EE Approvals MREditApp', () => { describe('EE Approvals MREditApp', () => {
let wrapper; let wrapper;
let store; let store;
let axiosMock;
const factory = () => { const factory = () => {
wrapper = mount(localVue.extend(MREditApp), { wrapper = mount(MREditApp, {
localVue, localVue,
store: new Vuex.Store(store), store: new Vuex.Store(store),
}); });
}; };
beforeEach(() => { beforeEach(() => {
axiosMock = new MockAdapter(axios);
axiosMock.onGet('*');
store = createStoreOptions(MREditModule()); store = createStoreOptions(MREditModule());
store.modules.approvals.state.hasLoaded = true; store.modules.approvals.state.hasLoaded = true;
}); });
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
axiosMock.restore();
}); });
describe('with empty rules', () => { describe('with empty rules', () => {
...@@ -36,11 +43,11 @@ describe('EE Approvals MREditApp', () => { ...@@ -36,11 +43,11 @@ describe('EE Approvals MREditApp', () => {
}); });
it('does not render MR rules', () => { it('does not render MR rules', () => {
expect(wrapper.find(MRRules).exists()).toBe(true); expect(wrapper.find(MRRules).findAll('.js-name')).toHaveLength(0);
}); });
it('renders hidden inputs', () => { it('renders hidden inputs', () => {
expect(wrapper.find(MRRulesHiddenInputs).exists()).toBe(true); expect(wrapper.find('.js-approval-rules').contains(MRRulesHiddenInputs)).toBe(true);
}); });
}); });
...@@ -51,11 +58,11 @@ describe('EE Approvals MREditApp', () => { ...@@ -51,11 +58,11 @@ describe('EE Approvals MREditApp', () => {
}); });
it('renders MR rules', () => { it('renders MR rules', () => {
expect(wrapper.find(MRRules).exists()).toBe(true); expect(wrapper.find(MRRules).findAll('.js-name')).toHaveLength(1);
}); });
it('renders hidden inputs', () => { it('renders hidden inputs', () => {
expect(wrapper.find(MRRulesHiddenInputs).exists()).toBe(true); expect(wrapper.find('.js-approval-rules').contains(MRRulesHiddenInputs)).toBe(true);
}); });
}); });
}); });
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import testAction from 'spec/helpers/vuex_action_helper'; import createFlash from '~/flash';
import testAction from 'helpers/vuex_action_helper';
import * as types from 'ee/approvals/stores/modules/base/mutation_types'; import * as types from 'ee/approvals/stores/modules/base/mutation_types';
import actionsModule, * as actions from 'ee/approvals/stores/modules/project_settings/actions'; import * as actions from 'ee/approvals/stores/modules/project_settings/actions';
import { mapApprovalRuleRequest, mapApprovalSettingsResponse } from 'ee/approvals/mappers'; import { mapApprovalRuleRequest, mapApprovalSettingsResponse } from 'ee/approvals/mappers';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
jest.mock('~/flash');
const TEST_PROJECT_ID = 9; const TEST_PROJECT_ID = 9;
const TEST_RULE_ID = 7; const TEST_RULE_ID = 7;
const TEST_RULE_REQUEST = { const TEST_RULE_REQUEST = {
...@@ -26,7 +29,6 @@ const TEST_RULES_PATH = 'projects/9/approval_settings/rules'; ...@@ -26,7 +29,6 @@ const TEST_RULES_PATH = 'projects/9/approval_settings/rules';
describe('EE approvals project settings module actions', () => { describe('EE approvals project settings module actions', () => {
let state; let state;
let flashSpy;
let mock; let mock;
beforeEach(() => { beforeEach(() => {
...@@ -37,7 +39,6 @@ describe('EE approvals project settings module actions', () => { ...@@ -37,7 +39,6 @@ describe('EE approvals project settings module actions', () => {
rulesPath: TEST_RULES_PATH, rulesPath: TEST_RULES_PATH,
}, },
}; };
flashSpy = spyOnDependency(actionsModule, 'createFlash');
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
...@@ -46,23 +47,22 @@ describe('EE approvals project settings module actions', () => { ...@@ -46,23 +47,22 @@ describe('EE approvals project settings module actions', () => {
}); });
describe('requestRules', () => { describe('requestRules', () => {
it('sets loading', done => { it('sets loading', () => {
testAction( return testAction(
actions.requestRules, actions.requestRules,
null, null,
{}, {},
[{ type: types.SET_LOADING, payload: true }], [{ type: types.SET_LOADING, payload: true }],
[], [],
done,
); );
}); });
}); });
describe('receiveRulesSuccess', () => { describe('receiveRulesSuccess', () => {
it('sets rules', done => { it('sets rules', () => {
const settings = { rules: [{ id: 1 }] }; const settings = { rules: [{ id: 1 }] };
testAction( return testAction(
actions.receiveRulesSuccess, actions.receiveRulesSuccess,
settings, settings,
{}, {},
...@@ -71,28 +71,27 @@ describe('EE approvals project settings module actions', () => { ...@@ -71,28 +71,27 @@ describe('EE approvals project settings module actions', () => {
{ type: types.SET_LOADING, payload: false }, { type: types.SET_LOADING, payload: false },
], ],
[], [],
done,
); );
}); });
}); });
describe('receiveRulesError', () => { describe('receiveRulesError', () => {
it('creates a flash', () => { it('creates a flash', () => {
expect(flashSpy).not.toHaveBeenCalled(); expect(createFlash).not.toHaveBeenCalled();
actions.receiveRulesError(); actions.receiveRulesError();
expect(flashSpy).toHaveBeenCalledTimes(1); expect(createFlash).toHaveBeenCalledTimes(1);
expect(flashSpy).toHaveBeenCalledWith(jasmine.stringMatching('error occurred')); expect(createFlash).toHaveBeenCalledWith(expect.stringMatching('error occurred'));
}); });
}); });
describe('fetchRules', () => { describe('fetchRules', () => {
it('dispatches request/receive', done => { it('dispatches request/receive', () => {
const data = { rules: [TEST_RULE_RESPONSE] }; const data = { rules: [TEST_RULE_RESPONSE] };
mock.onGet(TEST_SETTINGS_PATH).replyOnce(200, data); mock.onGet(TEST_SETTINGS_PATH).replyOnce(200, data);
testAction( return testAction(
actions.fetchRules, actions.fetchRules,
null, null,
state, state,
...@@ -103,54 +102,50 @@ describe('EE approvals project settings module actions', () => { ...@@ -103,54 +102,50 @@ describe('EE approvals project settings module actions', () => {
], ],
() => { () => {
expect(mock.history.get.map(x => x.url)).toEqual([TEST_SETTINGS_PATH]); expect(mock.history.get.map(x => x.url)).toEqual([TEST_SETTINGS_PATH]);
done();
}, },
); );
}); });
it('dispatches request/receive on error', done => { it('dispatches request/receive on error', () => {
mock.onGet(TEST_SETTINGS_PATH).replyOnce(500); mock.onGet(TEST_SETTINGS_PATH).replyOnce(500);
testAction( return testAction(
actions.fetchRules, actions.fetchRules,
null, null,
state, state,
[], [],
[{ type: 'requestRules' }, { type: 'receiveRulesError' }], [{ type: 'requestRules' }, { type: 'receiveRulesError' }],
done,
); );
}); });
}); });
describe('postRuleSuccess', () => { describe('postRuleSuccess', () => {
it('closes modal and fetches', done => { it('closes modal and fetches', () => {
testAction( return testAction(
actions.postRuleSuccess, actions.postRuleSuccess,
null, null,
{}, {},
[], [],
[{ type: 'createModal/close' }, { type: 'fetchRules' }], [{ type: 'createModal/close' }, { type: 'fetchRules' }],
done,
); );
}); });
}); });
describe('postRuleError', () => { describe('postRuleError', () => {
it('creates a flash', () => { it('creates a flash', () => {
expect(flashSpy).not.toHaveBeenCalled(); expect(createFlash).not.toHaveBeenCalled();
actions.postRuleError(); actions.postRuleError();
expect(flashSpy.calls.allArgs()).toEqual([[jasmine.stringMatching('error occurred')]]); expect(createFlash.mock.calls[0]).toEqual([expect.stringMatching('error occurred')]);
}); });
}); });
describe('postRule', () => { describe('postRule', () => {
it('dispatches success on success', done => { it('dispatches success on success', () => {
mock.onPost(TEST_RULES_PATH).replyOnce(200); mock.onPost(TEST_RULES_PATH).replyOnce(200);
testAction( return testAction(
actions.postRule, actions.postRule,
TEST_RULE_REQUEST, TEST_RULE_REQUEST,
state, state,
...@@ -158,29 +153,33 @@ describe('EE approvals project settings module actions', () => { ...@@ -158,29 +153,33 @@ describe('EE approvals project settings module actions', () => {
[{ type: 'postRuleSuccess' }], [{ type: 'postRuleSuccess' }],
() => { () => {
expect(mock.history.post).toEqual([ expect(mock.history.post).toEqual([
jasmine.objectContaining({ expect.objectContaining({
url: TEST_RULES_PATH, url: TEST_RULES_PATH,
data: JSON.stringify(mapApprovalRuleRequest(TEST_RULE_REQUEST)), data: JSON.stringify(mapApprovalRuleRequest(TEST_RULE_REQUEST)),
}), }),
]); ]);
done();
}, },
); );
}); });
it('dispatches error on error', done => { it('dispatches error on error', () => {
mock.onPost(TEST_RULES_PATH).replyOnce(500); mock.onPost(TEST_RULES_PATH).replyOnce(500);
testAction(actions.postRule, TEST_RULE_REQUEST, state, [], [{ type: 'postRuleError' }], done); return testAction(
actions.postRule,
TEST_RULE_REQUEST,
state,
[],
[{ type: 'postRuleError' }],
);
}); });
}); });
describe('putRule', () => { describe('putRule', () => {
it('dispatches success on success', done => { it('dispatches success on success', () => {
mock.onPut(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(200); mock.onPut(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(200);
testAction( return testAction(
actions.putRule, actions.putRule,
{ id: TEST_RULE_ID, ...TEST_RULE_REQUEST }, { id: TEST_RULE_ID, ...TEST_RULE_REQUEST },
state, state,
...@@ -188,59 +187,55 @@ describe('EE approvals project settings module actions', () => { ...@@ -188,59 +187,55 @@ describe('EE approvals project settings module actions', () => {
[{ type: 'postRuleSuccess' }], [{ type: 'postRuleSuccess' }],
() => { () => {
expect(mock.history.put).toEqual([ expect(mock.history.put).toEqual([
jasmine.objectContaining({ expect.objectContaining({
url: `${TEST_RULES_PATH}/${TEST_RULE_ID}`, url: `${TEST_RULES_PATH}/${TEST_RULE_ID}`,
data: JSON.stringify(mapApprovalRuleRequest(TEST_RULE_REQUEST)), data: JSON.stringify(mapApprovalRuleRequest(TEST_RULE_REQUEST)),
}), }),
]); ]);
done();
}, },
); );
}); });
it('dispatches error on error', done => { it('dispatches error on error', () => {
mock.onPut(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(500); mock.onPut(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(500);
testAction( return testAction(
actions.putRule, actions.putRule,
{ id: TEST_RULE_ID, ...TEST_RULE_REQUEST }, { id: TEST_RULE_ID, ...TEST_RULE_REQUEST },
state, state,
[], [],
[{ type: 'postRuleError' }], [{ type: 'postRuleError' }],
done,
); );
}); });
}); });
describe('deleteRuleSuccess', () => { describe('deleteRuleSuccess', () => {
it('closes modal and fetches', done => { it('closes modal and fetches', () => {
testAction( return testAction(
actions.deleteRuleSuccess, actions.deleteRuleSuccess,
null, null,
{}, {},
[], [],
[{ type: 'deleteModal/close' }, { type: 'fetchRules' }], [{ type: 'deleteModal/close' }, { type: 'fetchRules' }],
done,
); );
}); });
}); });
describe('deleteRuleError', () => { describe('deleteRuleError', () => {
it('creates a flash', () => { it('creates a flash', () => {
expect(flashSpy).not.toHaveBeenCalled(); expect(createFlash).not.toHaveBeenCalled();
actions.deleteRuleError(); actions.deleteRuleError();
expect(flashSpy.calls.allArgs()).toEqual([[jasmine.stringMatching('error occurred')]]); expect(createFlash.mock.calls[0]).toEqual([expect.stringMatching('error occurred')]);
}); });
}); });
describe('deleteRule', () => { describe('deleteRule', () => {
it('dispatches success on success', done => { it('dispatches success on success', () => {
mock.onDelete(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(200); mock.onDelete(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(200);
testAction( return testAction(
actions.deleteRule, actions.deleteRule,
TEST_RULE_ID, TEST_RULE_ID,
state, state,
...@@ -248,20 +243,18 @@ describe('EE approvals project settings module actions', () => { ...@@ -248,20 +243,18 @@ describe('EE approvals project settings module actions', () => {
[{ type: 'deleteRuleSuccess' }], [{ type: 'deleteRuleSuccess' }],
() => { () => {
expect(mock.history.delete).toEqual([ expect(mock.history.delete).toEqual([
jasmine.objectContaining({ expect.objectContaining({
url: `${TEST_RULES_PATH}/${TEST_RULE_ID}`, url: `${TEST_RULES_PATH}/${TEST_RULE_ID}`,
}), }),
]); ]);
done();
}, },
); );
}); });
it('dispatches error on error', done => { it('dispatches error on error', () => {
mock.onDelete(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(500); mock.onDelete(`${TEST_RULES_PATH}/${TEST_RULE_ID}`).replyOnce(500);
testAction(actions.deleteRule, TEST_RULE_ID, state, [], [{ type: 'deleteRuleError' }], done); return testAction(actions.deleteRule, TEST_RULE_ID, state, [], [{ type: 'deleteRuleError' }]);
}); });
}); });
}); });
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