diff options
Diffstat (limited to 'spec/frontend/ref')
-rw-r--r-- | spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap | 70 | ||||
-rw-r--r-- | spec/frontend/ref/components/ref_selector_spec.js | 211 | ||||
-rw-r--r-- | spec/frontend/ref/stores/actions_spec.js | 22 | ||||
-rw-r--r-- | spec/frontend/ref/stores/mutations_spec.js | 11 |
4 files changed, 287 insertions, 27 deletions
diff --git a/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap b/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap new file mode 100644 index 00000000000..5f05b7fc68b --- /dev/null +++ b/spec/frontend/ref/components/__snapshots__/ref_selector_spec.js.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Ref selector component footer slot passes the expected slot props 1`] = ` +Object { + "isLoading": false, + "matches": Object { + "branches": Object { + "error": null, + "list": Array [ + Object { + "default": false, + "name": "add_images_and_changes", + }, + Object { + "default": false, + "name": "conflict-contains-conflict-markers", + }, + Object { + "default": false, + "name": "deleted-image-test", + }, + Object { + "default": false, + "name": "diff-files-image-to-symlink", + }, + Object { + "default": false, + "name": "diff-files-symlink-to-image", + }, + Object { + "default": false, + "name": "markdown", + }, + Object { + "default": true, + "name": "master", + }, + ], + "totalCount": 123, + }, + "commits": Object { + "error": null, + "list": Array [ + Object { + "name": "b83d6e39", + "subtitle": "Merge branch 'branch-merged' into 'master'", + "value": "b83d6e391c22777fca1ed3012fce84f633d7fed0", + }, + ], + "totalCount": 1, + }, + "tags": Object { + "error": null, + "list": Array [ + Object { + "name": "v1.1.1", + }, + Object { + "name": "v1.1.0", + }, + Object { + "name": "v1.0.0", + }, + ], + "totalCount": 456, + }, + }, + "query": "abcd1234", +} +`; diff --git a/spec/frontend/ref/components/ref_selector_spec.js b/spec/frontend/ref/components/ref_selector_spec.js index 27ada131ed6..a642a8cf8c2 100644 --- a/spec/frontend/ref/components/ref_selector_spec.js +++ b/spec/frontend/ref/components/ref_selector_spec.js @@ -1,13 +1,20 @@ -import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlSearchBoxByType, GlDropdownItem, GlDropdown, GlIcon } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { merge, last } from 'lodash'; import Vuex from 'vuex'; import { trimText } from 'helpers/text_helper'; import { ENTER_KEY } from '~/lib/utils/keys'; import { sprintf } from '~/locale'; import RefSelector from '~/ref/components/ref_selector.vue'; -import { X_TOTAL_HEADER, DEFAULT_I18N } from '~/ref/constants'; +import { + X_TOTAL_HEADER, + DEFAULT_I18N, + REF_TYPE_BRANCHES, + REF_TYPE_TAGS, + REF_TYPE_COMMITS, +} from '~/ref/constants'; import createStore from '~/ref/stores/'; const localVue = createLocalVue(); @@ -26,27 +33,32 @@ describe('Ref selector component', () => { let branchesApiCallSpy; let tagsApiCallSpy; let commitApiCallSpy; - - const createComponent = (props = {}, attrs = {}) => { - wrapper = mount(RefSelector, { - propsData: { - projectId, - value: '', - ...props, - }, - attrs, - listeners: { - // simulate a parent component v-model binding - input: (selectedRef) => { - wrapper.setProps({ value: selectedRef }); + let requestSpies; + + const createComponent = (mountOverrides = {}) => { + wrapper = mount( + RefSelector, + merge( + { + propsData: { + projectId, + value: '', + }, + listeners: { + // simulate a parent component v-model binding + input: (selectedRef) => { + wrapper.setProps({ value: selectedRef }); + }, + }, + stubs: { + GlSearchBoxByType: true, + }, + localVue, + store: createStore(), }, - }, - stubs: { - GlSearchBoxByType: true, - }, - localVue, - store: createStore(), - }); + mountOverrides, + ), + ); }; beforeEach(() => { @@ -58,6 +70,7 @@ describe('Ref selector component', () => { .mockReturnValue([200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }]); tagsApiCallSpy = jest.fn().mockReturnValue([200, fixtures.tags, { [X_TOTAL_HEADER]: '456' }]); commitApiCallSpy = jest.fn().mockReturnValue([200, fixtures.commit]); + requestSpies = { branchesApiCallSpy, tagsApiCallSpy, commitApiCallSpy }; mock .onGet(`/api/v4/projects/${projectId}/repository/branches`) @@ -78,7 +91,7 @@ describe('Ref selector component', () => { // // Finders // - const findButtonContent = () => wrapper.find('[data-testid="button-content"]'); + const findButtonContent = () => wrapper.find('button'); const findNoResults = () => wrapper.find('[data-testid="no-results"]'); @@ -175,7 +188,7 @@ describe('Ref selector component', () => { const id = 'git-ref'; beforeEach(() => { - createComponent({}, { id }); + createComponent({ attrs: { id } }); return waitForRequests(); }); @@ -189,7 +202,7 @@ describe('Ref selector component', () => { const preselectedRef = fixtures.branches[0].name; beforeEach(() => { - createComponent({ value: preselectedRef }); + createComponent({ propsData: { value: preselectedRef } }); return waitForRequests(); }); @@ -592,4 +605,152 @@ describe('Ref selector component', () => { }); }); }); + + describe('with non-default ref types', () => { + it.each` + enabledRefTypes | reqsCalled | reqsNotCalled + ${[REF_TYPE_BRANCHES]} | ${['branchesApiCallSpy']} | ${['tagsApiCallSpy', 'commitApiCallSpy']} + ${[REF_TYPE_TAGS]} | ${['tagsApiCallSpy']} | ${['branchesApiCallSpy', 'commitApiCallSpy']} + ${[REF_TYPE_COMMITS]} | ${[]} | ${['branchesApiCallSpy', 'tagsApiCallSpy', 'commitApiCallSpy']} + ${[REF_TYPE_TAGS, REF_TYPE_COMMITS]} | ${['tagsApiCallSpy']} | ${['branchesApiCallSpy', 'commitApiCallSpy']} + `( + 'only calls $reqsCalled requests when $enabledRefTypes are enabled', + async ({ enabledRefTypes, reqsCalled, reqsNotCalled }) => { + createComponent({ propsData: { enabledRefTypes } }); + + await waitForRequests(); + + reqsCalled.forEach((req) => expect(requestSpies[req]).toHaveBeenCalledTimes(1)); + reqsNotCalled.forEach((req) => expect(requestSpies[req]).not.toHaveBeenCalled()); + }, + ); + + it('only calls commitApiCallSpy when REF_TYPE_COMMITS is enabled', async () => { + createComponent({ propsData: { enabledRefTypes: [REF_TYPE_COMMITS] } }); + updateQuery('abcd1234'); + + await waitForRequests(); + + expect(commitApiCallSpy).toHaveBeenCalledTimes(1); + expect(branchesApiCallSpy).not.toHaveBeenCalled(); + expect(tagsApiCallSpy).not.toHaveBeenCalled(); + }); + + it('triggers another search if enabled ref types change', async () => { + createComponent({ propsData: { enabledRefTypes: [REF_TYPE_BRANCHES] } }); + await waitForRequests(); + + expect(branchesApiCallSpy).toHaveBeenCalledTimes(1); + expect(tagsApiCallSpy).not.toHaveBeenCalled(); + + wrapper.setProps({ + enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_TAGS], + }); + await waitForRequests(); + + expect(branchesApiCallSpy).toHaveBeenCalledTimes(2); + expect(tagsApiCallSpy).toHaveBeenCalledTimes(1); + }); + + it('if a ref type becomes disabled, its section is hidden, even if it had some results in store', async () => { + createComponent({ propsData: { enabledRefTypes: [REF_TYPE_BRANCHES, REF_TYPE_COMMITS] } }); + updateQuery('abcd1234'); + await waitForRequests(); + + expect(findBranchesSection().exists()).toBe(true); + expect(findCommitsSection().exists()).toBe(true); + + wrapper.setProps({ enabledRefTypes: [REF_TYPE_COMMITS] }); + await waitForRequests(); + + expect(findBranchesSection().exists()).toBe(false); + expect(findCommitsSection().exists()).toBe(true); + }); + + it.each` + enabledRefType | findVisibleSection | findHiddenSections + ${REF_TYPE_BRANCHES} | ${findBranchesSection} | ${[findTagsSection, findCommitsSection]} + ${REF_TYPE_TAGS} | ${findTagsSection} | ${[findBranchesSection, findCommitsSection]} + ${REF_TYPE_COMMITS} | ${findCommitsSection} | ${[findBranchesSection, findTagsSection]} + `( + 'hides section headers if a single ref type is enabled', + async ({ enabledRefType, findVisibleSection, findHiddenSections }) => { + createComponent({ propsData: { enabledRefTypes: [enabledRefType] } }); + updateQuery('abcd1234'); + await waitForRequests(); + + expect(findVisibleSection().exists()).toBe(true); + expect(findVisibleSection().find('[data-testid="section-header"]').exists()).toBe(false); + findHiddenSections.forEach((findHiddenSection) => + expect(findHiddenSection().exists()).toBe(false), + ); + }, + ); + }); + + describe('validation state', () => { + const invalidClass = 'gl-inset-border-1-red-500!'; + const isInvalidClassApplied = () => wrapper.find(GlDropdown).props('toggleClass')[invalidClass]; + + describe('valid state', () => { + describe('when the state prop is not provided', () => { + it('does not render a red border', () => { + createComponent(); + + expect(isInvalidClassApplied()).toBe(false); + }); + }); + + describe('when the state prop is true', () => { + it('does not render a red border', () => { + createComponent({ propsData: { state: true } }); + + expect(isInvalidClassApplied()).toBe(false); + }); + }); + }); + + describe('invalid state', () => { + it('renders the dropdown with a red border if the state prop is false', () => { + createComponent({ propsData: { state: false } }); + + expect(isInvalidClassApplied()).toBe(true); + }); + }); + }); + + describe('footer slot', () => { + const footerContent = 'This is the footer content'; + const createFooter = jest.fn().mockImplementation(function createMockFooter() { + return this.$createElement('div', { attrs: { 'data-testid': 'footer-content' } }, [ + footerContent, + ]); + }); + + beforeEach(() => { + createComponent({ + scopedSlots: { footer: createFooter }, + }); + + updateQuery('abcd1234'); + + return waitForRequests(); + }); + + afterEach(() => { + createFooter.mockClear(); + }); + + it('allows custom content to be shown at the bottom of the dropdown using the footer slot', () => { + expect(wrapper.find(`[data-testid="footer-content"]`).text()).toBe(footerContent); + }); + + it('passes the expected slot props', () => { + // The createFooter function gets called every time one of the scoped properties + // is updated. For the sake of this test, we'll just test the last call, which + // represents the final state of the slot props. + const lastCallProps = last(createFooter.mock.calls)[0]; + expect(lastCallProps).toMatchSnapshot(); + }); + }); }); diff --git a/spec/frontend/ref/stores/actions_spec.js b/spec/frontend/ref/stores/actions_spec.js index 11acec27165..099ce062a3a 100644 --- a/spec/frontend/ref/stores/actions_spec.js +++ b/spec/frontend/ref/stores/actions_spec.js @@ -1,4 +1,5 @@ import testAction from 'helpers/vuex_action_helper'; +import { ALL_REF_TYPES, REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS } from '~/ref/constants'; import * as actions from '~/ref/stores/actions'; import * as types from '~/ref/stores/mutation_types'; import createState from '~/ref/stores/state'; @@ -25,6 +26,14 @@ describe('Ref selector Vuex store actions', () => { state = createState(); }); + describe('setEnabledRefTypes', () => { + it(`commits ${types.SET_ENABLED_REF_TYPES} with the enabled ref types`, () => { + testAction(actions.setProjectId, ALL_REF_TYPES, state, [ + { type: types.SET_PROJECT_ID, payload: ALL_REF_TYPES }, + ]); + }); + }); + describe('setProjectId', () => { it(`commits ${types.SET_PROJECT_ID} with the new project ID`, () => { const projectId = '4'; @@ -46,12 +55,23 @@ describe('Ref selector Vuex store actions', () => { describe('search', () => { it(`commits ${types.SET_QUERY} with the new search query`, () => { const query = 'hello'; + testAction(actions.search, query, state, [{ type: types.SET_QUERY, payload: query }]); + }); + + it.each` + enabledRefTypes | expectedActions + ${[REF_TYPE_BRANCHES]} | ${['searchBranches']} + ${[REF_TYPE_COMMITS]} | ${['searchCommits']} + ${[REF_TYPE_BRANCHES, REF_TYPE_TAGS, REF_TYPE_COMMITS]} | ${['searchBranches', 'searchTags', 'searchCommits']} + `(`dispatches fetch actions for enabled ref types`, ({ enabledRefTypes, expectedActions }) => { + const query = 'hello'; + state.enabledRefTypes = enabledRefTypes; testAction( actions.search, query, state, [{ type: types.SET_QUERY, payload: query }], - [{ type: 'searchBranches' }, { type: 'searchTags' }, { type: 'searchCommits' }], + expectedActions.map((type) => ({ type })), ); }); }); diff --git a/spec/frontend/ref/stores/mutations_spec.js b/spec/frontend/ref/stores/mutations_spec.js index cda13089766..11d4fe0e206 100644 --- a/spec/frontend/ref/stores/mutations_spec.js +++ b/spec/frontend/ref/stores/mutations_spec.js @@ -1,4 +1,4 @@ -import { X_TOTAL_HEADER } from '~/ref/constants'; +import { X_TOTAL_HEADER, ALL_REF_TYPES } from '~/ref/constants'; import * as types from '~/ref/stores/mutation_types'; import mutations from '~/ref/stores/mutations'; import createState from '~/ref/stores/state'; @@ -13,6 +13,7 @@ describe('Ref selector Vuex store mutations', () => { describe('initial state', () => { it('is created with the correct structure and initial values', () => { expect(state).toEqual({ + enabledRefTypes: [], projectId: null, query: '', @@ -39,6 +40,14 @@ describe('Ref selector Vuex store mutations', () => { }); }); + describe(`${types.SET_ENABLED_REF_TYPES}`, () => { + it('sets the enabled ref types', () => { + mutations[types.SET_ENABLED_REF_TYPES](state, ALL_REF_TYPES); + + expect(state.enabledRefTypes).toBe(ALL_REF_TYPES); + }); + }); + describe(`${types.SET_PROJECT_ID}`, () => { it('updates the project ID', () => { const newProjectId = '4'; |