summaryrefslogtreecommitdiff
path: root/spec/frontend/snippets/components/edit_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/snippets/components/edit_spec.js')
-rw-r--r--spec/frontend/snippets/components/edit_spec.js657
1 files changed, 306 insertions, 351 deletions
diff --git a/spec/frontend/snippets/components/edit_spec.js b/spec/frontend/snippets/components/edit_spec.js
index 1c06465907a..7a1c6e64614 100644
--- a/spec/frontend/snippets/components/edit_spec.js
+++ b/spec/frontend/snippets/components/edit_spec.js
@@ -2,6 +2,8 @@ import VueApollo, { ApolloMutation } from 'vue-apollo';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
+import { merge } from 'lodash';
+import { useFakeDate } from 'helpers/fake_date';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
@@ -22,162 +24,97 @@ import {
} from '~/snippets/constants';
import UpdateSnippetMutation from '~/snippets/mutations/updateSnippet.mutation.graphql';
import CreateSnippetMutation from '~/snippets/mutations/createSnippet.mutation.graphql';
-import { testEntries } from '../test_utils';
+import { testEntries, createGQLSnippetsQueryResponse, createGQLSnippet } from '../test_utils';
jest.mock('~/flash');
const TEST_UPLOADED_FILES = ['foo/bar.txt', 'alpha/beta.js'];
-const TEST_API_ERROR = 'Ufff';
-const TEST_MUTATION_ERROR = 'Bummer';
-
+const TEST_API_ERROR = new Error('TEST_API_ERROR');
+const TEST_MUTATION_ERROR = 'Test mutation error';
+const TEST_CAPTCHA_RESPONSE = 'i-got-a-captcha';
+const TEST_CAPTCHA_SITE_KEY = 'abc123';
const TEST_ACTIONS = {
- NO_CONTENT: {
- ...testEntries.created.diff,
- content: '',
- },
- NO_PATH: {
- ...testEntries.created.diff,
- filePath: '',
- },
- VALID: {
- ...testEntries.created.diff,
- },
+ NO_CONTENT: merge({}, testEntries.created.diff, { content: '' }),
+ NO_PATH: merge({}, testEntries.created.diff, { filePath: '' }),
+ VALID: merge({}, testEntries.created.diff),
};
-
const TEST_WEB_URL = '/snippets/7';
+const TEST_SNIPPET_GID = 'gid://gitlab/PersonalSnippet/42';
-const createTestSnippet = () => ({
- webUrl: TEST_WEB_URL,
- id: 7,
- title: 'Snippet Title',
- description: 'Lorem ipsum snippet desc',
- visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
+const createSnippet = () =>
+ merge(createGQLSnippet(), {
+ webUrl: TEST_WEB_URL,
+ visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
+ });
+
+const createQueryResponse = (obj = {}) =>
+ createGQLSnippetsQueryResponse([merge(createSnippet(), obj)]);
+
+const createMutationResponse = (key, obj = {}) => ({
+ data: {
+ [key]: merge(
+ {
+ errors: [],
+ snippet: {
+ __typename: 'Snippet',
+ webUrl: TEST_WEB_URL,
+ },
+ spamLogId: null,
+ needsCaptchaResponse: false,
+ captchaSiteKey: null,
+ },
+ obj,
+ ),
+ },
});
+const createMutationResponseWithErrors = (key) =>
+ createMutationResponse(key, { errors: [TEST_MUTATION_ERROR] });
+
+const createMutationResponseWithRecaptcha = (key) =>
+ createMutationResponse(key, {
+ errors: ['ignored captcha error message'],
+ needsCaptchaResponse: true,
+ captchaSiteKey: TEST_CAPTCHA_SITE_KEY,
+ });
+
+const getApiData = ({
+ id,
+ title = '',
+ description = '',
+ visibilityLevel = SNIPPET_VISIBILITY_PRIVATE,
+} = {}) => ({
+ id,
+ title,
+ description,
+ visibilityLevel,
+ blobActions: [],
+});
+
+const localVue = createLocalVue();
+localVue.use(VueApollo);
+
describe('Snippet Edit app', () => {
+ useFakeDate();
+
let wrapper;
- let fakeApollo;
- const captchaSiteKey = 'abc123';
+ let getSpy;
+
+ // Mutate spy receives a "key" so that we can:
+ // - Use the same spy whether we are creating or updating.
+ // - Build the correct response object
+ // - Assert which mutation was sent
+ let mutateSpy;
+
const relativeUrlRoot = '/foo/';
const originalRelativeUrlRoot = gon.relative_url_root;
- const GetSnippetQuerySpy = jest.fn().mockResolvedValue({
- data: { snippets: { nodes: [createTestSnippet()] } },
- });
- const mutationTypes = {
- RESOLVE: jest.fn().mockResolvedValue({
- data: {
- updateSnippet: {
- errors: [],
- snippet: createTestSnippet(),
- needsCaptchaResponse: null,
- captchaSiteKey: null,
- },
- },
- }),
- RESOLVE_WITH_ERRORS: jest.fn().mockResolvedValue({
- data: {
- updateSnippet: {
- errors: [TEST_MUTATION_ERROR],
- snippet: createTestSnippet(),
- needsCaptchaResponse: null,
- captchaSiteKey: null,
- },
- createSnippet: {
- errors: [TEST_MUTATION_ERROR],
- snippet: null,
- needsCaptchaResponse: null,
- captchaSiteKey: null,
- },
- },
- }),
- // TODO: QUESTION - This has to be wrapped in a factory function in order for the mock to have
- // the `mockResolvedValueOnce` counter properly cleared/reset between test `it` examples, by
- // ensuring each one gets a fresh mock instance. It's apparently impossible/hard to manually
- // clear/reset them (see https://github.com/facebook/jest/issues/7136). So, should
- // we convert all the others to factory functions too, to be consistent? And/or move the whole
- // `mutationTypes` declaration into a `beforeEach`? (not sure if that will still solve the
- // mock reset problem though).
- RESOLVE_WITH_NEEDS_CAPTCHA_RESPONSE: () =>
- jest
- .fn()
- // NOTE: There may be a captcha-related error, but it is not used in the GraphQL/Vue flow,
- // only a truthy 'needsCaptchaResponse' value is used to trigger the captcha modal showing.
- .mockResolvedValueOnce({
- data: {
- createSnippet: {
- errors: ['ignored captcha error message'],
- snippet: null,
- needsCaptchaResponse: true,
- captchaSiteKey,
- },
- },
- })
- // After the captcha is solved and the modal is closed, the second form submission should
- // be successful and return needsCaptchaResponse = false.
- .mockResolvedValueOnce({
- data: {
- createSnippet: {
- errors: ['ignored captcha error message'],
- snippet: createTestSnippet(),
- needsCaptchaResponse: false,
- captchaSiteKey: null,
- },
- },
- }),
- REJECT: jest.fn().mockRejectedValue(TEST_API_ERROR),
- };
+ beforeEach(() => {
+ getSpy = jest.fn().mockResolvedValue(createQueryResponse());
- function createComponent({
- props = {},
- loading = false,
- mutationRes = mutationTypes.RESOLVE,
- selectedLevel = SNIPPET_VISIBILITY_PRIVATE,
- withApollo = false,
- } = {}) {
- let componentData = {
- mocks: {
- $apollo: {
- queries: {
- snippet: { loading },
- },
- mutate: mutationRes,
- },
- },
- };
-
- if (withApollo) {
- const localVue = createLocalVue();
- localVue.use(VueApollo);
-
- const requestHandlers = [[GetSnippetQuery, GetSnippetQuerySpy]];
- fakeApollo = createMockApollo(requestHandlers);
- componentData = {
- localVue,
- apolloProvider: fakeApollo,
- };
- }
+ // See `mutateSpy` declaration comment for why we send a key
+ mutateSpy = jest.fn().mockImplementation((key) => Promise.resolve(createMutationResponse(key)));
- wrapper = shallowMount(SnippetEditApp, {
- ...componentData,
- stubs: {
- ApolloMutation,
- FormFooterActions,
- CaptchaModal: stubComponent(CaptchaModal),
- },
- provide: {
- selectedLevel,
- },
- propsData: {
- snippetGid: 'gid://gitlab/PersonalSnippet/42',
- markdownPreviewPath: 'http://preview.foo.bar',
- markdownDocsPath: 'http://docs.foo.bar',
- ...props,
- },
- });
- }
-
- beforeEach(() => {
gon.relative_url_root = relativeUrlRoot;
jest.spyOn(urlUtils, 'redirectTo').mockImplementation();
});
@@ -193,7 +130,6 @@ describe('Snippet Edit app', () => {
const findSubmitButton = () => wrapper.find('[data-testid="snippet-submit-btn"]');
const findCancelButton = () => wrapper.find('[data-testid="snippet-cancel-btn"]');
const hasDisabledSubmit = () => Boolean(findSubmitButton().attributes('disabled'));
-
const clickSubmitBtn = () => wrapper.find('[data-testid="snippet-edit-form"]').trigger('submit');
const triggerBlobActions = (actions) => findBlobActions().vm.$emit('actions', actions);
const setUploadFilesHtml = (paths) => {
@@ -201,56 +137,92 @@ describe('Snippet Edit app', () => {
.map((path) => `<input name="files[]" value="${path}">`)
.join('');
};
- const getApiData = ({
- id,
- title = '',
- description = '',
- visibilityLevel = SNIPPET_VISIBILITY_PRIVATE,
- } = {}) => ({
- id,
- title,
- description,
- visibilityLevel,
- blobActions: [],
- });
const setTitle = (val) => wrapper.find(TitleField).vm.$emit('input', val);
const setDescription = (val) => wrapper.find(SnippetDescriptionEdit).vm.$emit('input', val);
- // Ideally we wouldn't call this method directly, but we don't have a way to trigger
- // apollo responses yet.
- const loadSnippet = (...nodes) => {
- if (nodes.length) {
- wrapper.setData({
- snippet: nodes[0],
- newSnippet: false,
- });
- } else {
- wrapper.setData({
- newSnippet: true,
- });
+ const createComponent = ({ props = {}, selectedLevel = SNIPPET_VISIBILITY_PRIVATE } = {}) => {
+ if (wrapper) {
+ throw new Error('wrapper already created');
}
+
+ const requestHandlers = [
+ [GetSnippetQuery, getSpy],
+ // See `mutateSpy` declaration comment for why we send a key
+ [UpdateSnippetMutation, (...args) => mutateSpy('updateSnippet', ...args)],
+ [CreateSnippetMutation, (...args) => mutateSpy('createSnippet', ...args)],
+ ];
+ const apolloProvider = createMockApollo(requestHandlers);
+
+ wrapper = shallowMount(SnippetEditApp, {
+ apolloProvider,
+ localVue,
+ stubs: {
+ ApolloMutation,
+ FormFooterActions,
+ CaptchaModal: stubComponent(CaptchaModal),
+ },
+ provide: {
+ selectedLevel,
+ },
+ propsData: {
+ snippetGid: TEST_SNIPPET_GID,
+ markdownPreviewPath: 'http://preview.foo.bar',
+ markdownDocsPath: 'http://docs.foo.bar',
+ ...props,
+ },
+ });
+ };
+
+ // Creates comopnent and waits for gql load
+ const createComponentAndLoad = async (...args) => {
+ createComponent(...args);
+
+ await waitForPromises();
};
- describe('rendering', () => {
- it('renders loader while the query is in flight', () => {
- createComponent({ loading: true });
+ // Creates loaded component and submits form
+ const createComponentAndSubmit = async (...args) => {
+ await createComponentAndLoad(...args);
+
+ clickSubmitBtn();
+
+ await waitForPromises();
+ };
+
+ describe('when loading', () => {
+ it('renders loader', () => {
+ createComponent();
+
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
+ });
- it.each([[{}], [{ snippetGid: '' }]])(
- 'should render all required components with %s',
- (props) => {
- createComponent(props);
-
- expect(wrapper.find(CaptchaModal).exists()).toBe(true);
- expect(wrapper.find(TitleField).exists()).toBe(true);
- expect(wrapper.find(SnippetDescriptionEdit).exists()).toBe(true);
- expect(wrapper.find(SnippetVisibilityEdit).exists()).toBe(true);
- expect(wrapper.find(FormFooterActions).exists()).toBe(true);
- expect(findBlobActions().exists()).toBe(true);
- },
- );
+ describe.each`
+ snippetGid | expectedQueries
+ ${TEST_SNIPPET_GID} | ${[[{ ids: [TEST_SNIPPET_GID] }]]}
+ ${''} | ${[]}
+ `('when loaded with snippetGid=$snippetGid', ({ snippetGid, expectedQueries }) => {
+ beforeEach(() => createComponentAndLoad({ props: { snippetGid } }));
+ it(`queries with ${JSON.stringify(expectedQueries)}`, () => {
+ expect(getSpy.mock.calls).toEqual(expectedQueries);
+ });
+
+ it('should render components', () => {
+ expect(wrapper.find(CaptchaModal).exists()).toBe(true);
+ expect(wrapper.find(TitleField).exists()).toBe(true);
+ expect(wrapper.find(SnippetDescriptionEdit).exists()).toBe(true);
+ expect(wrapper.find(SnippetVisibilityEdit).exists()).toBe(true);
+ expect(wrapper.find(FormFooterActions).exists()).toBe(true);
+ expect(findBlobActions().exists()).toBe(true);
+ });
+
+ it('should hide loader', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ });
+ });
+
+ describe('default', () => {
it.each`
title | actions | shouldDisable
${''} | ${[]} | ${true}
@@ -260,11 +232,12 @@ describe('Snippet Edit app', () => {
${'foo'} | ${[TEST_ACTIONS.VALID, TEST_ACTIONS.NO_CONTENT]} | ${true}
${'foo'} | ${[TEST_ACTIONS.VALID, TEST_ACTIONS.NO_PATH]} | ${false}
`(
- 'should handle submit disable (title=$title, actions=$actions, shouldDisable=$shouldDisable)',
+ 'should handle submit disable (title="$title", actions="$actions", shouldDisable="$shouldDisable")',
async ({ title, actions, shouldDisable }) => {
- createComponent();
+ getSpy.mockResolvedValue(createQueryResponse({ title }));
+
+ await createComponentAndLoad();
- loadSnippet({ title });
triggerBlobActions(actions);
await nextTick();
@@ -274,244 +247,226 @@ describe('Snippet Edit app', () => {
);
it.each`
- projectPath | snippetArg | expectation
- ${''} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
- ${'project/path'} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
- ${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
- ${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
+ projectPath | snippetGid | expectation
+ ${''} | ${''} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
+ ${'project/path'} | ${''} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
+ ${''} | ${TEST_SNIPPET_GID} | ${TEST_WEB_URL}
+ ${'project/path'} | ${TEST_SNIPPET_GID} | ${TEST_WEB_URL}
`(
- 'should set cancel href when (projectPath=$projectPath, snippet=$snippetArg)',
- async ({ projectPath, snippetArg, expectation }) => {
- createComponent({
- props: { projectPath },
+ 'should set cancel href (projectPath="$projectPath", snippetGid="$snippetGid")',
+ async ({ projectPath, snippetGid, expectation }) => {
+ await createComponentAndLoad({
+ props: {
+ projectPath,
+ snippetGid,
+ },
});
- loadSnippet(...snippetArg);
-
- await nextTick();
-
expect(findCancelButton().attributes('href')).toBe(expectation);
},
);
- });
-
- describe('functionality', () => {
- it('does not fetch snippet when create a new snippet', async () => {
- createComponent({ props: { snippetGid: '' }, withApollo: true });
-
- jest.runOnlyPendingTimers();
- await nextTick();
- expect(GetSnippetQuerySpy).not.toHaveBeenCalled();
- });
+ it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])(
+ 'marks %s visibility by default',
+ async (visibility) => {
+ createComponent({
+ props: { snippetGid: '' },
+ selectedLevel: visibility,
+ });
- describe('default visibility', () => {
- it.each([SNIPPET_VISIBILITY_PRIVATE, SNIPPET_VISIBILITY_INTERNAL, SNIPPET_VISIBILITY_PUBLIC])(
- 'marks %s visibility by default',
- async (visibility) => {
- createComponent({
- props: { snippetGid: '' },
- selectedLevel: visibility,
- });
- expect(wrapper.vm.snippet.visibilityLevel).toEqual(visibility);
- },
- );
- });
+ expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility);
+ },
+ );
describe('form submission handling', () => {
it.each`
- snippetArg | projectPath | uploadedFiles | input | mutation
- ${[]} | ${'project/path'} | ${[]} | ${{ ...getApiData(), projectPath: 'project/path', uploadedFiles: [] }} | ${CreateSnippetMutation}
- ${[]} | ${''} | ${[]} | ${{ ...getApiData(), projectPath: '', uploadedFiles: [] }} | ${CreateSnippetMutation}
- ${[]} | ${''} | ${TEST_UPLOADED_FILES} | ${{ ...getApiData(), projectPath: '', uploadedFiles: TEST_UPLOADED_FILES }} | ${CreateSnippetMutation}
- ${[createTestSnippet()]} | ${'project/path'} | ${[]} | ${getApiData(createTestSnippet())} | ${UpdateSnippetMutation}
- ${[createTestSnippet()]} | ${''} | ${[]} | ${getApiData(createTestSnippet())} | ${UpdateSnippetMutation}
+ snippetGid | projectPath | uploadedFiles | input | mutationType
+ ${''} | ${'project/path'} | ${[]} | ${{ ...getApiData(), projectPath: 'project/path', uploadedFiles: [] }} | ${'createSnippet'}
+ ${''} | ${''} | ${[]} | ${{ ...getApiData(), projectPath: '', uploadedFiles: [] }} | ${'createSnippet'}
+ ${''} | ${''} | ${TEST_UPLOADED_FILES} | ${{ ...getApiData(), projectPath: '', uploadedFiles: TEST_UPLOADED_FILES }} | ${'createSnippet'}
+ ${TEST_SNIPPET_GID} | ${'project/path'} | ${[]} | ${getApiData(createSnippet())} | ${'updateSnippet'}
+ ${TEST_SNIPPET_GID} | ${''} | ${[]} | ${getApiData(createSnippet())} | ${'updateSnippet'}
`(
- 'should submit mutation with (snippet=$snippetArg, projectPath=$projectPath, uploadedFiles=$uploadedFiles)',
- async ({ snippetArg, projectPath, uploadedFiles, mutation, input }) => {
- createComponent({
+ 'should submit mutation $mutationType (snippetGid=$snippetGid, projectPath=$projectPath, uploadedFiles=$uploadedFiles)',
+ async ({ snippetGid, projectPath, uploadedFiles, mutationType, input }) => {
+ await createComponentAndLoad({
props: {
+ snippetGid,
projectPath,
},
});
- loadSnippet(...snippetArg);
+
setUploadFilesHtml(uploadedFiles);
await nextTick();
clickSubmitBtn();
- expect(mutationTypes.RESOLVE).toHaveBeenCalledWith({
- mutation,
- variables: {
- input,
- },
+ expect(mutateSpy).toHaveBeenCalledTimes(1);
+ expect(mutateSpy).toHaveBeenCalledWith(mutationType, {
+ input,
});
},
);
it('should redirect to snippet view on successful mutation', async () => {
- createComponent();
- loadSnippet(createTestSnippet());
-
- clickSubmitBtn();
-
- await waitForPromises();
+ await createComponentAndSubmit();
expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL);
});
it.each`
- snippetArg | projectPath | mutationRes | expectMessage
- ${[]} | ${'project/path'} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
- ${[]} | ${''} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
- ${[]} | ${''} | ${mutationTypes.REJECT} | ${`Can't create snippet: ${TEST_API_ERROR}`}
- ${[createTestSnippet()]} | ${'project/path'} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
- ${[createTestSnippet()]} | ${''} | ${mutationTypes.RESOLVE_WITH_ERRORS} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
+ snippetGid | projectPath | mutationRes | expectMessage
+ ${''} | ${'project/path'} | ${createMutationResponseWithErrors('createSnippet')} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
+ ${''} | ${''} | ${createMutationResponseWithErrors('createSnippet')} | ${`Can't create snippet: ${TEST_MUTATION_ERROR}`}
+ ${TEST_SNIPPET_GID} | ${'project/path'} | ${createMutationResponseWithErrors('updateSnippet')} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
+ ${TEST_SNIPPET_GID} | ${''} | ${createMutationResponseWithErrors('updateSnippet')} | ${`Can't update snippet: ${TEST_MUTATION_ERROR}`}
`(
- 'should flash error with (snippet=$snippetArg, projectPath=$projectPath)',
- async ({ snippetArg, projectPath, mutationRes, expectMessage }) => {
- createComponent({
+ 'should flash error with (snippet=$snippetGid, projectPath=$projectPath)',
+ async ({ snippetGid, projectPath, mutationRes, expectMessage }) => {
+ mutateSpy.mockResolvedValue(mutationRes);
+
+ await createComponentAndSubmit({
props: {
projectPath,
+ snippetGid,
},
- mutationRes,
});
- loadSnippet(...snippetArg);
-
- clickSubmitBtn();
-
- await waitForPromises();
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
expect(Flash).toHaveBeenCalledWith(expectMessage);
},
);
+ describe('with apollo network error', () => {
+ beforeEach(async () => {
+ jest.spyOn(console, 'error').mockImplementation();
+ mutateSpy.mockRejectedValue(TEST_API_ERROR);
+
+ await createComponentAndSubmit();
+ });
+
+ it('should not redirect', () => {
+ expect(urlUtils.redirectTo).not.toHaveBeenCalled();
+ });
+
+ it('should flash', () => {
+ // Apollo automatically wraps the resolver's error in a NetworkError
+ expect(Flash).toHaveBeenCalledWith(
+ `Can't update snippet: Network error: ${TEST_API_ERROR.message}`,
+ );
+ });
+
+ it('should console error', () => {
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledTimes(1);
+ // eslint-disable-next-line no-console
+ expect(console.error).toHaveBeenCalledWith(
+ '[gitlab] unexpected error while updating snippet',
+ expect.objectContaining({ message: `Network error: ${TEST_API_ERROR.message}` }),
+ );
+ });
+ });
+
describe('when needsCaptchaResponse is true', () => {
let modal;
- let captchaResponse;
- let mutationRes;
beforeEach(async () => {
- mutationRes = mutationTypes.RESOLVE_WITH_NEEDS_CAPTCHA_RESPONSE();
- createComponent({
- props: {
- snippetGid: '',
- projectPath: '',
- },
- mutationRes,
- });
- // await waitForPromises();
- modal = findCaptchaModal();
+ mutateSpy
+ .mockResolvedValueOnce(createMutationResponseWithRecaptcha('updateSnippet'))
+ .mockResolvedValueOnce(createMutationResponseWithErrors('updateSnippet'));
- loadSnippet();
+ await createComponentAndSubmit();
- clickSubmitBtn();
- await waitForPromises();
+ modal = findCaptchaModal();
+
+ mutateSpy.mockClear();
});
it('should display captcha modal', () => {
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
- expect(modal.props('needsCaptchaResponse')).toEqual(true);
- expect(modal.props('captchaSiteKey')).toEqual(captchaSiteKey);
- });
-
- describe('when a non-empty captcha response is received', () => {
- beforeEach(() => {
- captchaResponse = 'xyz123';
+ expect(modal.props()).toEqual({
+ needsCaptchaResponse: true,
+ captchaSiteKey: TEST_CAPTCHA_SITE_KEY,
});
+ });
- it('sets needsCaptchaResponse to false', async () => {
- modal.vm.$emit('receivedCaptchaResponse', captchaResponse);
- await nextTick();
- expect(modal.props('needsCaptchaResponse')).toEqual(false);
- });
+ describe.each`
+ response | expectedCalls
+ ${null} | ${[]}
+ ${TEST_CAPTCHA_RESPONSE} | ${[['updateSnippet', { input: { ...getApiData(createSnippet()), captchaResponse: TEST_CAPTCHA_RESPONSE } }]]}
+ `('when captcha response is $response', ({ response, expectedCalls }) => {
+ beforeEach(async () => {
+ modal.vm.$emit('receivedCaptchaResponse', response);
- it('resubmits form with captchaResponse', async () => {
- modal.vm.$emit('receivedCaptchaResponse', captchaResponse);
await nextTick();
- expect(mutationRes.mock.calls[1][0]).toEqual({
- mutation: CreateSnippetMutation,
- variables: {
- input: {
- ...getApiData(),
- captchaResponse,
- projectPath: '',
- uploadedFiles: [],
- },
- },
- });
- });
- });
-
- describe('when an empty captcha response is received ', () => {
- beforeEach(() => {
- captchaResponse = '';
});
- it('sets needsCaptchaResponse to false', async () => {
- modal.vm.$emit('receivedCaptchaResponse', captchaResponse);
- await nextTick();
+ it('sets needsCaptchaResponse to false', () => {
expect(modal.props('needsCaptchaResponse')).toEqual(false);
});
- it('does not resubmit form', async () => {
- modal.vm.$emit('receivedCaptchaResponse', captchaResponse);
- await nextTick();
- expect(mutationRes.mock.calls.length).toEqual(1);
+ it(`expected to call times = ${expectedCalls.length}`, () => {
+ expect(mutateSpy.mock.calls).toEqual(expectedCalls);
});
});
});
});
+ });
- describe('on before unload', () => {
- const caseNoActions = () => triggerBlobActions([]);
- const caseEmptyAction = () => triggerBlobActions([testEntries.empty.diff]);
- const caseSomeActions = () => triggerBlobActions([testEntries.updated.diff]);
- const caseTitleIsSet = () => {
- caseEmptyAction();
- setTitle('test');
- };
- const caseDescriptionIsSet = () => {
- caseEmptyAction();
- setDescription('test');
- };
- const caseClickSubmitBtn = () => {
- caseSomeActions();
- clickSubmitBtn();
- };
-
- it.each`
- condition | expectPrevented | action
- ${'there are no actions'} | ${false} | ${caseNoActions}
- ${'there is an empty action'} | ${false} | ${caseEmptyAction}
- ${'there are actions'} | ${true} | ${caseSomeActions}
- ${'the title is set'} | ${true} | ${caseTitleIsSet}
- ${'the description is set'} | ${true} | ${caseDescriptionIsSet}
- ${'the snippet is being saved'} | ${false} | ${caseClickSubmitBtn}
- `(
- 'handles before unload prevent when $condition (expectPrevented=$expectPrevented)',
- ({ expectPrevented, action }) => {
- createComponent();
- loadSnippet();
+ describe('on before unload', () => {
+ it.each([
+ ['there are no actions', false, () => triggerBlobActions([])],
+ ['there is an empty action', false, () => triggerBlobActions([testEntries.empty.diff])],
+ ['there are actions', true, () => triggerBlobActions([testEntries.updated.diff])],
+ [
+ 'the title is set',
+ true,
+ () => {
+ triggerBlobActions([testEntries.empty.diff]);
+ setTitle('test');
+ },
+ ],
+ [
+ 'the description is set',
+ true,
+ () => {
+ triggerBlobActions([testEntries.empty.diff]);
+ setDescription('test');
+ },
+ ],
+ [
+ 'the snippet is being saved',
+ false,
+ () => {
+ triggerBlobActions([testEntries.updated.diff]);
+ clickSubmitBtn();
+ },
+ ],
+ ])(
+ 'handles before unload prevent when %s (expectPrevented=%s)',
+ async (_, expectPrevented, action) => {
+ await createComponentAndLoad({
+ props: {
+ snippetGid: '',
+ },
+ });
- action();
+ action();
- const event = new Event('beforeunload');
- const returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
+ const event = new Event('beforeunload');
+ const returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
- window.dispatchEvent(event);
+ window.dispatchEvent(event);
- if (expectPrevented) {
- expect(returnValueSetter).toHaveBeenCalledWith(
- 'Are you sure you want to lose unsaved changes?',
- );
- } else {
- expect(returnValueSetter).not.toHaveBeenCalled();
- }
- },
- );
- });
+ if (expectPrevented) {
+ expect(returnValueSetter).toHaveBeenCalledWith(
+ 'Are you sure you want to lose unsaved changes?',
+ );
+ } else {
+ expect(returnValueSetter).not.toHaveBeenCalled();
+ }
+ },
+ );
});
});