diff options
Diffstat (limited to 'spec/frontend/vue_shared')
3 files changed, 214 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..96074519857 --- /dev/null +++ b/spec/frontend/vue_shared/components/list_selector/index_spec.js @@ -0,0 +1,134 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlCard, GlIcon, GlCollapsibleListbox, GlSearchBoxByType } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import ListSelector from '~/vue_shared/components/list_selector/index.vue'; +import User from '~/vue_shared/components/list_selector/user.vue'; +import usersAutocompleteQuery from '~/graphql_shared/queries/users_autocomplete.query.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { USERS_RESPONSE_MOCK } from './mock_data'; + +Vue.use(VueApollo); + +describe('List Selector spec', () => { + let wrapper; + let fakeApollo; + + const MOCK_PROPS = { + title: 'Users', + projectPath: 'some/project/path', + type: 'users', + }; + + const usersAutocompleteQuerySuccess = jest.fn().mockResolvedValue(USERS_RESPONSE_MOCK); + + const createComponent = async (props) => { + fakeApollo = createMockApollo([[usersAutocompleteQuery, usersAutocompleteQuerySuccess]]); + + wrapper = mountExtended(ListSelector, { + apolloProvider: fakeApollo, + propsData: { + ...MOCK_PROPS, + ...props, + }, + }); + + await waitForPromises(); + }; + + const findCard = () => wrapper.findComponent(GlCard); + const findTitle = () => wrapper.findByText(MOCK_PROPS.title); + const findIcon = () => wrapper.findComponent(GlIcon); + const findListBox = () => wrapper.findComponent(GlCollapsibleListbox); + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + const findAllUserComponents = () => wrapper.findAllComponents(User); + + describe('Users type', () => { + beforeEach(() => createComponent({ type: 'users' })); + + it('renders a Card component', () => { + expect(findCard().exists()).toBe(true); + }); + + it('renders a title', () => { + expect(findTitle().exists()).toBe(true); + }); + + it('renders the correct icon', () => { + expect(findIcon().props('name')).toBe('user'); + }); + + it('renders a Search box component', () => { + expect(findSearchBox().exists()).toBe(true); + }); + + it('does not call query when search box has not received an input', () => { + expect(usersAutocompleteQuerySuccess).not.toHaveBeenCalled(); + expect(findAllUserComponents().length).toBe(0); + }); + + describe('searching', () => { + const searchResponse = USERS_RESPONSE_MOCK.data.project.autocompleteUsers; + 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(usersAutocompleteQuerySuccess).toHaveBeenCalledWith({ + fullPath: MOCK_PROPS.projectPath, + isProject: true, + search, + }); + }); + + it('renders a List box component with the correct props', () => { + expect(findListBox().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({ selectedItems })); + + it('renders a heading with the total selected items', () => { + expect(findCard().text()).toContain('Users'); + expect(findCard().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]]); + }); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/list_selector/mock_data.js b/spec/frontend/vue_shared/components/list_selector/mock_data.js new file mode 100644 index 00000000000..ce5d209a938 --- /dev/null +++ b/spec/frontend/vue_shared/components/list_selector/mock_data.js @@ -0,0 +1,25 @@ +export const USERS_RESPONSE_MOCK = { + data: { + project: { + id: 'gid://gitlab/Project/20', + autocompleteUsers: [ + { + id: 'gid://gitlab/User/1', + avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png', + name: 'Administrator', + username: 'root', + __typename: 'AutocompletedUser', + }, + { + id: 'gid://gitlab/User/15', + avatarUrl: + 'https://www.gravatar.com/avatar/c4ab964b90c3049c47882b319d3c5cc0?s=80\u0026d=identicon', + name: 'Corrine Rath', + username: 'laronda.graham', + __typename: 'AutocompletedUser', + }, + ], + __typename: 'Project', + }, + }, +}; diff --git a/spec/frontend/vue_shared/components/list_selector/user_spec.js b/spec/frontend/vue_shared/components/list_selector/user_spec.js new file mode 100644 index 00000000000..5ce21f6672b --- /dev/null +++ b/spec/frontend/vue_shared/components/list_selector/user_spec.js @@ -0,0 +1,55 @@ +import { GlAvatar } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import User from '~/vue_shared/components/list_selector/user.vue'; + +describe('User spec', () => { + let wrapper; + + const MOCK_USER = { name: 'Admin', username: 'root', avatarUrl: 'some/avatar.jpg' }; + + const createComponent = (props) => { + wrapper = mountExtended(User, { + propsData: { + data: MOCK_USER, + ...props, + }, + }); + }; + + const findAvatar = () => wrapper.findComponent(GlAvatar); + const findDeleteButton = () => wrapper.findByRole('button', { name: 'Delete Admin' }); + + beforeEach(() => createComponent()); + + it('renders an Avatar component', () => { + expect(findAvatar().props('size')).toBe(32); + expect(findAvatar().attributes()).toMatchObject({ + src: MOCK_USER.avatarUrl, + alt: MOCK_USER.name, + }); + }); + + it('renders a name and username', () => { + expect(wrapper.text()).toContain('Admin'); + expect(wrapper.text()).toContain('@root'); + }); + + it('does not render a delete button by default', () => { + expect(findDeleteButton().exists()).toBe(false); + }); + + describe('Delete button', () => { + beforeEach(() => createComponent({ canDelete: true })); + + it('renders a delete button', () => { + expect(findDeleteButton().exists()).toBe(true); + expect(findDeleteButton().props('icon')).toBe('remove'); + }); + + it('emits a delete event if the delete button is clicked', () => { + findDeleteButton().trigger('click'); + + expect(wrapper.emitted('delete')).toEqual([[MOCK_USER.username]]); + }); + }); +}); |