diff options
Diffstat (limited to 'spec/frontend/user_lists/components')
5 files changed, 529 insertions, 0 deletions
diff --git a/spec/frontend/user_lists/components/add_user_modal_spec.js b/spec/frontend/user_lists/components/add_user_modal_spec.js new file mode 100644 index 00000000000..82ce195d7cd --- /dev/null +++ b/spec/frontend/user_lists/components/add_user_modal_spec.js @@ -0,0 +1,50 @@ +import { mount } from '@vue/test-utils'; +import AddUserModal from '~/user_lists/components/add_user_modal.vue'; + +describe('Add User Modal', () => { + let wrapper; + + const click = testId => wrapper.find(`[data-testid="${testId}"]`).trigger('click'); + + beforeEach(() => { + wrapper = mount(AddUserModal, { + propsData: { visible: true }, + }); + }); + + it('should explain the format of user IDs to enter', () => { + expect(wrapper.find('[data-testid="add-userids-description"]').text()).toContain( + 'Enter a comma separated list of user IDs', + ); + }); + + describe('events', () => { + beforeEach(() => { + wrapper.find('#add-user-ids').setValue('1, 2, 3, 4'); + }); + + it('should emit the users entered when Add Users is clicked', () => { + click('confirm-add-user-ids'); + expect(wrapper.emitted('addUsers')).toContainEqual(['1, 2, 3, 4']); + }); + + it('should clear the input after emitting', async () => { + click('confirm-add-user-ids'); + await wrapper.vm.$nextTick(); + + expect(wrapper.find('#add-user-ids').element.value).toBe(''); + }); + + it('should not emit the users entered if cancel is clicked', () => { + click('cancel-add-user-ids'); + expect(wrapper.emitted('addUsers')).toBeUndefined(); + }); + + it('should clear the input after cancelling', async () => { + click('cancel-add-user-ids'); + await wrapper.vm.$nextTick(); + + expect(wrapper.find('#add-user-ids').element.value).toBe(''); + }); + }); +}); diff --git a/spec/frontend/user_lists/components/edit_user_list_spec.js b/spec/frontend/user_lists/components/edit_user_list_spec.js new file mode 100644 index 00000000000..51a38e12916 --- /dev/null +++ b/spec/frontend/user_lists/components/edit_user_list_spec.js @@ -0,0 +1,150 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import { createLocalVue, mount } from '@vue/test-utils'; +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; +import Api from '~/api'; +import createStore from '~/user_lists/store/edit'; +import EditUserList from '~/user_lists/components/edit_user_list.vue'; +import UserListForm from '~/user_lists/components/user_list_form.vue'; +import { userList } from '../../feature_flags/mock_data'; +import { redirectTo } from '~/lib/utils/url_utility'; + +jest.mock('~/api'); +jest.mock('~/lib/utils/url_utility'); + +const localVue = createLocalVue(Vue); +localVue.use(Vuex); + +describe('user_lists/components/edit_user_list', () => { + let wrapper; + + const setInputValue = value => wrapper.find('[data-testid="user-list-name"]').setValue(value); + + const click = button => wrapper.find(`[data-testid="${button}"]`).trigger('click'); + const clickSave = () => click('save-user-list'); + + const destroy = () => wrapper?.destroy(); + + const factory = () => { + destroy(); + + wrapper = mount(EditUserList, { + localVue, + store: createStore({ projectId: '1', userListIid: '2' }), + provide: { + userListsDocsPath: '/docs/user_lists', + }, + }); + }; + + afterEach(() => { + destroy(); + }); + + describe('loading', () => { + beforeEach(() => { + Api.fetchFeatureFlagUserList.mockReturnValue(new Promise(() => {})); + factory(); + }); + + it('should show a loading icon', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + }); + + describe('loading error', () => { + const message = 'error creating list'; + let alert; + + beforeEach(async () => { + Api.fetchFeatureFlagUserList.mockRejectedValue({ message }); + factory(); + await waitForPromises(); + + alert = wrapper.find(GlAlert); + }); + + it('should show a flash with the error respopnse', () => { + expect(alert.text()).toContain(message); + }); + + it('should not be dismissible', async () => { + expect(alert.props('dismissible')).toBe(false); + }); + + it('should not show a user list form', () => { + expect(wrapper.find(UserListForm).exists()).toBe(false); + }); + }); + + describe('update', () => { + beforeEach(() => { + Api.fetchFeatureFlagUserList.mockResolvedValue({ data: userList }); + factory(); + + return wrapper.vm.$nextTick(); + }); + + it('should link to the documentation', () => { + const link = wrapper.find('[data-testid="user-list-docs-link"]'); + expect(link.attributes('href')).toBe('/docs/user_lists'); + }); + + it('should link the cancel button to the user list details path', () => { + const link = wrapper.find('[data-testid="user-list-cancel"]'); + expect(link.attributes('href')).toBe(userList.path); + }); + + it('should show the user list name in the title', () => { + expect(wrapper.find('[data-testid="user-list-title"]').text()).toBe(`Edit ${userList.name}`); + }); + + describe('success', () => { + beforeEach(() => { + Api.updateFeatureFlagUserList.mockResolvedValue({ data: userList }); + setInputValue('test'); + clickSave(); + return wrapper.vm.$nextTick(); + }); + + it('should create a user list with the entered name', () => { + expect(Api.updateFeatureFlagUserList).toHaveBeenCalledWith('1', { + name: 'test', + iid: userList.iid, + }); + }); + + it('should redirect to the feature flag details page', () => { + expect(redirectTo).toHaveBeenCalledWith(userList.path); + }); + }); + + describe('error', () => { + let alert; + let message; + + beforeEach(async () => { + message = 'error creating list'; + Api.updateFeatureFlagUserList.mockRejectedValue({ message }); + setInputValue('test'); + clickSave(); + await waitForPromises(); + + alert = wrapper.find(GlAlert); + }); + + it('should show a flash with the error respopnse', () => { + expect(alert.text()).toContain(message); + }); + + it('should dismiss the error if dismiss is clicked', async () => { + alert.find('button').trigger('click'); + + await wrapper.vm.$nextTick(); + + expect(alert.exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/user_lists/components/new_user_list_spec.js b/spec/frontend/user_lists/components/new_user_list_spec.js new file mode 100644 index 00000000000..62fb0ca0859 --- /dev/null +++ b/spec/frontend/user_lists/components/new_user_list_spec.js @@ -0,0 +1,93 @@ +import { mount, createLocalVue } from '@vue/test-utils'; +import Vue from 'vue'; +import Vuex from 'vuex'; +import { GlAlert } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; +import Api from '~/api'; +import createStore from '~/user_lists/store/new'; +import NewUserList from '~/user_lists/components/new_user_list.vue'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { userList } from '../../feature_flags/mock_data'; + +jest.mock('~/api'); +jest.mock('~/lib/utils/url_utility'); + +const localVue = createLocalVue(Vue); +localVue.use(Vuex); + +describe('user_lists/components/new_user_list', () => { + let wrapper; + + const setInputValue = value => wrapper.find('[data-testid="user-list-name"]').setValue(value); + + const click = button => wrapper.find(`[data-testid="${button}"]`).trigger('click'); + + beforeEach(() => { + wrapper = mount(NewUserList, { + localVue, + store: createStore({ projectId: '1' }), + provide: { + featureFlagsPath: '/feature_flags', + userListsDocsPath: '/docs/user_lists', + }, + }); + }); + + it('should link to the documentation', () => { + const link = wrapper.find('[data-testid="user-list-docs-link"]'); + expect(link.attributes('href')).toBe('/docs/user_lists'); + }); + + it('should link the cancel buton back to feature flags', () => { + const cancel = wrapper.find('[data-testid="user-list-cancel"'); + expect(cancel.attributes('href')).toBe('/feature_flags'); + }); + + describe('create', () => { + describe('success', () => { + beforeEach(() => { + Api.createFeatureFlagUserList.mockResolvedValue({ data: userList }); + setInputValue('test'); + click('save-user-list'); + return wrapper.vm.$nextTick(); + }); + + it('should create a user list with the entered name', () => { + expect(Api.createFeatureFlagUserList).toHaveBeenCalledWith('1', { + name: 'test', + user_xids: '', + }); + }); + + it('should redirect to the feature flag details page', () => { + expect(redirectTo).toHaveBeenCalledWith(userList.path); + }); + }); + + describe('error', () => { + let alert; + + beforeEach(async () => { + Api.createFeatureFlagUserList.mockRejectedValue({ message: 'error creating list' }); + setInputValue('test'); + click('save-user-list'); + + await waitForPromises(); + + alert = wrapper.find(GlAlert); + }); + + it('should show a flash with the error respopnse', () => { + expect(alert.text()).toContain('error creating list'); + }); + + it('should dismiss the error when the dismiss button is clicked', async () => { + alert.find('button').trigger('click'); + + await wrapper.vm.$nextTick(); + + expect(alert.exists()).toBe(false); + }); + }); + }); +}); diff --git a/spec/frontend/user_lists/components/user_list_form_spec.js b/spec/frontend/user_lists/components/user_list_form_spec.js new file mode 100644 index 00000000000..42f7659600e --- /dev/null +++ b/spec/frontend/user_lists/components/user_list_form_spec.js @@ -0,0 +1,40 @@ +import { mount } from '@vue/test-utils'; +import Form from '~/user_lists/components/user_list_form.vue'; +import { userList } from '../../feature_flags/mock_data'; + +describe('user_lists/components/user_list_form', () => { + let wrapper; + let input; + + beforeEach(() => { + wrapper = mount(Form, { + propsData: { + cancelPath: '/cancel', + saveButtonLabel: 'Save', + userListsDocsPath: '/docs', + userList, + }, + }); + + input = wrapper.find('[data-testid="user-list-name"]'); + }); + + it('should set the name to the name of the given user list', () => { + expect(input.element.value).toBe(userList.name); + }); + + it('should link to the user lists docs', () => { + expect(wrapper.find('[data-testid="user-list-docs-link"]').attributes('href')).toBe('/docs'); + }); + + it('should emit an updated user list when save is clicked', () => { + input.setValue('test'); + wrapper.find('[data-testid="save-user-list"]').trigger('click'); + + expect(wrapper.emitted('submit')).toEqual([[{ ...userList, name: 'test' }]]); + }); + + it('should set the cancel button to the passed url', () => { + expect(wrapper.find('[data-testid="user-list-cancel"]').attributes('href')).toBe('/cancel'); + }); +}); diff --git a/spec/frontend/user_lists/components/user_list_spec.js b/spec/frontend/user_lists/components/user_list_spec.js new file mode 100644 index 00000000000..5f9b7967846 --- /dev/null +++ b/spec/frontend/user_lists/components/user_list_spec.js @@ -0,0 +1,196 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import { mount } from '@vue/test-utils'; +import { uniq } from 'lodash'; +import { GlAlert, GlEmptyState, GlLoadingIcon } from '@gitlab/ui'; +import Api from '~/api'; +import { parseUserIds, stringifyUserIds } from '~/user_lists/store/utils'; +import createStore from '~/user_lists/store/show'; +import UserList from '~/user_lists/components/user_list.vue'; +import { userList } from '../../feature_flags/mock_data'; + +jest.mock('~/api'); + +Vue.use(Vuex); + +describe('User List', () => { + let wrapper; + + const click = testId => wrapper.find(`[data-testid="${testId}"]`).trigger('click'); + + const findUserIds = () => wrapper.findAll('[data-testid="user-id"]'); + + const destroy = () => wrapper?.destroy(); + + const factory = () => { + destroy(); + + wrapper = mount(UserList, { + store: createStore({ projectId: '1', userListIid: '2' }), + propsData: { + emptyStatePath: '/empty_state.svg', + }, + }); + }; + + describe('loading', () => { + let resolveFn; + + beforeEach(() => { + Api.fetchFeatureFlagUserList.mockReturnValue( + new Promise(resolve => { + resolveFn = resolve; + }), + ); + factory(); + }); + + afterEach(() => { + resolveFn(); + }); + + it('shows a loading icon', () => { + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + }); + + describe('success', () => { + let userIds; + + beforeEach(() => { + userIds = parseUserIds(userList.user_xids); + Api.fetchFeatureFlagUserList.mockResolvedValueOnce({ data: userList }); + factory(); + + return wrapper.vm.$nextTick(); + }); + + it('requests the user list on mount', () => { + expect(Api.fetchFeatureFlagUserList).toHaveBeenCalledWith('1', '2'); + }); + + it('shows the list name', () => { + expect(wrapper.find('h3').text()).toBe(userList.name); + }); + + it('shows an add users button', () => { + expect(wrapper.find('[data-testid="add-users"]').text()).toBe('Add Users'); + }); + + it('shows an edit list button', () => { + expect(wrapper.find('[data-testid="edit-user-list"]').text()).toBe('Edit'); + }); + + it('shows a row for every id', () => { + expect(wrapper.findAll('[data-testid="user-id-row"]')).toHaveLength(userIds.length); + }); + + it('shows one id on each row', () => { + findUserIds().wrappers.forEach((w, i) => expect(w.text()).toBe(userIds[i])); + }); + + it('shows a delete button for every row', () => { + expect(wrapper.findAll('[data-testid="delete-user-id"]')).toHaveLength(userIds.length); + }); + + describe('adding users', () => { + const newIds = ['user3', 'user4', 'user5', 'test', 'example', 'foo']; + let receivedUserIds; + let parsedReceivedUserIds; + + beforeEach(async () => { + Api.updateFeatureFlagUserList.mockResolvedValue(userList); + click('add-users'); + await wrapper.vm.$nextTick(); + wrapper.find('#add-user-ids').setValue(`${stringifyUserIds(newIds)},`); + click('confirm-add-user-ids'); + await wrapper.vm.$nextTick(); + [[, { user_xids: receivedUserIds }]] = Api.updateFeatureFlagUserList.mock.calls; + parsedReceivedUserIds = parseUserIds(receivedUserIds); + }); + + it('should add user IDs to the user list', () => { + newIds.forEach(id => expect(receivedUserIds).toContain(id)); + }); + + it('should not remove existing user ids', () => { + userIds.forEach(id => expect(receivedUserIds).toContain(id)); + }); + + it('should not submit empty IDs', () => { + parsedReceivedUserIds.forEach(id => expect(id).not.toBe('')); + }); + + it('should not create duplicate entries', () => { + expect(uniq(parsedReceivedUserIds)).toEqual(parsedReceivedUserIds); + }); + + it('should display the new IDs', () => { + const userIdWrappers = findUserIds(); + newIds.forEach(id => { + const userIdWrapper = userIdWrappers.wrappers.find(w => w.text() === id); + expect(userIdWrapper.exists()).toBe(true); + }); + }); + }); + + describe('deleting users', () => { + let receivedUserIds; + + beforeEach(async () => { + Api.updateFeatureFlagUserList.mockResolvedValue(userList); + click('delete-user-id'); + await wrapper.vm.$nextTick(); + [[, { user_xids: receivedUserIds }]] = Api.updateFeatureFlagUserList.mock.calls; + }); + + it('should remove the ID clicked', () => { + expect(receivedUserIds).not.toContain(userIds[0]); + }); + + it('should not display the deleted user', () => { + const userIdWrappers = findUserIds(); + const userIdWrapper = userIdWrappers.wrappers.find(w => w.text() === userIds[0]); + expect(userIdWrapper).toBeUndefined(); + }); + }); + }); + + describe('error', () => { + const findAlert = () => wrapper.find(GlAlert); + + beforeEach(() => { + Api.fetchFeatureFlagUserList.mockRejectedValue(); + factory(); + + return wrapper.vm.$nextTick(); + }); + + it('displays the alert message', () => { + const alert = findAlert(); + expect(alert.text()).toBe('Something went wrong on our end. Please try again!'); + }); + + it('can dismiss the alert', async () => { + const alert = findAlert(); + alert.find('button').trigger('click'); + + await wrapper.vm.$nextTick(); + + expect(alert.exists()).toBe(false); + }); + }); + + describe('empty list', () => { + beforeEach(() => { + Api.fetchFeatureFlagUserList.mockResolvedValueOnce({ data: { ...userList, user_xids: '' } }); + factory(); + + return wrapper.vm.$nextTick(); + }); + + it('displays an empty state', () => { + expect(wrapper.find(GlEmptyState).exists()).toBe(true); + }); + }); +}); |