diff options
Diffstat (limited to 'spec/frontend/registry/explorer/pages')
-rw-r--r-- | spec/frontend/registry/explorer/pages/details_spec.js | 336 | ||||
-rw-r--r-- | spec/frontend/registry/explorer/pages/index_spec.js | 4 | ||||
-rw-r--r-- | spec/frontend/registry/explorer/pages/list_spec.js | 341 |
3 files changed, 446 insertions, 235 deletions
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js index c09b7e0c067..d307dfe590c 100644 --- a/spec/frontend/registry/explorer/pages/details_spec.js +++ b/spec/frontend/registry/explorer/pages/details_spec.js @@ -1,5 +1,8 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlPagination } from '@gitlab/ui'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlKeysetPagination } from '@gitlab/ui'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'jest/helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import Tracking from '~/tracking'; import component from '~/registry/explorer/pages/details.vue'; import DeleteAlert from '~/registry/explorer/components/details_page/delete_alert.vue'; @@ -8,25 +11,28 @@ import DetailsHeader from '~/registry/explorer/components/details_page/details_h import TagsLoader from '~/registry/explorer/components/details_page/tags_loader.vue'; import TagsList from '~/registry/explorer/components/details_page/tags_list.vue'; import EmptyTagsState from '~/registry/explorer/components/details_page/empty_tags_state.vue'; -import { createStore } from '~/registry/explorer/stores/'; + +import getContainerRepositoryDetailsQuery from '~/registry/explorer/graphql/queries/get_container_repository_details.query.graphql'; +import deleteContainerRepositoryTagsMutation from '~/registry/explorer/graphql/mutations/delete_container_repository_tags.mutation.graphql'; + import { - SET_MAIN_LOADING, - SET_TAGS_LIST_SUCCESS, - SET_TAGS_PAGINATION, - SET_INITIAL_STATE, - SET_IMAGE_DETAILS, -} from '~/registry/explorer/stores/mutation_types'; - -import { tagsListResponse, imageDetailsMock } from '../mock_data'; + graphQLImageDetailsMock, + graphQLImageDetailsEmptyTagsMock, + graphQLDeleteImageRepositoryTagsMock, + containerRepositoryMock, + tagsMock, + tagsPageInfo, +} from '../mock_data'; import { DeleteModal } from '../stubs'; +const localVue = createLocalVue(); + describe('Details Page', () => { let wrapper; - let dispatchSpy; - let store; + let apolloProvider; const findDeleteModal = () => wrapper.find(DeleteModal); - const findPagination = () => wrapper.find(GlPagination); + const findPagination = () => wrapper.find(GlKeysetPagination); const findTagsLoader = () => wrapper.find(TagsLoader); const findTagsList = () => wrapper.find(TagsList); const findDeleteAlert = () => wrapper.find(DeleteAlert); @@ -36,15 +42,46 @@ describe('Details Page', () => { const routeId = 1; + const breadCrumbState = { + updateName: jest.fn(), + }; + + const cleanTags = tagsMock.map(t => { + const result = { ...t }; + // eslint-disable-next-line no-underscore-dangle + delete result.__typename; + return result; + }); + + const waitForApolloRequestRender = async () => { + await waitForPromises(); + await wrapper.vm.$nextTick(); + }; + const tagsArrayToSelectedTags = tags => tags.reduce((acc, c) => { acc[c.name] = true; return acc; }, {}); - const mountComponent = ({ options } = {}) => { + const mountComponent = ({ + resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()), + mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock), + options, + config = {}, + } = {}) => { + localVue.use(VueApollo); + + const requestHandlers = [ + [getContainerRepositoryDetailsQuery, resolver], + [deleteContainerRepositoryTagsMutation, mutationResolver], + ]; + + apolloProvider = createMockApollo(requestHandlers); + wrapper = shallowMount(component, { - store, + localVue, + apolloProvider, stubs: { DeleteModal, }, @@ -55,17 +92,17 @@ describe('Details Page', () => { }, }, }, + provide() { + return { + breadCrumbState, + config, + }; + }, ...options, }); }; beforeEach(() => { - store = createStore(); - dispatchSpy = jest.spyOn(store, 'dispatch'); - dispatchSpy.mockResolvedValue(); - store.commit(SET_TAGS_LIST_SUCCESS, tagsListResponse.data); - store.commit(SET_TAGS_PAGINATION, tagsListResponse.headers); - store.commit(SET_IMAGE_DETAILS, imageDetailsMock); jest.spyOn(Tracking, 'event'); }); @@ -74,85 +111,90 @@ describe('Details Page', () => { wrapper = null; }); - describe('lifecycle events', () => { - it('calls the appropriate action on mount', () => { - mountComponent(); - expect(dispatchSpy).toHaveBeenCalledWith('requestImageDetailsAndTagsList', routeId); - }); - }); - describe('when isLoading is true', () => { - beforeEach(() => { - store.commit(SET_MAIN_LOADING, true); + it('shows the loader', () => { mountComponent(); - }); - - afterEach(() => store.commit(SET_MAIN_LOADING, false)); - it('shows the loader', () => { expect(findTagsLoader().exists()).toBe(true); }); it('does not show the list', () => { + mountComponent(); + expect(findTagsList().exists()).toBe(false); }); it('does not show pagination', () => { + mountComponent(); + expect(findPagination().exists()).toBe(false); }); }); describe('when the list of tags is empty', () => { - beforeEach(() => { - store.commit(SET_TAGS_LIST_SUCCESS, []); - mountComponent(); - }); + const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsEmptyTagsMock); + + it('has the empty state', async () => { + mountComponent({ resolver }); + + await waitForApolloRequestRender(); - it('has the empty state', () => { expect(findEmptyTagsState().exists()).toBe(true); }); - it('does not show the loader', () => { + it('does not show the loader', async () => { + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + expect(findTagsLoader().exists()).toBe(false); }); - it('does not show the list', () => { + it('does not show the list', async () => { + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + expect(findTagsList().exists()).toBe(false); }); }); describe('list', () => { - beforeEach(() => { + it('exists', async () => { mountComponent(); - }); - it('exists', () => { + await waitForApolloRequestRender(); + expect(findTagsList().exists()).toBe(true); }); - it('has the correct props bound', () => { + it('has the correct props bound', async () => { + mountComponent(); + + await waitForApolloRequestRender(); + expect(findTagsList().props()).toMatchObject({ isMobile: false, - tags: store.state.tags, + tags: cleanTags, }); }); describe('deleteEvent', () => { describe('single item', () => { let tagToBeDeleted; - beforeEach(() => { - [tagToBeDeleted] = store.state.tags; + beforeEach(async () => { + mountComponent(); + + await waitForApolloRequestRender(); + + [tagToBeDeleted] = cleanTags; findTagsList().vm.$emit('delete', { [tagToBeDeleted.name]: true }); }); - it('open the modal', () => { + it('open the modal', async () => { expect(DeleteModal.methods.show).toHaveBeenCalled(); }); - it('maps the selection to itemToBeDeleted', () => { - expect(wrapper.vm.itemsToBeDeleted).toEqual([tagToBeDeleted]); - }); - it('tracks a single delete event', () => { expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { label: 'registry_tag_delete', @@ -161,18 +203,18 @@ describe('Details Page', () => { }); describe('multiple items', () => { - beforeEach(() => { - findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags)); + beforeEach(async () => { + mountComponent(); + + await waitForApolloRequestRender(); + + findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(cleanTags)); }); it('open the modal', () => { expect(DeleteModal.methods.show).toHaveBeenCalled(); }); - it('maps the selection to itemToBeDeleted', () => { - expect(wrapper.vm.itemsToBeDeleted).toEqual(store.state.tags); - }); - it('tracks a single delete event', () => { expect(Tracking.event).toHaveBeenCalledWith(undefined, 'click_button', { label: 'bulk_registry_tag_delete', @@ -183,40 +225,77 @@ describe('Details Page', () => { }); describe('pagination', () => { - beforeEach(() => { + it('exists', async () => { mountComponent(); - }); - it('exists', () => { + await waitForApolloRequestRender(); + expect(findPagination().exists()).toBe(true); }); - it('is wired to the correct pagination props', () => { - const pagination = findPagination(); - expect(pagination.props('perPage')).toBe(store.state.tagsPagination.perPage); - expect(pagination.props('totalItems')).toBe(store.state.tagsPagination.total); - expect(pagination.props('value')).toBe(store.state.tagsPagination.page); + it('is hidden when there are no more pages', async () => { + mountComponent({ resolver: jest.fn().mockResolvedValue(graphQLImageDetailsEmptyTagsMock) }); + + await waitForApolloRequestRender(); + + expect(findPagination().exists()).toBe(false); }); - it('fetch the data from the API when the v-model changes', () => { - dispatchSpy.mockResolvedValue(); - findPagination().vm.$emit(GlPagination.model.event, 2); - expect(store.dispatch).toHaveBeenCalledWith('requestTagsList', { - page: 2, + it('is wired to the correct pagination props', async () => { + mountComponent(); + + await waitForApolloRequestRender(); + + expect(findPagination().props()).toMatchObject({ + hasNextPage: tagsPageInfo.hasNextPage, + hasPreviousPage: tagsPageInfo.hasPreviousPage, }); }); + + it('fetch next page when user clicks next', async () => { + const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()); + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + + findPagination().vm.$emit('next'); + + expect(resolver).toHaveBeenCalledWith( + expect.objectContaining({ after: tagsPageInfo.endCursor }), + ); + }); + + it('fetch previous page when user clicks prev', async () => { + const resolver = jest.fn().mockResolvedValue(graphQLImageDetailsMock()); + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + + findPagination().vm.$emit('prev'); + + expect(resolver).toHaveBeenCalledWith( + expect.objectContaining({ first: null, before: tagsPageInfo.startCursor }), + ); + }); }); describe('modal', () => { - it('exists', () => { + it('exists', async () => { mountComponent(); + + await waitForApolloRequestRender(); + expect(findDeleteModal().exists()).toBe(true); }); describe('cancel event', () => { - it('tracks cancel_delete', () => { + it('tracks cancel_delete', async () => { mountComponent(); + + await waitForApolloRequestRender(); + findDeleteModal().vm.$emit('cancel'); + expect(Tracking.event).toHaveBeenCalledWith(undefined, 'cancel_delete', { label: 'registry_tag_delete', }); @@ -224,45 +303,62 @@ describe('Details Page', () => { }); describe('confirmDelete event', () => { + let mutationResolver; + + beforeEach(() => { + mutationResolver = jest.fn().mockResolvedValue(graphQLDeleteImageRepositoryTagsMock); + mountComponent({ mutationResolver }); + + return waitForApolloRequestRender(); + }); describe('when one item is selected to be deleted', () => { - beforeEach(() => { - mountComponent(); - findTagsList().vm.$emit('delete', { [store.state.tags[0].name]: true }); - }); + it('calls apollo mutation with the right parameters', async () => { + findTagsList().vm.$emit('delete', { [cleanTags[0].name]: true }); + + await wrapper.vm.$nextTick(); - it('dispatch requestDeleteTag with the right parameters', () => { findDeleteModal().vm.$emit('confirmDelete'); - expect(dispatchSpy).toHaveBeenCalledWith('requestDeleteTag', { - tag: store.state.tags[0], - }); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: [cleanTags[0].name] }), + ); }); }); describe('when more than one item is selected to be deleted', () => { - beforeEach(() => { - mountComponent(); - findTagsList().vm.$emit('delete', tagsArrayToSelectedTags(store.state.tags)); - }); + it('calls apollo mutation with the right parameters', async () => { + findTagsList().vm.$emit('delete', { ...tagsArrayToSelectedTags(tagsMock) }); + + await wrapper.vm.$nextTick(); - it('dispatch requestDeleteTags with the right parameters', () => { findDeleteModal().vm.$emit('confirmDelete'); - expect(dispatchSpy).toHaveBeenCalledWith('requestDeleteTags', { - ids: store.state.tags.map(t => t.name), - }); + + expect(mutationResolver).toHaveBeenCalledWith( + expect.objectContaining({ tagNames: tagsMock.map(t => t.name) }), + ); }); }); }); }); describe('Header', () => { - it('exists', () => { + it('exists', async () => { mountComponent(); + + await waitForApolloRequestRender(); expect(findDetailsHeader().exists()).toBe(true); }); - it('has the correct props', () => { + it('has the correct props', async () => { mountComponent(); - expect(findDetailsHeader().props()).toEqual({ imageName: imageDetailsMock.name }); + + await waitForApolloRequestRender(); + expect(findDetailsHeader().props('image')).toMatchObject({ + name: containerRepositoryMock.name, + project: { + visibility: containerRepositoryMock.project.visibility, + }, + }); }); }); @@ -273,20 +369,25 @@ describe('Details Page', () => { }; const deleteAlertType = 'success_tag'; - it('exists', () => { + it('exists', async () => { mountComponent(); + + await waitForApolloRequestRender(); expect(findDeleteAlert().exists()).toBe(true); }); - it('has the correct props', () => { - store.commit(SET_INITIAL_STATE, { ...config }); + it('has the correct props', async () => { mountComponent({ options: { data: () => ({ deleteAlertType, }), }, + config, }); + + await waitForApolloRequestRender(); + expect(findDeleteAlert().props()).toEqual({ ...config, deleteAlertType }); }); }); @@ -298,30 +399,38 @@ describe('Details Page', () => { }; describe('when expiration_policy_started is not null', () => { + let resolver; + beforeEach(() => { - store.commit(SET_IMAGE_DETAILS, { - ...imageDetailsMock, - cleanup_policy_started_at: Date.now().toString(), - }); + resolver = jest.fn().mockResolvedValue( + graphQLImageDetailsMock({ + expirationPolicyStartedAt: Date.now().toString(), + }), + ); }); - it('exists', () => { - mountComponent(); + it('exists', async () => { + mountComponent({ resolver }); + + await waitForApolloRequestRender(); expect(findPartialCleanupAlert().exists()).toBe(true); }); - it('has the correct props', () => { - store.commit(SET_INITIAL_STATE, { ...config }); + it('has the correct props', async () => { + mountComponent({ resolver, config }); - mountComponent(); + await waitForApolloRequestRender(); expect(findPartialCleanupAlert().props()).toEqual({ ...config }); }); it('dismiss hides the component', async () => { - mountComponent(); + mountComponent({ resolver }); + + await waitForApolloRequestRender(); expect(findPartialCleanupAlert().exists()).toBe(true); + findPartialCleanupAlert().vm.$emit('dismiss'); await wrapper.vm.$nextTick(); @@ -331,11 +440,22 @@ describe('Details Page', () => { }); describe('when expiration_policy_started is null', () => { - it('the component is hidden', () => { + it('the component is hidden', async () => { mountComponent(); + await waitForApolloRequestRender(); expect(findPartialCleanupAlert().exists()).toBe(false); }); }); }); + + describe('Breadcrumb connection', () => { + it('when the details are fetched updates the name', async () => { + mountComponent(); + + await waitForApolloRequestRender(); + + expect(breadCrumbState.updateName).toHaveBeenCalledWith(containerRepositoryMock.name); + }); + }); }); diff --git a/spec/frontend/registry/explorer/pages/index_spec.js b/spec/frontend/registry/explorer/pages/index_spec.js index 1dc5376cacf..b5f718b3e61 100644 --- a/spec/frontend/registry/explorer/pages/index_spec.js +++ b/spec/frontend/registry/explorer/pages/index_spec.js @@ -1,16 +1,13 @@ import { shallowMount } from '@vue/test-utils'; import component from '~/registry/explorer/pages/index.vue'; -import { createStore } from '~/registry/explorer/stores/'; describe('List Page', () => { let wrapper; - let store; const findRouterView = () => wrapper.find({ ref: 'router-view' }); const mountComponent = () => { wrapper = shallowMount(component, { - store, stubs: { RouterView: true, }, @@ -18,7 +15,6 @@ describe('List Page', () => { }; beforeEach(() => { - store = createStore(); mountComponent(); }); diff --git a/spec/frontend/registry/explorer/pages/list_spec.js b/spec/frontend/registry/explorer/pages/list_spec.js index b24422adb03..7d32a667011 100644 --- a/spec/frontend/registry/explorer/pages/list_spec.js +++ b/spec/frontend/registry/explorer/pages/list_spec.js @@ -1,5 +1,7 @@ -import { shallowMount } from '@vue/test-utils'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; import { GlSkeletonLoader, GlSprintf, GlAlert, GlSearchBoxByClick } from '@gitlab/ui'; +import createMockApollo from 'jest/helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Tracking from '~/tracking'; import component from '~/registry/explorer/pages/list.vue'; @@ -9,27 +11,35 @@ import ProjectEmptyState from '~/registry/explorer/components/list_page/project_ import RegistryHeader from '~/registry/explorer/components/list_page/registry_header.vue'; import ImageList from '~/registry/explorer/components/list_page/image_list.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; -import { createStore } from '~/registry/explorer/stores/'; -import { - SET_MAIN_LOADING, - SET_IMAGES_LIST_SUCCESS, - SET_PAGINATION, - SET_INITIAL_STATE, -} from '~/registry/explorer/stores/mutation_types'; + import { DELETE_IMAGE_SUCCESS_MESSAGE, DELETE_IMAGE_ERROR_MESSAGE, IMAGE_REPOSITORY_LIST_LABEL, SEARCH_PLACEHOLDER_TEXT, } from '~/registry/explorer/constants'; -import { imagesListResponse } from '../mock_data'; + +import getProjectContainerRepositoriesQuery from '~/registry/explorer/graphql/queries/get_project_container_repositories.query.graphql'; +import getGroupContainerRepositoriesQuery from '~/registry/explorer/graphql/queries/get_group_container_repositories.query.graphql'; +import deleteContainerRepositoryMutation from '~/registry/explorer/graphql/mutations/delete_container_repository.mutation.graphql'; + +import { + graphQLImageListMock, + graphQLImageDeleteMock, + deletedContainerRepository, + graphQLImageDeleteMockError, + graphQLEmptyImageListMock, + graphQLEmptyGroupImageListMock, + pageInfo, +} from '../mock_data'; import { GlModal, GlEmptyState } from '../stubs'; import { $toast } from '../../shared/mocks'; +const localVue = createLocalVue(); + describe('List Page', () => { let wrapper; - let dispatchSpy; - let store; + let apolloProvider; const findDeleteModal = () => wrapper.find(GlModal); const findSkeletonLoader = () => wrapper.find(GlSkeletonLoader); @@ -47,9 +57,31 @@ describe('List Page', () => { const findSearchBox = () => wrapper.find(GlSearchBoxByClick); const findEmptySearchMessage = () => wrapper.find('[data-testid="emptySearch"]'); - const mountComponent = ({ mocks } = {}) => { + const waitForApolloRequestRender = async () => { + await waitForPromises(); + await wrapper.vm.$nextTick(); + }; + + const mountComponent = ({ + mocks, + resolver = jest.fn().mockResolvedValue(graphQLImageListMock), + groupResolver = jest.fn().mockResolvedValue(graphQLImageListMock), + mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock), + config = {}, + } = {}) => { + localVue.use(VueApollo); + + const requestHandlers = [ + [getProjectContainerRepositoriesQuery, resolver], + [getGroupContainerRepositoriesQuery, groupResolver], + [deleteContainerRepositoryMutation, mutationResolver], + ]; + + apolloProvider = createMockApollo(requestHandlers); + wrapper = shallowMount(component, { - store, + localVue, + apolloProvider, stubs: { GlModal, GlEmptyState, @@ -64,42 +96,27 @@ describe('List Page', () => { }, ...mocks, }, + provide() { + return { + config, + }; + }, }); }; - beforeEach(() => { - store = createStore(); - dispatchSpy = jest.spyOn(store, 'dispatch'); - dispatchSpy.mockResolvedValue(); - store.commit(SET_IMAGES_LIST_SUCCESS, imagesListResponse.data); - store.commit(SET_PAGINATION, imagesListResponse.headers); - }); - afterEach(() => { wrapper.destroy(); }); - describe('API calls', () => { - it.each` - imageList | name | called - ${[]} | ${'foo'} | ${['requestImagesList']} - ${imagesListResponse.data} | ${undefined} | ${['requestImagesList']} - ${imagesListResponse.data} | ${'foo'} | ${undefined} - `( - 'with images equal $imageList and name $name dispatch calls $called', - ({ imageList, name, called }) => { - store.commit(SET_IMAGES_LIST_SUCCESS, imageList); - dispatchSpy.mockClear(); - mountComponent({ mocks: { $route: { name } } }); - - expect(dispatchSpy.mock.calls[0]).toEqual(called); - }, - ); - }); - - it('contains registry header', () => { + it('contains registry header', async () => { mountComponent(); + + await waitForApolloRequestRender(); + expect(findRegistryHeader().exists()).toBe(true); + expect(findRegistryHeader().props()).toMatchObject({ + imagesCount: 2, + }); }); describe('connection error', () => { @@ -109,88 +126,100 @@ describe('List Page', () => { helpPagePath: 'bar', }; - beforeEach(() => { - store.commit(SET_INITIAL_STATE, config); - mountComponent(); - }); - - afterEach(() => { - store.commit(SET_INITIAL_STATE, {}); - }); - it('should show an empty state', () => { + mountComponent({ config }); + expect(findEmptyState().exists()).toBe(true); }); it('empty state should have an svg-path', () => { - expect(findEmptyState().attributes('svg-path')).toBe(config.containersErrorImage); + mountComponent({ config }); + + expect(findEmptyState().props('svgPath')).toBe(config.containersErrorImage); }); it('empty state should have a description', () => { - expect(findEmptyState().html()).toContain('connection error'); + mountComponent({ config }); + + expect(findEmptyState().props('title')).toContain('connection error'); }); it('should not show the loading or default state', () => { + mountComponent({ config }); + expect(findSkeletonLoader().exists()).toBe(false); expect(findImageList().exists()).toBe(false); }); }); describe('isLoading is true', () => { - beforeEach(() => { - store.commit(SET_MAIN_LOADING, true); + it('shows the skeleton loader', () => { mountComponent(); - }); - - afterEach(() => store.commit(SET_MAIN_LOADING, false)); - it('shows the skeleton loader', () => { expect(findSkeletonLoader().exists()).toBe(true); }); it('imagesList is not visible', () => { + mountComponent(); + expect(findImageList().exists()).toBe(false); }); it('cli commands is not visible', () => { + mountComponent(); + expect(findCliCommands().exists()).toBe(false); }); }); describe('list is empty', () => { - beforeEach(() => { - store.commit(SET_IMAGES_LIST_SUCCESS, []); - mountComponent(); - return waitForPromises(); - }); + describe('project page', () => { + const resolver = jest.fn().mockResolvedValue(graphQLEmptyImageListMock); - it('cli commands is not visible', () => { - expect(findCliCommands().exists()).toBe(false); - }); + it('cli commands is not visible', async () => { + mountComponent({ resolver }); - it('project empty state is visible', () => { - expect(findProjectEmptyState().exists()).toBe(true); - }); + await waitForApolloRequestRender(); - describe('is group page is true', () => { - beforeEach(() => { - store.commit(SET_INITIAL_STATE, { isGroupPage: true }); - mountComponent(); + expect(findCliCommands().exists()).toBe(false); }); - afterEach(() => { - store.commit(SET_INITIAL_STATE, { isGroupPage: undefined }); + it('project empty state is visible', async () => { + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + + expect(findProjectEmptyState().exists()).toBe(true); }); + }); + describe('group page', () => { + const groupResolver = jest.fn().mockResolvedValue(graphQLEmptyGroupImageListMock); + + const config = { + isGroupPage: true, + }; + + it('group empty state is visible', async () => { + mountComponent({ groupResolver, config }); + + await waitForApolloRequestRender(); - it('group empty state is visible', () => { expect(findGroupEmptyState().exists()).toBe(true); }); - it('cli commands is not visible', () => { + it('cli commands is not visible', async () => { + mountComponent({ groupResolver, config }); + + await waitForApolloRequestRender(); + expect(findCliCommands().exists()).toBe(false); }); - it('list header is not visible', () => { + it('list header is not visible', async () => { + mountComponent({ groupResolver, config }); + + await waitForApolloRequestRender(); + expect(findListHeader().exists()).toBe(false); }); }); @@ -198,55 +227,91 @@ describe('List Page', () => { describe('list is not empty', () => { describe('unfiltered state', () => { - beforeEach(() => { + it('quick start is visible', async () => { mountComponent(); - }); - it('quick start is visible', () => { + await waitForApolloRequestRender(); + expect(findCliCommands().exists()).toBe(true); }); - it('list component is visible', () => { + it('list component is visible', async () => { + mountComponent(); + + await waitForApolloRequestRender(); + expect(findImageList().exists()).toBe(true); }); - it('list header is visible', () => { + it('list header is visible', async () => { + mountComponent(); + + await waitForApolloRequestRender(); + const header = findListHeader(); expect(header.exists()).toBe(true); expect(header.text()).toBe(IMAGE_REPOSITORY_LIST_LABEL); }); describe('delete image', () => { - const itemToDelete = { path: 'bar' }; - it('should call deleteItem when confirming deletion', () => { - dispatchSpy.mockResolvedValue(); - findImageList().vm.$emit('delete', itemToDelete); - expect(wrapper.vm.itemToDelete).toEqual(itemToDelete); + const deleteImage = async () => { + await wrapper.vm.$nextTick(); + + findImageList().vm.$emit('delete', deletedContainerRepository); findDeleteModal().vm.$emit('ok'); - expect(store.dispatch).toHaveBeenCalledWith( - 'requestDeleteImage', - wrapper.vm.itemToDelete, + + await waitForApolloRequestRender(); + }; + + it('should call deleteItem when confirming deletion', async () => { + const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock); + mountComponent({ mutationResolver }); + + await deleteImage(); + + expect(wrapper.vm.itemToDelete).toEqual(deletedContainerRepository); + expect(mutationResolver).toHaveBeenCalledWith({ id: deletedContainerRepository.id }); + + const updatedImage = findImageList() + .props('images') + .find(i => i.id === deletedContainerRepository.id); + + expect(updatedImage.status).toBe(deletedContainerRepository.status); + }); + + it('should show a success alert when delete request is successful', async () => { + const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMock); + mountComponent({ mutationResolver }); + + await deleteImage(); + + const alert = findDeleteAlert(); + expect(alert.exists()).toBe(true); + expect(alert.text().replace(/\s\s+/gm, ' ')).toBe( + DELETE_IMAGE_SUCCESS_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path), ); }); - it('should show a success alert when delete request is successful', () => { - dispatchSpy.mockResolvedValue(); - findImageList().vm.$emit('delete', itemToDelete); - expect(wrapper.vm.itemToDelete).toEqual(itemToDelete); - return wrapper.vm.handleDeleteImage().then(() => { + describe('when delete request fails it shows an alert', () => { + it('user recoverable error', async () => { + const mutationResolver = jest.fn().mockResolvedValue(graphQLImageDeleteMockError); + mountComponent({ mutationResolver }); + + await deleteImage(); + const alert = findDeleteAlert(); expect(alert.exists()).toBe(true); expect(alert.text().replace(/\s\s+/gm, ' ')).toBe( - DELETE_IMAGE_SUCCESS_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path), + DELETE_IMAGE_ERROR_MESSAGE.replace('%{title}', wrapper.vm.itemToDelete.path), ); }); - }); - it('should show an error alert when delete request fails', () => { - dispatchSpy.mockRejectedValue(); - findImageList().vm.$emit('delete', itemToDelete); - expect(wrapper.vm.itemToDelete).toEqual(itemToDelete); - return wrapper.vm.handleDeleteImage().then(() => { + it('network error', async () => { + const mutationResolver = jest.fn().mockRejectedValue(); + mountComponent({ mutationResolver }); + + await deleteImage(); + const alert = findDeleteAlert(); expect(alert.exists()).toBe(true); expect(alert.text().replace(/\s\s+/gm, ' ')).toBe( @@ -258,38 +323,68 @@ describe('List Page', () => { }); describe('search', () => { - it('has a search box element', () => { + const doSearch = async () => { + await waitForApolloRequestRender(); + findSearchBox().vm.$emit('submit', 'centos6'); + await wrapper.vm.$nextTick(); + }; + + it('has a search box element', async () => { mountComponent(); + + await waitForApolloRequestRender(); + const searchBox = findSearchBox(); expect(searchBox.exists()).toBe(true); expect(searchBox.attributes('placeholder')).toBe(SEARCH_PLACEHOLDER_TEXT); }); - it('performs a search', () => { - mountComponent(); - findSearchBox().vm.$emit('submit', 'foo'); - expect(store.dispatch).toHaveBeenCalledWith('requestImagesList', { - name: 'foo', - }); + it('performs a search', async () => { + const resolver = jest.fn().mockResolvedValue(graphQLImageListMock); + mountComponent({ resolver }); + + await doSearch(); + + expect(resolver).toHaveBeenCalledWith(expect.objectContaining({ name: 'centos6' })); }); - it('when search result is empty displays an empty search message', () => { - mountComponent(); - store.commit(SET_IMAGES_LIST_SUCCESS, []); - return wrapper.vm.$nextTick().then(() => { - expect(findEmptySearchMessage().exists()).toBe(true); - }); + it('when search result is empty displays an empty search message', async () => { + const resolver = jest.fn().mockResolvedValue(graphQLImageListMock); + mountComponent({ resolver }); + + resolver.mockResolvedValue(graphQLEmptyImageListMock); + + await doSearch(); + + expect(findEmptySearchMessage().exists()).toBe(true); }); }); describe('pagination', () => { - it('pageChange event triggers the appropriate store function', () => { - mountComponent(); - findImageList().vm.$emit('pageChange', 2); - expect(store.dispatch).toHaveBeenCalledWith('requestImagesList', { - pagination: { page: 2 }, - name: wrapper.vm.search, - }); + it('prev-page event triggers a fetchMore request', async () => { + const resolver = jest.fn().mockResolvedValue(graphQLImageListMock); + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + + findImageList().vm.$emit('prev-page'); + + expect(resolver).toHaveBeenCalledWith( + expect.objectContaining({ first: null, before: pageInfo.startCursor }), + ); + }); + + it('next-page event triggers a fetchMore request', async () => { + const resolver = jest.fn().mockResolvedValue(graphQLImageListMock); + mountComponent({ resolver }); + + await waitForApolloRequestRender(); + + findImageList().vm.$emit('next-page'); + + expect(resolver).toHaveBeenCalledWith( + expect.objectContaining({ after: pageInfo.endCursor }), + ); }); }); }); @@ -324,11 +419,11 @@ describe('List Page', () => { beforeEach(() => { jest.spyOn(Tracking, 'event'); - dispatchSpy.mockResolvedValue(); }); it('send an event when delete button is clicked', () => { findImageList().vm.$emit('delete', {}); + testTrackingCall('click_button'); }); |