diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/list_selector/index_spec.js')
-rw-r--r-- | spec/frontend/vue_shared/components/list_selector/index_spec.js | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/spec/frontend/vue_shared/components/list_selector/index_spec.js b/spec/frontend/vue_shared/components/list_selector/index_spec.js new file mode 100644 index 00000000000..11e64a91eb0 --- /dev/null +++ b/spec/frontend/vue_shared/components/list_selector/index_spec.js @@ -0,0 +1,257 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlCard, GlIcon, GlCollapsibleListbox, GlSearchBoxByType } from '@gitlab/ui'; +import Api from '~/api'; +import { createAlert } from '~/alert'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import ListSelector from '~/vue_shared/components/list_selector/index.vue'; +import UserItem from '~/vue_shared/components/list_selector/user_item.vue'; +import GroupItem from '~/vue_shared/components/list_selector/group_item.vue'; +import groupsAutocompleteQuery from '~/graphql_shared/queries/groups_autocomplete.query.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { USERS_RESPONSE_MOCK, GROUPS_RESPONSE_MOCK } from './mock_data'; + +jest.mock('~/alert'); +Vue.use(VueApollo); + +describe('List Selector spec', () => { + let wrapper; + let fakeApollo; + + const USERS_MOCK_PROPS = { + title: 'Users', + projectPath: 'some/project/path', + groupPath: 'some/group/path', + type: 'users', + }; + + const GROUPS_MOCK_PROPS = { + title: 'Groups', + projectPath: 'some/project/path', + type: 'groups', + }; + + const groupsAutocompleteQuerySuccess = jest.fn().mockResolvedValue(GROUPS_RESPONSE_MOCK); + + const createComponent = async (props) => { + fakeApollo = createMockApollo([[groupsAutocompleteQuery, groupsAutocompleteQuerySuccess]]); + + wrapper = mountExtended(ListSelector, { + apolloProvider: fakeApollo, + propsData: { + ...props, + }, + }); + + await waitForPromises(); + }; + + const findCard = () => wrapper.findComponent(GlCard); + const findTitle = () => findCard().find('[data-testid="list-selector-title"]'); + const findIcon = () => wrapper.findComponent(GlIcon); + const findAllListBoxComponents = () => wrapper.findAllComponents(GlCollapsibleListbox); + const findSearchResultsDropdown = () => findAllListBoxComponents().at(0); + const findNamespaceDropdown = () => findAllListBoxComponents().at(1); + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + const findAllUserComponents = () => wrapper.findAllComponents(UserItem); + const findAllGroupComponents = () => wrapper.findAllComponents(GroupItem); + + beforeEach(() => { + jest.spyOn(Api, 'projectUsers').mockResolvedValue(USERS_RESPONSE_MOCK); + jest.spyOn(Api, 'groupMembers').mockResolvedValue({ data: USERS_RESPONSE_MOCK }); + }); + + describe('Users type', () => { + const search = 'foo'; + + beforeEach(() => createComponent(USERS_MOCK_PROPS)); + + it('renders a Card component', () => { + expect(findCard().exists()).toBe(true); + }); + + it('renders a correct title', () => { + expect(findTitle().exists()).toBe(true); + expect(findTitle().text()).toContain('Users'); + }); + + it('renders the correct icon', () => { + expect(findIcon().props('name')).toBe('user'); + }); + + it('renders a Search box component', () => { + expect(findSearchBox().exists()).toBe(true); + }); + + it('renders two namespace dropdown items', () => { + expect(findNamespaceDropdown().props('items').length).toBe(2); + }); + + it('does not call query when search box has not received an input', () => { + expect(Api.projectUsers).not.toHaveBeenCalled(); + expect(Api.groupMembers).not.toHaveBeenCalled(); + expect(findAllUserComponents().length).toBe(0); + }); + + describe.each` + dropdownItemValue | apiMethod | apiParams | searchResponse + ${'false'} | ${'groupMembers'} | ${[USERS_MOCK_PROPS.groupPath, { query: search }]} | ${USERS_RESPONSE_MOCK} + ${'true'} | ${'projectUsers'} | ${[USERS_MOCK_PROPS.projectPath, search]} | ${USERS_RESPONSE_MOCK} + `( + 'searching based on namespace dropdown selection', + ({ dropdownItemValue, apiMethod, apiParams, searchResponse }) => { + const emitSearchInput = async () => { + findSearchBox().vm.$emit('input', search); + await waitForPromises(); + }; + + beforeEach(async () => { + findNamespaceDropdown().vm.$emit('select', dropdownItemValue); + await emitSearchInput(); + }); + + it('shows error alert when API fails', async () => { + jest.spyOn(Api, apiMethod).mockRejectedValueOnce(); + await emitSearchInput(); + + expect(createAlert).toHaveBeenCalledWith({ + message: 'An error occurred while fetching. Please try again.', + }); + }); + + it('calls query with correct variables when Search box receives an input', () => { + expect(Api[apiMethod]).toHaveBeenCalledWith(...apiParams); + }); + + it('renders a List box component with the correct props', () => { + expect(findSearchResultsDropdown().props()).toMatchObject({ + multiple: true, + items: searchResponse, + }); + }); + + it('renders a user component for each search result', () => { + expect(findAllUserComponents().length).toBe(searchResponse.length); + }); + + it('emits an event when a search result is selected', () => { + const firstSearchResult = searchResponse[0]; + findAllUserComponents().at(0).vm.$emit('select', firstSearchResult.username); + + expect(wrapper.emitted('select')).toEqual([ + [{ ...firstSearchResult, text: 'Administrator', value: 'root' }], + ]); + }); + }, + ); + + describe('selected items', () => { + const selectedUser = { username: 'root' }; + const selectedItems = [selectedUser]; + beforeEach(() => createComponent({ ...USERS_MOCK_PROPS, selectedItems })); + + it('renders a heading with the total selected items', () => { + expect(findTitle().text()).toContain('Users'); + expect(findTitle().text()).toContain('1'); + }); + + it('renders a user component for each selected item', () => { + expect(findAllUserComponents().length).toBe(selectedItems.length); + expect(findAllUserComponents().at(0).props()).toMatchObject({ + data: selectedUser, + canDelete: true, + }); + }); + + it('emits a delete event when a delete event is emitted from the user component', () => { + const username = 'root'; + findAllUserComponents().at(0).vm.$emit('delete', username); + + expect(wrapper.emitted('delete')).toEqual([[username]]); + }); + }); + }); + + describe('Groups type', () => { + beforeEach(() => createComponent(GROUPS_MOCK_PROPS)); + + it('renders a correct title', () => { + expect(findTitle().exists()).toBe(true); + expect(findTitle().text()).toContain('Groups'); + }); + + it('renders the correct icon', () => { + expect(findIcon().props('name')).toBe('group'); + }); + + it('does not call query when search box has not received an input', () => { + expect(groupsAutocompleteQuerySuccess).not.toHaveBeenCalled(); + expect(findAllGroupComponents().length).toBe(0); + }); + + describe('searching', () => { + const searchResponse = GROUPS_RESPONSE_MOCK.data.groups.nodes; + const search = 'foo'; + + const emitSearchInput = async () => { + findSearchBox().vm.$emit('input', search); + await waitForPromises(); + }; + + beforeEach(() => emitSearchInput()); + + it('calls query with correct variables when Search box receives an input', () => { + expect(groupsAutocompleteQuerySuccess).toHaveBeenCalledWith({ + search, + }); + }); + + it('renders a dropdown for the search results', () => { + expect(findSearchResultsDropdown().props()).toMatchObject({ + multiple: true, + items: searchResponse, + }); + }); + + it('renders a group component for each search result', () => { + expect(findAllGroupComponents().length).toBe(searchResponse.length); + }); + + it('emits an event when a search result is selected', () => { + const firstSearchResult = searchResponse[0]; + findAllGroupComponents().at(0).vm.$emit('select', firstSearchResult.name); + + expect(wrapper.emitted('select')).toEqual([ + [{ ...firstSearchResult, text: 'Flightjs', value: 'Flightjs' }], + ]); + }); + }); + + describe('selected items', () => { + const selectedGroup = { name: 'Flightjs' }; + const selectedItems = [selectedGroup]; + beforeEach(() => createComponent({ ...GROUPS_MOCK_PROPS, selectedItems })); + + it('renders a heading with the total selected items', () => { + expect(findTitle().text()).toContain('Groups'); + expect(findTitle().text()).toContain('1'); + }); + + it('renders a group component for each selected item', () => { + expect(findAllGroupComponents().length).toBe(selectedItems.length); + expect(findAllGroupComponents().at(0).props()).toMatchObject({ + data: selectedGroup, + canDelete: true, + }); + }); + + it('emits a delete event when a delete event is emitted from the group component', () => { + const name = 'Flightjs'; + findAllGroupComponents().at(0).vm.$emit('delete', name); + + expect(wrapper.emitted('delete')).toEqual([[name]]); + }); + }); + }); +}); |