diff options
Diffstat (limited to 'spec/frontend/helpers')
-rw-r--r-- | spec/frontend/helpers/stub_component.js | 12 | ||||
-rw-r--r-- | spec/frontend/helpers/vue_test_utils_helper.js | 15 | ||||
-rw-r--r-- | spec/frontend/helpers/vue_test_utils_helper_spec.js | 46 | ||||
-rw-r--r-- | spec/frontend/helpers/vuex_action_helper.js | 41 | ||||
-rw-r--r-- | spec/frontend/helpers/vuex_action_helper_spec.js | 264 |
5 files changed, 240 insertions, 138 deletions
diff --git a/spec/frontend/helpers/stub_component.js b/spec/frontend/helpers/stub_component.js new file mode 100644 index 00000000000..45550450517 --- /dev/null +++ b/spec/frontend/helpers/stub_component.js @@ -0,0 +1,12 @@ +export function stubComponent(Component, options = {}) { + return { + props: Component.props, + model: Component.model, + // Do not render any slots/scoped slots except default + // This differs from VTU behavior which renders all slots + template: '<div><slot></slot></div>', + // allows wrapper.find(Component) to work for stub + $_vueTestUtils_original: Component, + ...options, + }; +} diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js index ead898f04d3..0e9127b5c65 100644 --- a/spec/frontend/helpers/vue_test_utils_helper.js +++ b/spec/frontend/helpers/vue_test_utils_helper.js @@ -1,3 +1,5 @@ +import { isArray } from 'lodash'; + const vNodeContainsText = (vnode, text) => (vnode.text && vnode.text.includes(text)) || (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length); @@ -34,9 +36,18 @@ export const waitForMutation = (store, expectedMutationType) => }); }); -export const extendedWrapper = wrapper => - Object.defineProperty(wrapper, 'findByTestId', { +export const extendedWrapper = wrapper => { + if (isArray(wrapper) || !wrapper?.find) { + // eslint-disable-next-line no-console + console.warn( + '[vue-test-utils-helper]: you are trying to extend an object that is not a VueWrapper.', + ); + return wrapper; + } + + return Object.defineProperty(wrapper, 'findByTestId', { value(id) { return this.find(`[data-testid="${id}"]`); }, }); +}; diff --git a/spec/frontend/helpers/vue_test_utils_helper_spec.js b/spec/frontend/helpers/vue_test_utils_helper_spec.js index 41714066da5..31c4ccd5dbb 100644 --- a/spec/frontend/helpers/vue_test_utils_helper_spec.js +++ b/spec/frontend/helpers/vue_test_utils_helper_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { shallowWrapperContainsSlotText } from './vue_test_utils_helper'; +import { extendedWrapper, shallowWrapperContainsSlotText } from './vue_test_utils_helper'; describe('Vue test utils helpers', () => { describe('shallowWrapperContainsSlotText', () => { @@ -45,4 +45,48 @@ describe('Vue test utils helpers', () => { expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false); }); }); + + describe('extendedWrapper', () => { + describe('when an invalid wrapper is provided', () => { + beforeEach(() => { + // eslint-disable-next-line no-console + console.warn = jest.fn(); + }); + + it.each` + wrapper + ${{}} + ${[]} + ${null} + ${undefined} + ${1} + ${''} + `('should warn with an error when the wrapper is $wrapper', ({ wrapper }) => { + extendedWrapper(wrapper); + /* eslint-disable no-console */ + expect(console.warn).toHaveBeenCalled(); + expect(console.warn).toHaveBeenCalledWith( + '[vue-test-utils-helper]: you are trying to extend an object that is not a VueWrapper.', + ); + /* eslint-enable no-console */ + }); + }); + + describe('findByTestId', () => { + const testId = 'a-component'; + let mockComponent; + + beforeEach(() => { + mockComponent = extendedWrapper( + shallowMount({ + template: `<div data-testid="${testId}"></div>`, + }), + ); + }); + + it('should find the component by test id', () => { + expect(mockComponent.findByTestId(testId).exists()).toBe(true); + }); + }); + }); }); diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js index 6c3569a2247..64dd3888d47 100644 --- a/spec/frontend/helpers/vuex_action_helper.js +++ b/spec/frontend/helpers/vuex_action_helper.js @@ -4,7 +4,7 @@ const noop = () => {}; * Helper for testing action with expected mutations inspired in * https://vuex.vuejs.org/en/testing.html * - * @param {Function} action to be tested + * @param {(Function|Object)} action to be tested, or object of named parameters * @param {Object} payload will be provided to the action * @param {Object} state will be provided to the action * @param {Array} [expectedMutations=[]] mutations expected to be committed @@ -39,15 +39,42 @@ const noop = () => {}; * [], // expected actions * ).then(done) * .catch(done.fail); + * + * @example + * await testAction({ + * action: actions.actionName, + * payload: { deleteListId: 1 }, + * state: { lists: [1, 2, 3] }, + * expectedMutations: [ { type: types.MUTATION} ], + * expectedActions: [], + * }) */ export default ( - action, - payload, - state, - expectedMutations = [], - expectedActions = [], - done = noop, + actionArg, + payloadArg, + stateArg, + expectedMutationsArg = [], + expectedActionsArg = [], + doneArg = noop, ) => { + let action = actionArg; + let payload = payloadArg; + let state = stateArg; + let expectedMutations = expectedMutationsArg; + let expectedActions = expectedActionsArg; + let done = doneArg; + + if (typeof actionArg !== 'function') { + ({ + action, + payload, + state, + expectedMutations = [], + expectedActions = [], + done = noop, + } = actionArg); + } + const mutations = []; const actions = []; diff --git a/spec/frontend/helpers/vuex_action_helper_spec.js b/spec/frontend/helpers/vuex_action_helper_spec.js index 61d05762a04..4d7bf21820a 100644 --- a/spec/frontend/helpers/vuex_action_helper_spec.js +++ b/spec/frontend/helpers/vuex_action_helper_spec.js @@ -1,166 +1,174 @@ import MockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'helpers/test_constants'; import axios from '~/lib/utils/axios_utils'; -import testAction from './vuex_action_helper'; - -describe('VueX test helper (testAction)', () => { - let originalExpect; - let assertion; - let mock; - const noop = () => {}; - - beforeEach(() => { - mock = new MockAdapter(axios); - /** - * In order to test the helper properly, we need to overwrite the Jest - * `expect` helper. We test that the testAction helper properly passes the - * dispatched actions/committed mutations to the Jest helper. - */ - originalExpect = expect; - assertion = null; - global.expect = actual => ({ - toEqual: () => { - originalExpect(actual).toEqual(assertion); - }, - }); - }); +import testActionFn from './vuex_action_helper'; - afterEach(() => { - mock.restore(); - global.expect = originalExpect; - }); +const testActionFnWithOptionsArg = (...args) => { + const [action, payload, state, expectedMutations, expectedActions, done] = args; + return testActionFn({ action, payload, state, expectedMutations, expectedActions, done }); +}; - it('properly passes state and payload to action', () => { - const exampleState = { FOO: 12, BAR: 3 }; - const examplePayload = { BAZ: 73, BIZ: 55 }; +describe.each([testActionFn, testActionFnWithOptionsArg])( + 'VueX test helper (testAction)', + testAction => { + let originalExpect; + let assertion; + let mock; + const noop = () => {}; - const action = ({ state }, payload) => { - originalExpect(state).toEqual(exampleState); - originalExpect(payload).toEqual(examplePayload); - }; + beforeEach(() => { + mock = new MockAdapter(axios); + /** + * In order to test the helper properly, we need to overwrite the Jest + * `expect` helper. We test that the testAction helper properly passes the + * dispatched actions/committed mutations to the Jest helper. + */ + originalExpect = expect; + assertion = null; + global.expect = actual => ({ + toEqual: () => { + originalExpect(actual).toEqual(assertion); + }, + }); + }); - assertion = { mutations: [], actions: [] }; + afterEach(() => { + mock.restore(); + global.expect = originalExpect; + }); - testAction(action, examplePayload, exampleState); - }); + it('properly passes state and payload to action', () => { + const exampleState = { FOO: 12, BAR: 3 }; + const examplePayload = { BAZ: 73, BIZ: 55 }; - describe('given a sync action', () => { - it('mocks committing mutations', () => { - const action = ({ commit }) => { - commit('MUTATION'); + const action = ({ state }, payload) => { + originalExpect(state).toEqual(exampleState); + originalExpect(payload).toEqual(examplePayload); }; - assertion = { mutations: [{ type: 'MUTATION' }], actions: [] }; + assertion = { mutations: [], actions: [] }; - testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + testAction(action, examplePayload, exampleState); }); - it('mocks dispatching actions', () => { - const action = ({ dispatch }) => { - dispatch('ACTION'); - }; + describe('given a sync action', () => { + it('mocks committing mutations', () => { + const action = ({ commit }) => { + commit('MUTATION'); + }; - assertion = { actions: [{ type: 'ACTION' }], mutations: [] }; + assertion = { mutations: [{ type: 'MUTATION' }], actions: [] }; - testAction(action, null, {}, assertion.mutations, assertion.actions, noop); - }); + testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + }); - it('works with done callback once finished', done => { - assertion = { mutations: [], actions: [] }; + it('mocks dispatching actions', () => { + const action = ({ dispatch }) => { + dispatch('ACTION'); + }; - testAction(noop, null, {}, assertion.mutations, assertion.actions, done); - }); + assertion = { actions: [{ type: 'ACTION' }], mutations: [] }; - it('returns a promise', done => { - assertion = { mutations: [], actions: [] }; + testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + }); - testAction(noop, null, {}, assertion.mutations, assertion.actions) - .then(done) - .catch(done.fail); - }); - }); - - describe('given an async action (returning a promise)', () => { - let lastError; - const data = { FOO: 'BAR' }; - - const asyncAction = ({ commit, dispatch }) => { - dispatch('ACTION'); - - return axios - .get(TEST_HOST) - .catch(error => { - commit('ERROR'); - lastError = error; - throw error; - }) - .then(() => { - commit('SUCCESS'); - return data; - }); - }; + it('works with done callback once finished', done => { + assertion = { mutations: [], actions: [] }; - beforeEach(() => { - lastError = null; + testAction(noop, null, {}, assertion.mutations, assertion.actions, done); + }); + + it('returns a promise', done => { + assertion = { mutations: [], actions: [] }; + + testAction(noop, null, {}, assertion.mutations, assertion.actions) + .then(done) + .catch(done.fail); + }); }); - it('works with done callback once finished', done => { - mock.onGet(TEST_HOST).replyOnce(200, 42); + describe('given an async action (returning a promise)', () => { + let lastError; + const data = { FOO: 'BAR' }; - assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + const asyncAction = ({ commit, dispatch }) => { + dispatch('ACTION'); - testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done); - }); + return axios + .get(TEST_HOST) + .catch(error => { + commit('ERROR'); + lastError = error; + throw error; + }) + .then(() => { + commit('SUCCESS'); + return data; + }); + }; - it('returns original data of successful promise while checking actions/mutations', done => { - mock.onGet(TEST_HOST).replyOnce(200, 42); + beforeEach(() => { + lastError = null; + }); - assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + it('works with done callback once finished', done => { + mock.onGet(TEST_HOST).replyOnce(200, 42); - testAction(asyncAction, null, {}, assertion.mutations, assertion.actions) - .then(res => { - originalExpect(res).toEqual(data); - done(); - }) - .catch(done.fail); - }); + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done); + }); + + it('returns original data of successful promise while checking actions/mutations', done => { + mock.onGet(TEST_HOST).replyOnce(200, 42); - it('returns original error of rejected promise while checking actions/mutations', done => { - mock.onGet(TEST_HOST).replyOnce(500, ''); + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; - assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] }; + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions) + .then(res => { + originalExpect(res).toEqual(data); + done(); + }) + .catch(done.fail); + }); - testAction(asyncAction, null, {}, assertion.mutations, assertion.actions) - .then(done.fail) - .catch(error => { - originalExpect(error).toBe(lastError); - done(); - }); + it('returns original error of rejected promise while checking actions/mutations', done => { + mock.onGet(TEST_HOST).replyOnce(500, ''); + + assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] }; + + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions) + .then(done.fail) + .catch(error => { + originalExpect(error).toBe(lastError); + done(); + }); + }); }); - }); - it('works with async actions not returning promises', done => { - const data = { FOO: 'BAR' }; + it('works with async actions not returning promises', done => { + const data = { FOO: 'BAR' }; - const asyncAction = ({ commit, dispatch }) => { - dispatch('ACTION'); + const asyncAction = ({ commit, dispatch }) => { + dispatch('ACTION'); - axios - .get(TEST_HOST) - .then(() => { - commit('SUCCESS'); - return data; - }) - .catch(error => { - commit('ERROR'); - throw error; - }); - }; + axios + .get(TEST_HOST) + .then(() => { + commit('SUCCESS'); + return data; + }) + .catch(error => { + commit('ERROR'); + throw error; + }); + }; - mock.onGet(TEST_HOST).replyOnce(200, 42); + mock.onGet(TEST_HOST).replyOnce(200, 42); - assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; - testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done); - }); -}); + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done); + }); + }, +); |