diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 16:05:49 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-17 16:05:49 +0000 |
commit | 43a25d93ebdabea52f99b05e15b06250cd8f07d7 (patch) | |
tree | dceebdc68925362117480a5d672bcff122fb625b /spec/frontend/snippets | |
parent | 20c84b99005abd1c82101dfeff264ac50d2df211 (diff) | |
download | gitlab-ce-16.0.0-rc42.tar.gz |
Add latest changes from gitlab-org/gitlab@16-0-stable-eev16.0.0-rc4216-0-stable
Diffstat (limited to 'spec/frontend/snippets')
14 files changed, 246 insertions, 211 deletions
diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap index fec300ddd7e..c8d972b19a3 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap @@ -19,7 +19,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] = <gl-form-input-stub class="form-control" data-qa-selector="description_placeholder" - placeholder="Optionally add a description about what your snippet does or how to use it…" + placeholder="Describe what your snippet does or how to use it…" /> </div> @@ -28,10 +28,13 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] = data-uploads-path="" > <markdown-header-stub + data-testid="markdownHeader" enablepreview="true" linecontent="" + markdownpreviewpath="foo/" restrictedtoolbaritems="" suggestionstartindex="0" + uploadspath="" /> <div @@ -87,7 +90,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] = </div> <div - class="js-vue-md-preview md md-preview-holder" + class="js-vue-md-preview md md-preview-holder gl-px-5" style="display: none;" /> diff --git a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap index f4ebc5c3e3f..ed54582ca29 100644 --- a/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap +++ b/spec/frontend/snippets/components/__snapshots__/snippet_visibility_edit_spec.js.snap @@ -13,7 +13,7 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] = target="_blank" > <gl-icon-stub - name="question" + name="question-o" size="12" /> </gl-link-stub> diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js index e7dab0ad79d..d17e20ac227 100644 --- a/spec/frontend/snippets/components/edit_spec.js +++ b/spec/frontend/snippets/components/edit_spec.js @@ -9,7 +9,7 @@ import { stubPerformanceWebAPI } from 'helpers/performance'; import waitForPromises from 'helpers/wait_for_promises'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import * as urlUtils from '~/lib/utils/url_utility'; import SnippetEditApp from '~/snippets/components/edit.vue'; import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue'; @@ -25,7 +25,7 @@ import UpdateSnippetMutation from '~/snippets/mutations/update_snippet.mutation. import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue'; import { testEntries, createGQLSnippetsQueryResponse, createGQLSnippet } from '../test_utils'; -jest.mock('~/flash'); +jest.mock('~/alert'); const TEST_UPLOADED_FILES = ['foo/bar.txt', 'alpha/beta.js']; const TEST_API_ERROR = new Error('TEST_API_ERROR'); @@ -94,7 +94,6 @@ describe('Snippet Edit app', () => { let mutateSpy; const relativeUrlRoot = '/foo/'; - const originalRelativeUrlRoot = gon.relative_url_root; beforeEach(() => { stubPerformanceWebAPI(); @@ -108,12 +107,6 @@ describe('Snippet Edit app', () => { jest.spyOn(urlUtils, 'redirectTo').mockImplementation(); }); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - gon.relative_url_root = originalRelativeUrlRoot; - }); - const findBlobActions = () => wrapper.findComponent(SnippetBlobActionsEdit); const findCancelButton = () => wrapper.findByTestId('snippet-cancel-btn'); const clickSubmitBtn = () => wrapper.findByTestId('snippet-edit-form').trigger('submit'); @@ -132,10 +125,6 @@ describe('Snippet Edit app', () => { props = {}, selectedLevel = VISIBILITY_LEVEL_PRIVATE_STRING, } = {}) => { - if (wrapper) { - throw new Error('wrapper already created'); - } - const requestHandlers = [ [GetSnippetQuery, getSpy], // See `mutateSpy` declaration comment for why we send a key @@ -267,7 +256,7 @@ describe('Snippet Edit app', () => { VISIBILITY_LEVEL_PRIVATE_STRING, VISIBILITY_LEVEL_INTERNAL_STRING, VISIBILITY_LEVEL_PUBLIC_STRING, - ])('marks %s visibility by default', async (visibility) => { + ])('marks %s visibility by default', (visibility) => { createComponent({ props: { snippetGid: '' }, selectedLevel: visibility, @@ -339,7 +328,7 @@ describe('Snippet Edit app', () => { it('should redirect to snippet view on successful mutation', async () => { await createComponentAndSubmit(); - expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL); + expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL); // eslint-disable-line import/no-deprecated }); describe('when there are errors after creating a new snippet', () => { @@ -347,7 +336,7 @@ describe('Snippet Edit app', () => { projectPath ${'project/path'} ${''} - `('should flash error (projectPath=$projectPath)', async ({ projectPath }) => { + `('should alert error (projectPath=$projectPath)', async ({ projectPath }) => { mutateSpy.mockResolvedValue(createMutationResponseWithErrors('createSnippet')); await createComponentAndLoad({ @@ -360,7 +349,7 @@ describe('Snippet Edit app', () => { await waitForPromises(); - expect(urlUtils.redirectTo).not.toHaveBeenCalled(); + expect(urlUtils.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated expect(createAlert).toHaveBeenCalledWith({ message: `Can't create snippet: ${TEST_MUTATION_ERROR}`, }); @@ -373,7 +362,7 @@ describe('Snippet Edit app', () => { ${'project/path'} ${''} `( - 'should flash error with (snippet=$snippetGid, projectPath=$projectPath)', + 'should alert error with (snippet=$snippetGid, projectPath=$projectPath)', async ({ projectPath }) => { mutateSpy.mockResolvedValue(createMutationResponseWithErrors('updateSnippet')); @@ -384,7 +373,7 @@ describe('Snippet Edit app', () => { }, }); - expect(urlUtils.redirectTo).not.toHaveBeenCalled(); + expect(urlUtils.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated expect(createAlert).toHaveBeenCalledWith({ message: `Can't update snippet: ${TEST_MUTATION_ERROR}`, }); @@ -402,10 +391,10 @@ describe('Snippet Edit app', () => { }); it('should not redirect', () => { - expect(urlUtils.redirectTo).not.toHaveBeenCalled(); + expect(urlUtils.redirectTo).not.toHaveBeenCalled(); // eslint-disable-line import/no-deprecated }); - it('should flash', () => { + it('should alert', () => { // Apollo automatically wraps the resolver's error in a NetworkError expect(createAlert).toHaveBeenCalledWith({ message: `Can't update snippet: ${TEST_API_ERROR.message}`, diff --git a/spec/frontend/snippets/components/embed_dropdown_spec.js b/spec/frontend/snippets/components/embed_dropdown_spec.js index ed5ea6cab8a..d8c6ad3278a 100644 --- a/spec/frontend/snippets/components/embed_dropdown_spec.js +++ b/spec/frontend/snippets/components/embed_dropdown_spec.js @@ -17,11 +17,6 @@ describe('snippets/components/embed_dropdown', () => { }); }; - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - const findSectionsData = () => { const sections = []; let current = {}; diff --git a/spec/frontend/snippets/components/show_spec.js b/spec/frontend/snippets/components/show_spec.js index 032dcf8e5f5..45a7c7b0b4a 100644 --- a/spec/frontend/snippets/components/show_spec.js +++ b/spec/frontend/snippets/components/show_spec.js @@ -50,10 +50,6 @@ describe('Snippet view app', () => { stubPerformanceWebAPI(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('renders loader while the query is in flight', () => { createComponent({ loading: true }); expect(findLoadingIcon().exists()).toBe(true); diff --git a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js index a650353093d..58f47e8b0dc 100644 --- a/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_actions_edit_spec.js @@ -56,11 +56,6 @@ describe('snippets/components/snippet_blob_actions_edit', () => { const triggerBlobDelete = (idx) => findBlobEdits().at(idx).vm.$emit('delete'); const triggerBlobUpdate = (idx, props) => findBlobEdits().at(idx).vm.$emit('blob-updated', props); - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - describe('multi-file snippets rendering', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/snippets/components/snippet_blob_edit_spec.js b/spec/frontend/snippets/components/snippet_blob_edit_spec.js index 82c4a37ccc9..b699e056576 100644 --- a/spec/frontend/snippets/components/snippet_blob_edit_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_edit_spec.js @@ -4,14 +4,14 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { joinPaths } from '~/lib/utils/url_utility'; import SnippetBlobEdit from '~/snippets/components/snippet_blob_edit.vue'; import SourceEditor from '~/vue_shared/components/source_editor.vue'; -jest.mock('~/flash'); +jest.mock('~/alert'); const TEST_ID = 'blob_local_7'; const TEST_PATH = 'foo/bar/test.md'; @@ -62,8 +62,6 @@ describe('Snippet Blob Edit component', () => { }); afterEach(() => { - wrapper.destroy(); - wrapper = null; axiosMock.restore(); }); @@ -123,7 +121,7 @@ describe('Snippet Blob Edit component', () => { createComponent(); }); - it('should call flash', async () => { + it('should call alert', async () => { await waitForPromises(); expect(createAlert).toHaveBeenCalledWith({ diff --git a/spec/frontend/snippets/components/snippet_blob_view_spec.js b/spec/frontend/snippets/components/snippet_blob_view_spec.js index c7ff8c21d80..05ff64c2296 100644 --- a/spec/frontend/snippets/components/snippet_blob_view_spec.js +++ b/spec/frontend/snippets/components/snippet_blob_view_spec.js @@ -1,5 +1,6 @@ -import { mount } from '@vue/test-utils'; -import { nextTick } from 'vue'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMount } from '@vue/test-utils'; import { Blob as BlobMock, SimpleViewerMock, @@ -7,6 +8,7 @@ import { RichBlobContentMock, SimpleBlobContentMock, } from 'jest/blob/components/mock_data'; +import GetBlobContent from 'shared_queries/snippet/snippet_blob_content.query.graphql'; import BlobContent from '~/blob/components/blob_content.vue'; import BlobHeader from '~/blob/components/blob_header.vue'; import { @@ -17,9 +19,13 @@ import { import SnippetBlobView from '~/snippets/components/snippet_blob_view.vue'; import { VISIBILITY_LEVEL_PUBLIC_STRING } from '~/visibility_level/constants'; import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; describe('Blob Embeddable', () => { let wrapper; + let requestHandlers; + const snippet = { id: 'gid://foo.bar/snippet', webUrl: 'https://foo.bar', @@ -29,23 +35,47 @@ describe('Blob Embeddable', () => { activeViewerType: SimpleViewerMock.type, }; + const mockDefaultHandler = ({ path, nodes } = { path: BlobMock.path }) => { + const renderedNodes = nodes || [ + { __typename: 'Blob', path, richData: 'richData', plainData: 'plainData' }, + ]; + + return jest.fn().mockResolvedValue({ + data: { + snippets: { + __typename: 'Snippet', + id: '1', + nodes: [ + { + __typename: 'Snippet', + id: '2', + blobs: { + __typename: 'Blob', + hasUnretrievableBlobs: false, + nodes: renderedNodes, + }, + }, + ], + }, + }, + }); + }; + + const createMockApolloProvider = (handler) => { + Vue.use(VueApollo); + + requestHandlers = handler; + return createMockApollo([[GetBlobContent, requestHandlers]]); + }; + function createComponent({ snippetProps = {}, data = dataMock, blob = BlobMock, - contentLoading = false, + handler = mockDefaultHandler(), } = {}) { - const $apollo = { - queries: { - blobContent: { - loading: contentLoading, - refetch: jest.fn(), - skip: true, - }, - }, - }; - - wrapper = mount(SnippetBlobView, { + wrapper = shallowMount(SnippetBlobView, { + apolloProvider: createMockApolloProvider(handler), propsData: { snippet: { ...snippet, @@ -58,45 +88,56 @@ describe('Blob Embeddable', () => { ...data, }; }, - mocks: { $apollo }, + stubs: { + BlobHeader, + BlobContent, + }, }); } - afterEach(() => { - wrapper.destroy(); - }); + const findBlobHeader = () => wrapper.findComponent(BlobHeader); + const findBlobContent = () => wrapper.findComponent(BlobContent); + const findSimpleViewer = () => wrapper.findComponent(SimpleViewer); + const findRichViewer = () => wrapper.findComponent(RichViewer); describe('rendering', () => { it('renders correct components', () => { createComponent(); - expect(wrapper.findComponent(BlobHeader).exists()).toBe(true); - expect(wrapper.findComponent(BlobContent).exists()).toBe(true); + expect(findBlobHeader().exists()).toBe(true); + expect(findBlobContent().exists()).toBe(true); }); - it('sets simple viewer correctly', () => { + it('sets simple viewer correctly', async () => { createComponent(); - expect(wrapper.findComponent(SimpleViewer).exists()).toBe(true); + await waitForPromises(); + + expect(findSimpleViewer().exists()).toBe(true); }); - it('sets rich viewer correctly', () => { + it('sets rich viewer correctly', async () => { const data = { ...dataMock, activeViewerType: RichViewerMock.type }; createComponent({ data, }); - expect(wrapper.findComponent(RichViewer).exists()).toBe(true); + await waitForPromises(); + expect(findRichViewer().exists()).toBe(true); }); it('correctly switches viewer type', async () => { createComponent(); - expect(wrapper.findComponent(SimpleViewer).exists()).toBe(true); + await waitForPromises(); + + expect(findSimpleViewer().exists()).toBe(true); - wrapper.vm.switchViewer(RichViewerMock.type); + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE, RichViewerMock.type); + await waitForPromises(); - await nextTick(); - expect(wrapper.findComponent(RichViewer).exists()).toBe(true); - await wrapper.vm.switchViewer(SimpleViewerMock.type); + expect(findRichViewer().exists()).toBe(true); - expect(wrapper.findComponent(SimpleViewer).exists()).toBe(true); + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE, SimpleViewerMock.type); + await waitForPromises(); + + expect(findSimpleViewer().exists()).toBe(true); }); it('passes information about render error down to blob header', () => { @@ -110,7 +151,7 @@ describe('Blob Embeddable', () => { }, }); - expect(wrapper.findComponent(BlobHeader).props('hasRenderError')).toBe(true); + expect(findBlobHeader().props('hasRenderError')).toBe(true); }); describe('bob content in multi-file scenario', () => { @@ -123,47 +164,38 @@ describe('Blob Embeddable', () => { richData: 'Another Rich Foo', }; + const MixedSimpleBlobContentMock = { + ...SimpleBlobContentMock, + richData: '<h1>Rich</h1>', + }; + + const MixedRichBlobContentMock = { + ...RichBlobContentMock, + plainData: 'Plain', + }; + it.each` - snippetBlobs | description | currentBlob | expectedContent - ${[SimpleBlobContentMock]} | ${'one existing textual blob'} | ${SimpleBlobContentMock} | ${SimpleBlobContentMock.plainData} - ${[RichBlobContentMock]} | ${'one existing rich blob'} | ${RichBlobContentMock} | ${RichBlobContentMock.richData} - ${[SimpleBlobContentMock, RichBlobContentMock]} | ${'mixed blobs with current textual blob'} | ${SimpleBlobContentMock} | ${SimpleBlobContentMock.plainData} - ${[SimpleBlobContentMock, RichBlobContentMock]} | ${'mixed blobs with current rich blob'} | ${RichBlobContentMock} | ${RichBlobContentMock.richData} - ${[SimpleBlobContentMock, SimpleBlobContentMock2]} | ${'textual blobs with current textual blob'} | ${SimpleBlobContentMock} | ${SimpleBlobContentMock.plainData} - ${[RichBlobContentMock, RichBlobContentMock2]} | ${'rich blobs with current rich blob'} | ${RichBlobContentMock} | ${RichBlobContentMock.richData} + snippetBlobs | description | currentBlob | expectedContent | activeViewerType + ${[SimpleBlobContentMock]} | ${'one existing textual blob'} | ${SimpleBlobContentMock} | ${SimpleBlobContentMock.plainData} | ${SimpleViewerMock.type} + ${[RichBlobContentMock]} | ${'one existing rich blob'} | ${RichBlobContentMock} | ${RichBlobContentMock.richData} | ${RichViewerMock.type} + ${[SimpleBlobContentMock, MixedRichBlobContentMock]} | ${'mixed blobs with current textual blob'} | ${SimpleBlobContentMock} | ${SimpleBlobContentMock.plainData} | ${SimpleViewerMock.type} + ${[MixedSimpleBlobContentMock, RichBlobContentMock]} | ${'mixed blobs with current rich blob'} | ${RichBlobContentMock} | ${RichBlobContentMock.richData} | ${RichViewerMock.type} + ${[SimpleBlobContentMock, SimpleBlobContentMock2]} | ${'textual blobs with current textual blob'} | ${SimpleBlobContentMock} | ${SimpleBlobContentMock.plainData} | ${SimpleViewerMock.type} + ${[RichBlobContentMock, RichBlobContentMock2]} | ${'rich blobs with current rich blob'} | ${RichBlobContentMock} | ${RichBlobContentMock.richData} | ${RichViewerMock.type} `( 'renders correct content for $description', - async ({ snippetBlobs, currentBlob, expectedContent }) => { - const apolloData = { - snippets: { - nodes: [ - { - blobs: { - nodes: snippetBlobs, - }, - }, - ], - }, - }; + async ({ snippetBlobs, currentBlob, expectedContent, activeViewerType }) => { createComponent({ + handler: mockDefaultHandler({ path: currentBlob.path, nodes: snippetBlobs }), + data: { activeViewerType }, blob: { ...BlobMock, path: currentBlob.path, }, }); + await waitForPromises(); - // mimic apollo's update - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ - blobContent: wrapper.vm.onContentUpdate(apolloData), - }); - - await nextTick(); - - const findContent = () => wrapper.findComponent(BlobContent); - - expect(findContent().props('content')).toBe(expectedContent); + expect(findBlobContent().props('content')).toBe(expectedContent); }, ); }); @@ -178,28 +210,32 @@ describe('Blob Embeddable', () => { window.location.hash = '#LC2'; }); - it('renders simple viewer by default', () => { + it('renders simple viewer by default', async () => { createComponent({ data: {}, }); + await waitForPromises(); - expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type); - expect(wrapper.findComponent(SimpleViewer).exists()).toBe(true); + expect(findBlobHeader().props('activeViewerType')).toBe(SimpleViewerMock.type); + expect(findSimpleViewer().exists()).toBe(true); }); describe('switchViewer()', () => { it('switches to the passed viewer', async () => { createComponent(); + await waitForPromises(); + + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE, RichViewerMock.type); + await waitForPromises(); - wrapper.vm.switchViewer(RichViewerMock.type); + expect(findBlobHeader().props('activeViewerType')).toBe(RichViewerMock.type); + expect(findRichViewer().exists()).toBe(true); - await nextTick(); - expect(wrapper.vm.activeViewerType).toBe(RichViewerMock.type); - expect(wrapper.findComponent(RichViewer).exists()).toBe(true); + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE, SimpleViewerMock.type); + await waitForPromises(); - await wrapper.vm.switchViewer(SimpleViewerMock.type); - expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type); - expect(wrapper.findComponent(SimpleViewer).exists()).toBe(true); + expect(findBlobHeader().props('activeViewerType')).toBe(SimpleViewerMock.type); + expect(findSimpleViewer().exists()).toBe(true); }); }); }); @@ -209,28 +245,32 @@ describe('Blob Embeddable', () => { window.location.hash = '#last-headline'; }); - it('renders rich viewer by default', () => { + it('renders rich viewer by default', async () => { createComponent({ data: {}, }); + await waitForPromises(); - expect(wrapper.vm.activeViewerType).toBe(RichViewerMock.type); - expect(wrapper.findComponent(RichViewer).exists()).toBe(true); + expect(findBlobHeader().props('activeViewerType')).toBe(RichViewerMock.type); + expect(findRichViewer().exists()).toBe(true); }); describe('switchViewer()', () => { it('switches to the passed viewer', async () => { createComponent(); + await waitForPromises(); - wrapper.vm.switchViewer(SimpleViewerMock.type); + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE, SimpleViewerMock.type); + await waitForPromises(); - await nextTick(); - expect(wrapper.vm.activeViewerType).toBe(SimpleViewerMock.type); - expect(wrapper.findComponent(SimpleViewer).exists()).toBe(true); + expect(findBlobHeader().props('activeViewerType')).toBe(SimpleViewerMock.type); + expect(findSimpleViewer().exists()).toBe(true); - await wrapper.vm.switchViewer(RichViewerMock.type); - expect(wrapper.vm.activeViewerType).toBe(RichViewerMock.type); - expect(wrapper.findComponent(RichViewer).exists()).toBe(true); + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE, RichViewerMock.type); + await waitForPromises(); + + expect(findBlobHeader().props('activeViewerType')).toBe(RichViewerMock.type); + expect(findRichViewer().exists()).toBe(true); }); }); }); @@ -239,19 +279,21 @@ describe('Blob Embeddable', () => { describe('functionality', () => { describe('render error', () => { - const findContentEl = () => wrapper.findComponent(BlobContent); - it('correctly sets blob on the blob-content-error component', () => { createComponent(); - expect(findContentEl().props('blob')).toEqual(BlobMock); + expect(findBlobContent().props('blob')).toEqual(BlobMock); }); - it(`refetches blob content on ${BLOB_RENDER_EVENT_LOAD} event`, () => { + it(`refetches blob content on ${BLOB_RENDER_EVENT_LOAD} event`, async () => { createComponent(); + await waitForPromises(); + + expect(requestHandlers).toHaveBeenCalledTimes(1); + + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_LOAD); + await waitForPromises(); - expect(wrapper.vm.$apollo.queries.blobContent.refetch).not.toHaveBeenCalled(); - findContentEl().vm.$emit(BLOB_RENDER_EVENT_LOAD); - expect(wrapper.vm.$apollo.queries.blobContent.refetch).toHaveBeenCalledTimes(1); + expect(requestHandlers).toHaveBeenCalledTimes(2); }); it(`sets '${SimpleViewerMock.type}' as active on ${BLOB_RENDER_EVENT_SHOW_SOURCE} event`, () => { @@ -261,7 +303,7 @@ describe('Blob Embeddable', () => { }, }); - findContentEl().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE); + findBlobContent().vm.$emit(BLOB_RENDER_EVENT_SHOW_SOURCE); expect(wrapper.vm.activeViewerType).toEqual(SimpleViewerMock.type); }); }); diff --git a/spec/frontend/snippets/components/snippet_description_edit_spec.js b/spec/frontend/snippets/components/snippet_description_edit_spec.js index ff75515e71a..2b42eba19c2 100644 --- a/spec/frontend/snippets/components/snippet_description_edit_spec.js +++ b/spec/frontend/snippets/components/snippet_description_edit_spec.js @@ -30,10 +30,6 @@ describe('Snippet Description Edit component', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - describe('rendering', () => { it('matches the snapshot', () => { expect(wrapper.element).toMatchSnapshot(); diff --git a/spec/frontend/snippets/components/snippet_description_view_spec.js b/spec/frontend/snippets/components/snippet_description_view_spec.js index 14f116f2aaf..3c5d50ccaa6 100644 --- a/spec/frontend/snippets/components/snippet_description_view_spec.js +++ b/spec/frontend/snippets/components/snippet_description_view_spec.js @@ -17,10 +17,6 @@ describe('Snippet Description component', () => { createComponent(); }); - afterEach(() => { - wrapper.destroy(); - }); - it('matches the snapshot', () => { expect(wrapper.element).toMatchSnapshot(); }); diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js index c930c9f635b..4bf64bfd3cd 100644 --- a/spec/frontend/snippets/components/snippet_header_spec.js +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -1,8 +1,9 @@ -import { GlButton, GlModal, GlDropdown } from '@gitlab/ui'; +import { GlModal, GlButton, GlDropdown } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; -import { ApolloMutation } from 'vue-apollo'; +import VueApollo from 'vue-apollo'; import MockAdapter from 'axios-mock-adapter'; -import { nextTick } from 'vue'; +import Vue, { nextTick } from 'vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { Blob, BinaryBlob } from 'jest/blob/components/mock_data'; @@ -10,31 +11,41 @@ import { differenceInMilliseconds } from '~/lib/utils/datetime_utility'; import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue'; import DeleteSnippetMutation from '~/snippets/mutations/delete_snippet.mutation.graphql'; import axios from '~/lib/utils/axios_utils'; -import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/flash'; +import { createAlert, VARIANT_DANGER, VARIANT_SUCCESS } from '~/alert'; +import CanCreateProjectSnippet from 'shared_queries/snippet/project_permissions.query.graphql'; +import CanCreatePersonalSnippet from 'shared_queries/snippet/user_permissions.query.graphql'; +import { getCanCreateProjectSnippetMock, getCanCreatePersonalSnippetMock } from '../mock_data'; -jest.mock('~/flash'); +const ERROR_MSG = 'Foo bar'; +const ERR = { message: ERROR_MSG }; + +const MUTATION_TYPES = { + RESOLVE: jest.fn().mockResolvedValue({ data: { destroySnippet: { errors: [] } } }), + REJECT: jest.fn().mockRejectedValue(ERR), +}; + +jest.mock('~/alert'); + +Vue.use(VueApollo); describe('Snippet header component', () => { let wrapper; let snippet; - let mutationTypes; - let mutationVariables; let mock; + let mockApollo; - let errorMsg; - let err; - const originalRelativeUrlRoot = gon.relative_url_root; const reportAbusePath = '/-/snippets/42/mark_as_spam'; const canReportSpam = true; const GlEmoji = { template: '<img/>' }; function createComponent({ - loading = false, permissions = {}, - mutationRes = mutationTypes.RESOLVE, snippetProps = {}, provide = {}, + canCreateProjectSnippetMock = jest.fn().mockResolvedValue(getCanCreateProjectSnippetMock()), + canCreatePersonalSnippetMock = jest.fn().mockResolvedValue(getCanCreatePersonalSnippetMock()), + deleteSnippetMock = MUTATION_TYPES.RESOLVE, } = {}) { const defaultProps = Object.assign(snippet, snippetProps); if (permissions) { @@ -42,17 +53,14 @@ describe('Snippet header component', () => { ...permissions, }); } - const $apollo = { - queries: { - canCreateSnippet: { - loading, - }, - }, - mutate: mutationRes, - }; + + mockApollo = createMockApollo([ + [CanCreateProjectSnippet, canCreateProjectSnippetMock], + [CanCreatePersonalSnippet, canCreatePersonalSnippetMock], + [DeleteSnippetMutation, deleteSnippetMock], + ]); wrapper = mount(SnippetHeader, { - mocks: { $apollo }, provide: { reportAbusePath, canReportSpam, @@ -64,9 +72,9 @@ describe('Snippet header component', () => { }, }, stubs: { - ApolloMutation, GlEmoji, }, + apolloProvider: mockApollo, }); } @@ -91,6 +99,7 @@ describe('Snippet header component', () => { title: x.attributes('title'), text: x.text(), })); + const findDeleteModal = () => wrapper.findComponent(GlModal); beforeEach(() => { gon.relative_url_root = '/foo/'; @@ -113,28 +122,12 @@ describe('Snippet header component', () => { createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(), }; - mutationVariables = { - mutation: DeleteSnippetMutation, - variables: { - id: snippet.id, - }, - }; - - errorMsg = 'Foo bar'; - err = { message: errorMsg }; - - mutationTypes = { - RESOLVE: jest.fn(() => Promise.resolve({ data: { destroySnippet: { errors: [] } } })), - REJECT: jest.fn(() => Promise.reject(err)), - }; - mock = new MockAdapter(axios); }); afterEach(() => { - wrapper.destroy(); + mockApollo = null; mock.restore(); - gon.relative_url_root = originalRelativeUrlRoot; }); it('renders itself', () => { @@ -238,15 +231,16 @@ describe('Snippet header component', () => { }); it('with canCreateSnippet permission, renders create button', async () => { - createComponent(); + createComponent({ + canCreateProjectSnippetMock: jest + .fn() + .mockResolvedValue(getCanCreateProjectSnippetMock(true)), + canCreatePersonalSnippetMock: jest + .fn() + .mockResolvedValue(getCanCreatePersonalSnippetMock(true)), + }); - // TODO: we should avoid `wrapper.setData` since they - // are component internals. Let's use the apollo mock helpers - // in a follow-up. - // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details - // eslint-disable-next-line no-restricted-syntax - wrapper.setData({ canCreateSnippet: true }); - await nextTick(); + await waitForPromises(); expect(findButtonsAsModel()).toEqual( expect.arrayContaining([ @@ -262,7 +256,7 @@ describe('Snippet header component', () => { }); describe('submit snippet as spam', () => { - beforeEach(async () => { + beforeEach(() => { createComponent(); }); @@ -271,7 +265,7 @@ describe('Snippet header component', () => { ${200} | ${VARIANT_SUCCESS} | ${i18n.snippetSpamSuccess} ${500} | ${VARIANT_DANGER} | ${i18n.snippetSpamFailure} `( - 'renders a "$variant" flash message with "$text" message for a request with a "$request" response', + 'renders a "$variant" alert message with "$text" message for a request with a "$request" response', async ({ request, variant, text }) => { const submitAsSpamBtn = findButtons().at(2); mock.onPost(reportAbusePath).reply(request); @@ -329,21 +323,37 @@ describe('Snippet header component', () => { }); describe('Delete mutation', () => { - it('dispatches a mutation to delete the snippet with correct variables', () => { + const deleteSnippet = async () => { + // Click delete action + findButtons().at(1).trigger('click'); + await nextTick(); + + expect(findDeleteModal().props().visible).toBe(true); + + // Click delete button in delete modal + document.querySelector('[data-testid="delete-snippet"').click(); + await waitForPromises(); + }; + + it('dispatches a mutation to delete the snippet with correct variables', async () => { createComponent(); - wrapper.vm.deleteSnippet(); - expect(mutationTypes.RESOLVE).toHaveBeenCalledWith(mutationVariables); + + await deleteSnippet(); + + expect(MUTATION_TYPES.RESOLVE).toHaveBeenCalledWith({ + id: snippet.id, + }); }); it('sets error message if mutation fails', async () => { - createComponent({ mutationRes: mutationTypes.REJECT }); + createComponent({ deleteSnippetMock: MUTATION_TYPES.REJECT }); expect(Boolean(wrapper.vm.errorMessage)).toBe(false); - wrapper.vm.deleteSnippet(); - - await waitForPromises(); + await deleteSnippet(); - expect(wrapper.vm.errorMessage).toEqual(errorMsg); + expect(document.querySelector('[data-testid="delete-alert"').textContent.trim()).toBe( + ERROR_MSG, + ); }); describe('in case of successful mutation, closes modal and redirects to correct listing', () => { @@ -353,15 +363,16 @@ describe('Snippet header component', () => { createComponent({ snippetProps, }); - wrapper.vm.closeDeleteModal = jest.fn(); - wrapper.vm.deleteSnippet(); - await nextTick(); + await deleteSnippet(); }; it('redirects to dashboard/snippets for personal snippet', async () => { await createDeleteSnippet(); - expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled(); + + // Check that the modal is hidden after deleting the snippet + expect(findDeleteModal().props().visible).toBe(false); + expect(window.location.pathname).toBe(`${gon.relative_url_root}dashboard/snippets`); }); @@ -372,7 +383,10 @@ describe('Snippet header component', () => { fullPath, }, }); - expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled(); + + // Check that the modal is hidden after deleting the snippet + expect(findDeleteModal().props().visible).toBe(false); + expect(window.location.pathname).toBe(`${fullPath}/-/snippets`); }); }); diff --git a/spec/frontend/snippets/components/snippet_title_spec.js b/spec/frontend/snippets/components/snippet_title_spec.js index 7c40735d64e..0a3b57c9244 100644 --- a/spec/frontend/snippets/components/snippet_title_spec.js +++ b/spec/frontend/snippets/components/snippet_title_spec.js @@ -26,10 +26,6 @@ describe('Snippet header component', () => { }); } - afterEach(() => { - wrapper.destroy(); - }); - it('renders itself', () => { createComponent(); expect(wrapper.find('.snippet-header').exists()).toBe(true); diff --git a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js index 29eb002ef4a..70eb719f706 100644 --- a/spec/frontend/snippets/components/snippet_visibility_edit_spec.js +++ b/spec/frontend/snippets/components/snippet_visibility_edit_spec.js @@ -51,10 +51,6 @@ describe('Snippet Visibility Edit component', () => { }; }); - afterEach(() => { - wrapper.destroy(); - }); - describe('rendering', () => { it('matches the snapshot', () => { createComponent(); diff --git a/spec/frontend/snippets/mock_data.js b/spec/frontend/snippets/mock_data.js new file mode 100644 index 00000000000..7546fa575c6 --- /dev/null +++ b/spec/frontend/snippets/mock_data.js @@ -0,0 +1,19 @@ +export const getCanCreateProjectSnippetMock = (createSnippet = false) => ({ + data: { + project: { + userPermissions: { + createSnippet, + }, + }, + }, +}); + +export const getCanCreatePersonalSnippetMock = (createSnippet = false) => ({ + data: { + currentUser: { + userPermissions: { + createSnippet, + }, + }, + }, +}); |