diff options
Diffstat (limited to 'spec/frontend/user_lists')
12 files changed, 1044 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); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/edit/actions_spec.js b/spec/frontend/user_lists/store/edit/actions_spec.js new file mode 100644 index 00000000000..7f0fb8e5401 --- /dev/null +++ b/spec/frontend/user_lists/store/edit/actions_spec.js @@ -0,0 +1,121 @@ +import testAction from 'helpers/vuex_action_helper'; +import Api from '~/api'; +import createState from '~/user_lists/store/edit/state'; +import * as types from '~/user_lists/store/edit/mutation_types'; +import * as actions from '~/user_lists/store/edit/actions'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { userList } from '../../../feature_flags/mock_data'; + +jest.mock('~/api'); +jest.mock('~/lib/utils/url_utility'); + +describe('User Lists Edit Actions', () => { + let state; + + beforeEach(() => { + state = createState({ projectId: '1', userListIid: '2' }); + }); + + describe('fetchUserList', () => { + describe('success', () => { + beforeEach(() => { + Api.fetchFeatureFlagUserList.mockResolvedValue({ data: userList }); + }); + + it('should commit RECEIVE_USER_LIST_SUCCESS', () => { + return testAction( + actions.fetchUserList, + undefined, + state, + [ + { type: types.REQUEST_USER_LIST }, + { type: types.RECEIVE_USER_LIST_SUCCESS, payload: userList }, + ], + [], + () => expect(Api.fetchFeatureFlagUserList).toHaveBeenCalledWith('1', '2'), + ); + }); + }); + + describe('error', () => { + let error; + beforeEach(() => { + error = { response: { data: { message: ['error'] } } }; + Api.fetchFeatureFlagUserList.mockRejectedValue(error); + }); + + it('should commit RECEIVE_USER_LIST_ERROR', () => { + return testAction( + actions.fetchUserList, + undefined, + state, + [ + { type: types.REQUEST_USER_LIST }, + { type: types.RECEIVE_USER_LIST_ERROR, payload: ['error'] }, + ], + [], + () => expect(Api.fetchFeatureFlagUserList).toHaveBeenCalledWith('1', '2'), + ); + }); + }); + }); + + describe('dismissErrorAlert', () => { + it('should commit DISMISS_ERROR_ALERT', () => { + return testAction(actions.dismissErrorAlert, undefined, state, [ + { type: types.DISMISS_ERROR_ALERT }, + ]); + }); + }); + + describe('updateUserList', () => { + let updatedList; + + beforeEach(() => { + updatedList = { + ...userList, + name: 'new', + }; + }); + describe('success', () => { + beforeEach(() => { + Api.updateFeatureFlagUserList.mockResolvedValue({ data: userList }); + state.userList = userList; + }); + + it('should commit RECEIVE_USER_LIST_SUCCESS', () => { + return testAction(actions.updateUserList, updatedList, state, [], [], () => { + expect(Api.updateFeatureFlagUserList).toHaveBeenCalledWith('1', { + name: updatedList.name, + iid: updatedList.iid, + }); + expect(redirectTo).toHaveBeenCalledWith(userList.path); + }); + }); + }); + + describe('error', () => { + let error; + + beforeEach(() => { + error = { message: 'error' }; + Api.updateFeatureFlagUserList.mockRejectedValue(error); + }); + + it('should commit RECEIVE_USER_LIST_ERROR', () => { + return testAction( + actions.updateUserList, + updatedList, + state, + [{ type: types.RECEIVE_USER_LIST_ERROR, payload: ['error'] }], + [], + () => + expect(Api.updateFeatureFlagUserList).toHaveBeenCalledWith('1', { + name: updatedList.name, + iid: updatedList.iid, + }), + ); + }); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/edit/mutations_spec.js b/spec/frontend/user_lists/store/edit/mutations_spec.js new file mode 100644 index 00000000000..3d4d2a59717 --- /dev/null +++ b/spec/frontend/user_lists/store/edit/mutations_spec.js @@ -0,0 +1,61 @@ +import statuses from '~/user_lists/constants/edit'; +import createState from '~/user_lists/store/edit/state'; +import * as types from '~/user_lists/store/edit/mutation_types'; +import mutations from '~/user_lists/store/edit/mutations'; +import { userList } from '../../../feature_flags/mock_data'; + +describe('User List Edit Mutations', () => { + let state; + + beforeEach(() => { + state = createState({ projectId: '1', userListIid: '2' }); + }); + + describe(types.REQUEST_USER_LIST, () => { + beforeEach(() => { + mutations[types.REQUEST_USER_LIST](state); + }); + + it('sets the state to loading', () => { + expect(state.status).toBe(statuses.LOADING); + }); + }); + + describe(types.RECEIVE_USER_LIST_SUCCESS, () => { + beforeEach(() => { + mutations[types.RECEIVE_USER_LIST_SUCCESS](state, userList); + }); + + it('sets the state to success', () => { + expect(state.status).toBe(statuses.SUCCESS); + }); + + it('sets the user list to the one received', () => { + expect(state.userList).toEqual(userList); + }); + }); + + describe(types.RECIEVE_USER_LIST_ERROR, () => { + beforeEach(() => { + mutations[types.RECEIVE_USER_LIST_ERROR](state, ['network error']); + }); + + it('sets the state to error', () => { + expect(state.status).toBe(statuses.ERROR); + }); + + it('sets the error message to the recieved one', () => { + expect(state.errorMessage).toEqual(['network error']); + }); + }); + + describe(types.DISMISS_ERROR_ALERT, () => { + beforeEach(() => { + mutations[types.DISMISS_ERROR_ALERT](state); + }); + + it('sets the state to error dismissed', () => { + expect(state.status).toBe(statuses.UNSYNCED); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/new/actions_spec.js b/spec/frontend/user_lists/store/new/actions_spec.js new file mode 100644 index 00000000000..9cc6212a125 --- /dev/null +++ b/spec/frontend/user_lists/store/new/actions_spec.js @@ -0,0 +1,69 @@ +import testAction from 'helpers/vuex_action_helper'; +import Api from '~/api'; +import createState from '~/user_lists/store/new/state'; +import * as types from '~/user_lists/store/new/mutation_types'; +import * as actions from '~/user_lists/store/new/actions'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { userList } from '../../../feature_flags/mock_data'; + +jest.mock('~/api'); +jest.mock('~/lib/utils/url_utility'); + +describe('User Lists Edit Actions', () => { + let state; + + beforeEach(() => { + state = createState({ projectId: '1' }); + }); + + describe('dismissErrorAlert', () => { + it('should commit DISMISS_ERROR_ALERT', () => { + return testAction(actions.dismissErrorAlert, undefined, state, [ + { type: types.DISMISS_ERROR_ALERT }, + ]); + }); + }); + + describe('createUserList', () => { + let createdList; + + beforeEach(() => { + createdList = { + ...userList, + name: 'new', + }; + }); + describe('success', () => { + beforeEach(() => { + Api.createFeatureFlagUserList.mockResolvedValue({ data: userList }); + }); + + it('should redirect to the user list page', () => { + return testAction(actions.createUserList, createdList, state, [], [], () => { + expect(Api.createFeatureFlagUserList).toHaveBeenCalledWith('1', createdList); + expect(redirectTo).toHaveBeenCalledWith(userList.path); + }); + }); + }); + + describe('error', () => { + let error; + + beforeEach(() => { + error = { message: 'error' }; + Api.createFeatureFlagUserList.mockRejectedValue(error); + }); + + it('should commit RECEIVE_USER_LIST_ERROR', () => { + return testAction( + actions.createUserList, + createdList, + state, + [{ type: types.RECEIVE_CREATE_USER_LIST_ERROR, payload: ['error'] }], + [], + () => expect(Api.createFeatureFlagUserList).toHaveBeenCalledWith('1', createdList), + ); + }); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/new/mutations_spec.js b/spec/frontend/user_lists/store/new/mutations_spec.js new file mode 100644 index 00000000000..89e8a83eb25 --- /dev/null +++ b/spec/frontend/user_lists/store/new/mutations_spec.js @@ -0,0 +1,38 @@ +import createState from '~/user_lists/store/new/state'; +import * as types from '~/user_lists/store/new/mutation_types'; +import mutations from '~/user_lists/store/new/mutations'; + +describe('User List Edit Mutations', () => { + let state; + + beforeEach(() => { + state = createState({ projectId: '1' }); + }); + + describe(types.RECIEVE_USER_LIST_ERROR, () => { + beforeEach(() => { + mutations[types.RECEIVE_CREATE_USER_LIST_ERROR](state, ['network error']); + }); + + it('sets the error message to the recieved one', () => { + expect(state.errorMessage).toEqual(['network error']); + }); + + it('sets the error message to the recevied API message if present', () => { + const message = ['name is blank', 'name is too short']; + + mutations[types.RECEIVE_CREATE_USER_LIST_ERROR](state, message); + expect(state.errorMessage).toEqual(message); + }); + }); + + describe(types.DISMISS_ERROR_ALERT, () => { + beforeEach(() => { + mutations[types.DISMISS_ERROR_ALERT](state); + }); + + it('clears the error message', () => { + expect(state.errorMessage).toBe(''); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/show/actions_spec.js b/spec/frontend/user_lists/store/show/actions_spec.js new file mode 100644 index 00000000000..25a6b9ec0e4 --- /dev/null +++ b/spec/frontend/user_lists/store/show/actions_spec.js @@ -0,0 +1,117 @@ +import testAction from 'helpers/vuex_action_helper'; +import { userList } from 'jest/feature_flags/mock_data'; +import Api from '~/api'; +import { stringifyUserIds } from '~/user_lists/store/utils'; +import createState from '~/user_lists/store/show/state'; +import * as types from '~/user_lists/store/show/mutation_types'; +import * as actions from '~/user_lists/store/show/actions'; + +jest.mock('~/api'); + +describe('User Lists Show Actions', () => { + let mockState; + + beforeEach(() => { + mockState = createState({ projectId: '1', userListIid: '2' }); + }); + + describe('fetchUserList', () => { + it('commits REQUEST_USER_LIST and RECEIVE_USER_LIST_SUCCESS on success', () => { + Api.fetchFeatureFlagUserList.mockResolvedValue({ data: userList }); + return testAction( + actions.fetchUserList, + undefined, + mockState, + [ + { type: types.REQUEST_USER_LIST }, + { type: types.RECEIVE_USER_LIST_SUCCESS, payload: userList }, + ], + [], + () => expect(Api.fetchFeatureFlagUserList).toHaveBeenCalledWith('1', '2'), + ); + }); + + it('commits REQUEST_USER_LIST and RECEIVE_USER_LIST_ERROR on error', () => { + Api.fetchFeatureFlagUserList.mockRejectedValue({ message: 'fail' }); + return testAction( + actions.fetchUserList, + undefined, + mockState, + [{ type: types.REQUEST_USER_LIST }, { type: types.RECEIVE_USER_LIST_ERROR }], + [], + ); + }); + }); + + describe('dismissErrorAlert', () => { + it('commits DISMISS_ERROR_ALERT', () => { + return testAction( + actions.dismissErrorAlert, + undefined, + mockState, + [{ type: types.DISMISS_ERROR_ALERT }], + [], + ); + }); + }); + + describe('addUserIds', () => { + it('adds the given IDs and tries to update the user list', () => { + return testAction( + actions.addUserIds, + '1,2,3', + mockState, + [{ type: types.ADD_USER_IDS, payload: '1,2,3' }], + [{ type: 'updateUserList' }], + ); + }); + }); + + describe('removeUserId', () => { + it('removes the given ID and tries to update the user list', () => { + return testAction( + actions.removeUserId, + 'user3', + mockState, + [{ type: types.REMOVE_USER_ID, payload: 'user3' }], + [{ type: 'updateUserList' }], + ); + }); + }); + + describe('updateUserList', () => { + beforeEach(() => { + mockState.userList = userList; + mockState.userIds = ['user1', 'user2', 'user3']; + }); + + it('commits REQUEST_USER_LIST and RECEIVE_USER_LIST_SUCCESS on success', () => { + Api.updateFeatureFlagUserList.mockResolvedValue({ data: userList }); + return testAction( + actions.updateUserList, + undefined, + mockState, + [ + { type: types.REQUEST_USER_LIST }, + { type: types.RECEIVE_USER_LIST_SUCCESS, payload: userList }, + ], + [], + () => + expect(Api.updateFeatureFlagUserList).toHaveBeenCalledWith('1', { + ...userList, + user_xids: stringifyUserIds(mockState.userIds), + }), + ); + }); + it('commits REQUEST_USER_LIST and RECEIVE_USER_LIST_ERROR on error', () => { + Api.updateFeatureFlagUserList.mockRejectedValue({ message: 'fail' }); + return testAction( + actions.updateUserList, + undefined, + mockState, + [{ type: types.REQUEST_USER_LIST }, { type: types.RECEIVE_USER_LIST_ERROR }], + [], + ); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/show/mutations_spec.js b/spec/frontend/user_lists/store/show/mutations_spec.js new file mode 100644 index 00000000000..364cc6a0225 --- /dev/null +++ b/spec/frontend/user_lists/store/show/mutations_spec.js @@ -0,0 +1,86 @@ +import { uniq } from 'lodash'; +import { userList } from 'jest/feature_flags/mock_data'; +import createState from '~/user_lists/store/show/state'; +import mutations from '~/user_lists/store/show/mutations'; +import { states } from '~/user_lists/constants/show'; +import * as types from '~/user_lists/store/show/mutation_types'; + +describe('User Lists Show Mutations', () => { + let mockState; + + beforeEach(() => { + mockState = createState({ projectId: '1', userListIid: '2' }); + }); + + describe(types.REQUEST_USER_LIST, () => { + it('puts us in the loading state', () => { + mutations[types.REQUEST_USER_LIST](mockState); + + expect(mockState.state).toBe(states.LOADING); + }); + }); + + describe(types.RECEIVE_USER_LIST_SUCCESS, () => { + beforeEach(() => { + mutations[types.RECEIVE_USER_LIST_SUCCESS](mockState, userList); + }); + + it('sets the state to LOADED', () => { + expect(mockState.state).toBe(states.SUCCESS); + }); + + it('sets the active user list', () => { + expect(mockState.userList).toEqual(userList); + }); + + it('splits the user IDs into an Array', () => { + expect(mockState.userIds).toEqual(userList.user_xids.split(',')); + }); + + it('sets user IDs to an empty Array if an empty string is received', () => { + mutations[types.RECEIVE_USER_LIST_SUCCESS](mockState, { ...userList, user_xids: '' }); + expect(mockState.userIds).toEqual([]); + }); + }); + describe(types.RECEIVE_USER_LIST_ERROR, () => { + it('sets the state to error', () => { + mutations[types.RECEIVE_USER_LIST_ERROR](mockState); + expect(mockState.state).toBe(states.ERROR); + }); + }); + describe(types.ADD_USER_IDS, () => { + const newIds = ['user3', 'test1', '1', '3', '']; + + beforeEach(() => { + mutations[types.RECEIVE_USER_LIST_SUCCESS](mockState, userList); + mutations[types.ADD_USER_IDS](mockState, newIds.join(', ')); + }); + + it('adds the new IDs to the state unless empty', () => { + newIds.filter(id => id).forEach(id => expect(mockState.userIds).toContain(id)); + }); + + it('does not add duplicate IDs to the state', () => { + expect(mockState.userIds).toEqual(uniq(mockState.userIds)); + }); + }); + describe(types.REMOVE_USER_ID, () => { + let userIds; + let removedId; + + beforeEach(() => { + mutations[types.RECEIVE_USER_LIST_SUCCESS](mockState, userList); + userIds = mockState.userIds; + removedId = 'user3'; + mutations[types.REMOVE_USER_ID](mockState, removedId); + }); + + it('should remove the given id', () => { + expect(mockState).not.toContain(removedId); + }); + + it('should leave the rest of the IDs alone', () => { + userIds.filter(id => id !== removedId).forEach(id => expect(mockState.userIds).toContain(id)); + }); + }); +}); diff --git a/spec/frontend/user_lists/store/utils_spec.js b/spec/frontend/user_lists/store/utils_spec.js new file mode 100644 index 00000000000..9547b463eec --- /dev/null +++ b/spec/frontend/user_lists/store/utils_spec.js @@ -0,0 +1,23 @@ +import { parseUserIds, stringifyUserIds } from '~/user_lists/store/utils'; + +describe('User List Store Utils', () => { + describe('parseUserIds', () => { + it('should split comma-seperated user IDs into an array', () => { + expect(parseUserIds('1,2,3')).toEqual(['1', '2', '3']); + }); + + it('should filter whitespace before the comma', () => { + expect(parseUserIds('1\t,2 ,3')).toEqual(['1', '2', '3']); + }); + + it('should filter whitespace after the comma', () => { + expect(parseUserIds('1,\t2, 3')).toEqual(['1', '2', '3']); + }); + }); + + describe('stringifyUserIds', () => { + it('should convert a list of user IDs into a comma-separated string', () => { + expect(stringifyUserIds(['1', '2', '3'])).toBe('1,2,3'); + }); + }); +}); |