diff options
Diffstat (limited to 'spec/frontend/deploy_keys/components')
-rw-r--r-- | spec/frontend/deploy_keys/components/action_btn_spec.js | 43 | ||||
-rw-r--r-- | spec/frontend/deploy_keys/components/app_spec.js | 244 | ||||
-rw-r--r-- | spec/frontend/deploy_keys/components/key_spec.js | 154 | ||||
-rw-r--r-- | spec/frontend/deploy_keys/components/keys_panel_spec.js | 13 |
4 files changed, 331 insertions, 123 deletions
diff --git a/spec/frontend/deploy_keys/components/action_btn_spec.js b/spec/frontend/deploy_keys/components/action_btn_spec.js index c4c7a9aea2d..e94734da4ce 100644 --- a/spec/frontend/deploy_keys/components/action_btn_spec.js +++ b/spec/frontend/deploy_keys/components/action_btn_spec.js @@ -1,28 +1,44 @@ +import VueApollo from 'vue-apollo'; +import Vue, { nextTick } from 'vue'; import { GlButton } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; -import { nextTick } from 'vue'; import data from 'test_fixtures/deploy_keys/keys.json'; +import waitForPromises from 'helpers/wait_for_promises'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import enableKeyMutation from '~/deploy_keys/graphql/mutations/enable_key.mutation.graphql'; import actionBtn from '~/deploy_keys/components/action_btn.vue'; -import eventHub from '~/deploy_keys/eventhub'; + +Vue.use(VueApollo); describe('Deploy keys action btn', () => { const deployKey = data.enabled_keys[0]; let wrapper; + let enableKeyMock; const findButton = () => wrapper.findComponent(GlButton); beforeEach(() => { + enableKeyMock = jest.fn(); + + const mockResolvers = { + Mutation: { + enableKey: enableKeyMock, + }, + }; + + const apolloProvider = createMockApollo([], mockResolvers); wrapper = shallowMount(actionBtn, { propsData: { deployKey, - type: 'enable', category: 'primary', variant: 'confirm', icon: 'edit', + mutation: enableKeyMutation, }, slots: { default: 'Enable', }, + apolloProvider, }); }); @@ -38,13 +54,26 @@ describe('Deploy keys action btn', () => { }); }); - it('sends eventHub event with btn type', async () => { - jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); - + it('fires the passed mutation', async () => { findButton().vm.$emit('click'); await nextTick(); - expect(eventHub.$emit).toHaveBeenCalledWith('enable.key', deployKey, expect.anything()); + expect(enableKeyMock).toHaveBeenCalledWith( + expect.anything(), + { id: deployKey.id }, + expect.anything(), + expect.anything(), + ); + }); + + it('emits the mutation error', async () => { + const error = new Error('oops!'); + enableKeyMock.mockRejectedValue(error); + findButton().vm.$emit('click'); + + await waitForPromises(); + + expect(wrapper.emitted('error')).toEqual([[error]]); }); it('shows loading spinner after click', async () => { diff --git a/spec/frontend/deploy_keys/components/app_spec.js b/spec/frontend/deploy_keys/components/app_spec.js index de4112134ce..5e012bc1c51 100644 --- a/spec/frontend/deploy_keys/components/app_spec.js +++ b/spec/frontend/deploy_keys/components/app_spec.js @@ -1,28 +1,45 @@ +import VueApollo from 'vue-apollo'; +import Vue, { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; -import data from 'test_fixtures/deploy_keys/keys.json'; +import { GlPagination } from '@gitlab/ui'; +import enabledKeys from 'test_fixtures/deploy_keys/enabled_keys.json'; +import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { TEST_HOST } from 'spec/test_constants'; +import { captureException } from '~/sentry/sentry_browser_wrapper'; +import { mapDeployKey } from '~/deploy_keys/graphql/resolvers'; +import deployKeysQuery from '~/deploy_keys/graphql/queries/deploy_keys.query.graphql'; import deployKeysApp from '~/deploy_keys/components/app.vue'; import ConfirmModal from '~/deploy_keys/components/confirm_modal.vue'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; -import eventHub from '~/deploy_keys/eventhub'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; -const TEST_ENDPOINT = `${TEST_HOST}/dummy/`; +jest.mock('~/sentry/sentry_browser_wrapper'); + +Vue.use(VueApollo); describe('Deploy keys app component', () => { let wrapper; let mock; + let deployKeyMock; + let currentPageMock; + let currentScopeMock; + let confirmRemoveKeyMock; + let pageInfoMock; + let pageMutationMock; + let scopeMutationMock; + let disableKeyMock; + let resolvers; const mountComponent = () => { + const apolloProvider = createMockApollo([[deployKeysQuery, deployKeyMock]], resolvers); + wrapper = mount(deployKeysApp, { propsData: { - endpoint: TEST_ENDPOINT, + projectPath: 'test/project', projectId: '8', }, + apolloProvider, }); return waitForPromises(); @@ -30,7 +47,28 @@ describe('Deploy keys app component', () => { beforeEach(() => { mock = new MockAdapter(axios); - mock.onGet(TEST_ENDPOINT).reply(HTTP_STATUS_OK, data); + deployKeyMock = jest.fn(); + currentPageMock = jest.fn(); + currentScopeMock = jest.fn(); + confirmRemoveKeyMock = jest.fn(); + pageInfoMock = jest.fn(); + scopeMutationMock = jest.fn(); + pageMutationMock = jest.fn(); + disableKeyMock = jest.fn(); + + resolvers = { + Query: { + currentPage: currentPageMock, + currentScope: currentScopeMock, + deployKeyToRemove: confirmRemoveKeyMock, + pageInfo: pageInfoMock, + }, + Mutation: { + currentPage: pageMutationMock, + currentScope: scopeMutationMock, + disableKey: disableKeyMock, + }, + }; }); afterEach(() => { @@ -43,8 +81,7 @@ describe('Deploy keys app component', () => { const findNavigationTabs = () => wrapper.findComponent(NavigationTabs); it('renders loading icon while waiting for request', async () => { - mock.onGet(TEST_ENDPOINT).reply(() => new Promise()); - + deployKeyMock.mockReturnValue(new Promise(() => {})); mountComponent(); await nextTick(); @@ -52,85 +89,190 @@ describe('Deploy keys app component', () => { }); it('renders keys panels', async () => { + const deployKeys = enabledKeys.keys.map(mapDeployKey); + deployKeyMock.mockReturnValue({ + data: { + project: { id: 1, deployKeys, __typename: 'Project' }, + }, + }); await mountComponent(); expect(findKeyPanels().length).toBe(3); }); - it.each` - selector - ${'.js-deployKeys-tab-enabled_keys'} - ${'.js-deployKeys-tab-available_project_keys'} - ${'.js-deployKeys-tab-public_keys'} - `('$selector title exists', ({ selector }) => { - return mountComponent().then(() => { + describe.each` + scope + ${'enabledKeys'} + ${'availableProjectKeys'} + ${'availablePublicKeys'} + `('tab $scope', ({ scope }) => { + let selector; + + beforeEach(async () => { + selector = `.js-deployKeys-tab-${scope}`; + const deployKeys = enabledKeys.keys.map(mapDeployKey); + deployKeyMock.mockReturnValue({ + data: { + project: { id: 1, deployKeys, __typename: 'Project' }, + }, + }); + + await mountComponent(); + }); + + it('displays the title', () => { const element = wrapper.find(selector); expect(element.exists()).toBe(true); }); + + it('triggers changing the scope on click', async () => { + await findNavigationTabs().vm.$emit('onChangeTab', scope); + + expect(scopeMutationMock).toHaveBeenCalledWith( + expect.anything(), + { scope }, + expect.anything(), + expect.anything(), + ); + }); }); - it('does not render key panels when keys object is empty', () => { - mock.onGet(TEST_ENDPOINT).reply(HTTP_STATUS_OK, []); + it('captures a failed tab change', async () => { + const scope = 'fake scope'; + const error = new Error('fail!'); - return mountComponent().then(() => { - expect(findKeyPanels().length).toBe(0); + const deployKeys = enabledKeys.keys.map(mapDeployKey); + deployKeyMock.mockReturnValue({ + data: { + project: { id: 1, deployKeys, __typename: 'Project' }, + }, }); + + scopeMutationMock.mockRejectedValue(error); + await mountComponent(); + await findNavigationTabs().vm.$emit('onChangeTab', scope); + await waitForPromises(); + + expect(captureException).toHaveBeenCalledWith(error, { tags: { deployKeyScope: scope } }); }); it('hasKeys returns true when there are keys', async () => { + const deployKeys = enabledKeys.keys.map(mapDeployKey); + deployKeyMock.mockReturnValue({ + data: { + project: { id: 1, deployKeys, __typename: 'Project' }, + }, + }); await mountComponent(); expect(findNavigationTabs().exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(false); }); - describe('enabling and disabling keys', () => { - const key = data.public_keys[0]; - let getMethodMock; - let putMethodMock; + describe('disabling keys', () => { + const key = mapDeployKey(enabledKeys.keys[0]); + + beforeEach(() => { + deployKeyMock.mockReturnValue({ + data: { + project: { id: 1, deployKeys: [key], __typename: 'Project' }, + }, + }); + }); - const removeKey = async (keyEvent) => { - eventHub.$emit(keyEvent, key, () => {}); + it('re-fetches deploy keys when disabling a key', async () => { + confirmRemoveKeyMock.mockReturnValue(key); + await mountComponent(); + expect(deployKeyMock).toHaveBeenCalledTimes(1); await nextTick(); expect(findModal().props('visible')).toBe(true); findModal().vm.$emit('remove'); - }; - - beforeEach(() => { - getMethodMock = jest.spyOn(axios, 'get'); - putMethodMock = jest.spyOn(axios, 'put'); + await waitForPromises(); + expect(deployKeyMock).toHaveBeenCalledTimes(2); }); + }); - afterEach(() => { - getMethodMock.mockClear(); - putMethodMock.mockClear(); - }); + describe('pagination', () => { + const key = mapDeployKey(enabledKeys.keys[0]); + let page; + let pageInfo; + let glPagination; - it('re-fetches deploy keys when enabling a key', async () => { - await mountComponent(); + beforeEach(async () => { + page = 2; + pageInfo = { + total: 20, + perPage: 5, + nextPage: 3, + page, + previousPage: 1, + __typename: 'LocalPageInfo', + }; + deployKeyMock.mockReturnValue({ + data: { + project: { id: 1, deployKeys: [], __typename: 'Project' }, + }, + }); - eventHub.$emit('enable.key', key); + confirmRemoveKeyMock.mockReturnValue(key); + pageInfoMock.mockReturnValue(pageInfo); + currentPageMock.mockReturnValue(page); + await mountComponent(); + glPagination = wrapper.findComponent(GlPagination); + }); - expect(putMethodMock).toHaveBeenCalledWith(`${TEST_ENDPOINT}/${key.id}/enable`); - expect(getMethodMock).toHaveBeenCalled(); + it('shows pagination with correct page info', () => { + expect(glPagination.exists()).toBe(true); + expect(glPagination.props()).toMatchObject({ + totalItems: pageInfo.total, + perPage: pageInfo.perPage, + value: page, + }); }); - it('re-fetches deploy keys when disabling a key', async () => { - await mountComponent(); + it('moves back a page', async () => { + await glPagination.vm.$emit('previous'); - await removeKey('disable.key'); + expect(pageMutationMock).toHaveBeenCalledWith( + expect.anything(), + { page: page - 1 }, + expect.anything(), + expect.anything(), + ); + }); + + it('moves forward a page', async () => { + await glPagination.vm.$emit('next'); - expect(putMethodMock).toHaveBeenCalledWith(`${TEST_ENDPOINT}/${key.id}/disable`); - expect(getMethodMock).toHaveBeenCalled(); + expect(pageMutationMock).toHaveBeenCalledWith( + expect.anything(), + { page: page + 1 }, + expect.anything(), + expect.anything(), + ); }); - it('calls disableKey when removing a key', async () => { - await mountComponent(); + it('moves to specified page', async () => { + await glPagination.vm.$emit('input', 5); + + expect(pageMutationMock).toHaveBeenCalledWith( + expect.anything(), + { page: 5 }, + expect.anything(), + expect.anything(), + ); + }); - await removeKey('remove.key'); + it('moves a page back if there are no more keys on this page', async () => { + await findModal().vm.$emit('remove'); + await waitForPromises(); - expect(putMethodMock).toHaveBeenCalledWith(`${TEST_ENDPOINT}/${key.id}/disable`); - expect(getMethodMock).toHaveBeenCalled(); + expect(pageMutationMock).toHaveBeenCalledWith( + expect.anything(), + { page: page - 1 }, + expect.anything(), + expect.anything(), + ); }); }); }); diff --git a/spec/frontend/deploy_keys/components/key_spec.js b/spec/frontend/deploy_keys/components/key_spec.js index e57da4df150..5410914da04 100644 --- a/spec/frontend/deploy_keys/components/key_spec.js +++ b/spec/frontend/deploy_keys/components/key_spec.js @@ -1,64 +1,85 @@ +import VueApollo from 'vue-apollo'; +import Vue, { nextTick } from 'vue'; import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import data from 'test_fixtures/deploy_keys/keys.json'; +import enabledKeys from 'test_fixtures/deploy_keys/enabled_keys.json'; +import availablePublicKeys from 'test_fixtures/deploy_keys/available_public_keys.json'; +import { createAlert } from '~/alert'; +import { mapDeployKey } from '~/deploy_keys/graphql/resolvers'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import key from '~/deploy_keys/components/key.vue'; -import DeployKeysStore from '~/deploy_keys/store'; +import ActionBtn from '~/deploy_keys/components/action_btn.vue'; import { getTimeago, localeDateFormat } from '~/lib/utils/datetime_utility'; +jest.mock('~/alert'); + +Vue.use(VueApollo); + describe('Deploy keys key', () => { let wrapper; - let store; + let currentScopeMock; const findTextAndTrim = (selector) => wrapper.find(selector).text().trim(); - const createComponent = (propsData) => { + const createComponent = async (propsData) => { + const resolvers = { + Query: { + currentScope: currentScopeMock, + }, + }; + + const apolloProvider = createMockApollo([], resolvers); wrapper = mount(key, { propsData: { - store, endpoint: 'https://test.host/dummy/endpoint', ...propsData, }, + apolloProvider, directives: { GlTooltip: createMockDirective('gl-tooltip'), }, }); + await nextTick(); }; beforeEach(() => { - store = new DeployKeysStore(); - store.keys = data; + currentScopeMock = jest.fn(); }); describe('enabled key', () => { - const deployKey = data.enabled_keys[0]; + const deployKey = mapDeployKey(enabledKeys.keys[0]); - it('renders the keys title', () => { - createComponent({ deployKey }); + beforeEach(() => { + currentScopeMock.mockReturnValue('enabledKeys'); + }); + + it('renders the keys title', async () => { + await createComponent({ deployKey }); expect(findTextAndTrim('.title')).toContain('My title'); }); - it('renders human friendly formatted created date', () => { - createComponent({ deployKey }); + it('renders human friendly formatted created date', async () => { + await createComponent({ deployKey }); expect(findTextAndTrim('.key-created-at')).toBe( - `${getTimeago().format(deployKey.created_at)}`, + `${getTimeago().format(deployKey.createdAt)}`, ); }); - it('renders human friendly expiration date', () => { + it('renders human friendly expiration date', async () => { const expiresAt = new Date(); - createComponent({ - deployKey: { ...deployKey, expires_at: expiresAt }, + await createComponent({ + deployKey: { ...deployKey, expiresAt }, }); expect(findTextAndTrim('.key-expires-at')).toBe(`${getTimeago().format(expiresAt)}`); }); - it('shows tooltip for expiration date', () => { + it('shows tooltip for expiration date', async () => { const expiresAt = new Date(); - createComponent({ - deployKey: { ...deployKey, expires_at: expiresAt }, + await createComponent({ + deployKey: { ...deployKey, expiresAt }, }); const expiryComponent = wrapper.find('[data-testid="expires-at-tooltip"]'); @@ -68,55 +89,57 @@ describe('Deploy keys key', () => { `${localeDateFormat.asDateTimeFull.format(expiresAt)}`, ); }); - it('renders never when no expiration date', () => { - createComponent({ - deployKey: { ...deployKey, expires_at: null }, + it('renders never when no expiration date', async () => { + await createComponent({ + deployKey: { ...deployKey, expiresAt: null }, }); expect(wrapper.find('[data-testid="expires-never"]').exists()).toBe(true); }); - it('shows pencil button for editing', () => { - createComponent({ deployKey }); + it('shows pencil button for editing', async () => { + await createComponent({ deployKey }); expect(wrapper.find('.btn [data-testid="pencil-icon"]').exists()).toBe(true); }); - it('shows disable button when the project is not deletable', () => { - createComponent({ deployKey }); + it('shows disable button when the project is not deletable', async () => { + await createComponent({ deployKey }); + await waitForPromises(); expect(wrapper.find('.btn [data-testid="cancel-icon"]').exists()).toBe(true); }); - it('shows remove button when the project is deletable', () => { - createComponent({ - deployKey: { ...deployKey, destroyed_when_orphaned: true, almost_orphaned: true }, + it('shows remove button when the project is deletable', async () => { + await createComponent({ + deployKey: { ...deployKey, destroyedWhenOrphaned: true, almostOrphaned: true }, }); + await waitForPromises(); expect(wrapper.find('.btn [data-testid="remove-icon"]').exists()).toBe(true); }); }); describe('deploy key labels', () => { - const deployKey = data.enabled_keys[0]; - const deployKeysProjects = [...deployKey.deploy_keys_projects]; - it('shows write access title when key has write access', () => { - deployKeysProjects[0] = { ...deployKeysProjects[0], can_push: true }; - createComponent({ deployKey: { ...deployKey, deploy_keys_projects: deployKeysProjects } }); + const deployKey = mapDeployKey(enabledKeys.keys[0]); + const deployKeysProjects = [...deployKey.deployKeysProjects]; + it('shows write access title when key has write access', async () => { + deployKeysProjects[0] = { ...deployKeysProjects[0], canPush: true }; + await createComponent({ deployKey: { ...deployKey, deployKeysProjects } }); expect(wrapper.find('.deploy-project-label').attributes('title')).toBe( 'Grant write permissions to this key', ); }); - it('does not show write access title when key has write access', () => { - deployKeysProjects[0] = { ...deployKeysProjects[0], can_push: false }; - createComponent({ deployKey: { ...deployKey, deploy_keys_projects: deployKeysProjects } }); + it('does not show write access title when key has write access', async () => { + deployKeysProjects[0] = { ...deployKeysProjects[0], canPush: false }; + await createComponent({ deployKey: { ...deployKey, deployKeysProjects } }); expect(wrapper.find('.deploy-project-label').attributes('title')).toBe('Read access only'); }); - it('shows expandable button if more than two projects', () => { - createComponent({ deployKey }); + it('shows expandable button if more than two projects', async () => { + await createComponent({ deployKey }); const labels = wrapper.findAll('.deploy-project-label'); expect(labels.length).toBe(2); @@ -125,53 +148,68 @@ describe('Deploy keys key', () => { }); it('expands all project labels after click', async () => { - createComponent({ deployKey }); - const { length } = deployKey.deploy_keys_projects; + await createComponent({ deployKey }); + const { length } = deployKey.deployKeysProjects; wrapper.findAll('.deploy-project-label').at(1).trigger('click'); await nextTick(); const labels = wrapper.findAll('.deploy-project-label'); - expect(labels.length).toBe(length); + expect(labels).toHaveLength(length); expect(labels.at(1).text()).not.toContain(`+${length} others`); expect(labels.at(1).attributes('title')).not.toContain('Expand'); }); - it('shows two projects', () => { - createComponent({ - deployKey: { ...deployKey, deploy_keys_projects: [...deployKeysProjects].slice(0, 2) }, + it('shows two projects', async () => { + await createComponent({ + deployKey: { ...deployKey, deployKeysProjects: [...deployKeysProjects].slice(0, 2) }, }); const labels = wrapper.findAll('.deploy-project-label'); expect(labels.length).toBe(2); - expect(labels.at(1).text()).toContain(deployKey.deploy_keys_projects[1].project.full_name); + expect(labels.at(1).text()).toContain(deployKey.deployKeysProjects[1].project.fullName); }); }); describe('public keys', () => { - const deployKey = data.public_keys[0]; + const deployKey = mapDeployKey(availablePublicKeys.keys[0]); - it('renders deploy keys without any enabled projects', () => { - createComponent({ deployKey: { ...deployKey, deploy_keys_projects: [] } }); + it('renders deploy keys without any enabled projects', async () => { + await createComponent({ deployKey: { ...deployKey, deployKeysProjects: [] } }); expect(findTextAndTrim('.deploy-project-list')).toBe('None'); }); - it('shows enable button', () => { - createComponent({ deployKey }); + it('shows enable button', async () => { + await createComponent({ deployKey }); expect(findTextAndTrim('.btn')).toBe('Enable'); }); - it('shows pencil button for editing', () => { - createComponent({ deployKey }); - expect(wrapper.find('.btn [data-testid="pencil-icon"]').exists()).toBe(true); + it('shows an error on enable failure', async () => { + await createComponent({ deployKey }); + + const error = new Error('oops!'); + wrapper.findComponent(ActionBtn).vm.$emit('error', error); + + await nextTick(); + + expect(createAlert).toHaveBeenCalledWith({ + message: 'Error enabling deploy key', + captureError: true, + error, + }); }); - it('shows disable button when key is enabled', () => { - store.keys.enabled_keys.push(deployKey); + it('shows pencil button for editing', async () => { + await createComponent({ deployKey }); + expect(wrapper.find('.btn [data-testid="pencil-icon"]').exists()).toBe(true); + }); - createComponent({ deployKey }); + it('shows disable button when key is enabled', async () => { + currentScopeMock.mockReturnValue('enabledKeys'); + await createComponent({ deployKey }); + await waitForPromises(); expect(wrapper.find('.btn [data-testid="cancel-icon"]').exists()).toBe(true); }); diff --git a/spec/frontend/deploy_keys/components/keys_panel_spec.js b/spec/frontend/deploy_keys/components/keys_panel_spec.js index e63b269fe23..6e653010d8f 100644 --- a/spec/frontend/deploy_keys/components/keys_panel_spec.js +++ b/spec/frontend/deploy_keys/components/keys_panel_spec.js @@ -1,7 +1,9 @@ import { mount } from '@vue/test-utils'; -import data from 'test_fixtures/deploy_keys/keys.json'; +import enabledKeys from 'test_fixtures/deploy_keys/enabled_keys.json'; import deployKeysPanel from '~/deploy_keys/components/keys_panel.vue'; -import DeployKeysStore from '~/deploy_keys/store'; +import { mapDeployKey } from '~/deploy_keys/graphql/resolvers'; + +const keys = enabledKeys.keys.map(mapDeployKey); describe('Deploy keys panel', () => { let wrapper; @@ -9,14 +11,11 @@ describe('Deploy keys panel', () => { const findTableRowHeader = () => wrapper.find('.table-row-header'); const mountComponent = (props) => { - const store = new DeployKeysStore(); - store.keys = data; wrapper = mount(deployKeysPanel, { propsData: { title: 'test', - keys: data.enabled_keys, + keys, showHelpBox: true, - store, endpoint: 'https://test.host/dummy/endpoint', ...props, }, @@ -25,7 +24,7 @@ describe('Deploy keys panel', () => { it('renders list of keys', () => { mountComponent(); - expect(wrapper.findAll('.deploy-key').length).toBe(wrapper.vm.keys.length); + expect(wrapper.findAll('.deploy-key').length).toBe(keys.length); }); it('renders table header', () => { |