2020-10-24 23:57:45 +05:30
|
|
|
import { ApolloMutation } from 'vue-apollo';
|
2020-04-22 19:07:51 +05:30
|
|
|
import { GlLoadingIcon } from '@gitlab/ui';
|
2020-10-24 23:57:45 +05:30
|
|
|
import { shallowMount } from '@vue/test-utils';
|
|
|
|
import waitForPromises from 'helpers/wait_for_promises';
|
|
|
|
import { deprecatedCreateFlash as Flash } from '~/flash';
|
|
|
|
import * as urlUtils from '~/lib/utils/url_utility';
|
2020-04-22 19:07:51 +05:30
|
|
|
import SnippetEditApp from '~/snippets/components/edit.vue';
|
|
|
|
import SnippetDescriptionEdit from '~/snippets/components/snippet_description_edit.vue';
|
|
|
|
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
|
2020-10-24 23:57:45 +05:30
|
|
|
import SnippetBlobActionsEdit from '~/snippets/components/snippet_blob_actions_edit.vue';
|
2020-04-22 19:07:51 +05:30
|
|
|
import TitleField from '~/vue_shared/components/form/title.vue';
|
|
|
|
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
|
2020-10-24 23:57:45 +05:30
|
|
|
import { SNIPPET_VISIBILITY_PRIVATE } from '~/snippets/constants';
|
2020-04-22 19:07:51 +05:30
|
|
|
import UpdateSnippetMutation from '~/snippets/mutations/updateSnippet.mutation.graphql';
|
|
|
|
import CreateSnippetMutation from '~/snippets/mutations/createSnippet.mutation.graphql';
|
2020-10-24 23:57:45 +05:30
|
|
|
import { testEntries } from '../test_utils';
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
jest.mock('~/flash');
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
const TEST_UPLOADED_FILES = ['foo/bar.txt', 'alpha/beta.js'];
|
|
|
|
const TEST_API_ERROR = 'Ufff';
|
|
|
|
const TEST_MUTATION_ERROR = 'Bummer';
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
const TEST_ACTIONS = {
|
|
|
|
NO_CONTENT: {
|
|
|
|
...testEntries.created.diff,
|
|
|
|
content: '',
|
|
|
|
},
|
|
|
|
NO_PATH: {
|
|
|
|
...testEntries.created.diff,
|
|
|
|
filePath: '',
|
|
|
|
},
|
|
|
|
VALID: {
|
|
|
|
...testEntries.created.diff,
|
2020-07-28 23:09:34 +05:30
|
|
|
},
|
|
|
|
};
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
const TEST_WEB_URL = '/snippets/7';
|
|
|
|
|
|
|
|
const createTestSnippet = () => ({
|
|
|
|
webUrl: TEST_WEB_URL,
|
|
|
|
id: 7,
|
|
|
|
title: 'Snippet Title',
|
|
|
|
description: 'Lorem ipsum snippet desc',
|
|
|
|
visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
|
|
|
|
});
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
describe('Snippet Edit app', () => {
|
|
|
|
let wrapper;
|
2020-11-24 15:15:51 +05:30
|
|
|
const relativeUrlRoot = '/foo/';
|
|
|
|
const originalRelativeUrlRoot = gon.relative_url_root;
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
const mutationTypes = {
|
|
|
|
RESOLVE: jest.fn().mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
updateSnippet: {
|
|
|
|
errors: [],
|
|
|
|
snippet: createTestSnippet(),
|
2020-04-22 19:07:51 +05:30
|
|
|
},
|
|
|
|
},
|
2020-10-24 23:57:45 +05:30
|
|
|
}),
|
|
|
|
RESOLVE_WITH_ERRORS: jest.fn().mockResolvedValue({
|
|
|
|
data: {
|
|
|
|
updateSnippet: {
|
|
|
|
errors: [TEST_MUTATION_ERROR],
|
|
|
|
snippet: createTestSnippet(),
|
|
|
|
},
|
|
|
|
createSnippet: {
|
|
|
|
errors: [TEST_MUTATION_ERROR],
|
|
|
|
snippet: null,
|
2020-06-23 00:09:42 +05:30
|
|
|
},
|
|
|
|
},
|
2020-10-24 23:57:45 +05:30
|
|
|
}),
|
|
|
|
REJECT: jest.fn().mockRejectedValue(TEST_API_ERROR),
|
2020-04-22 19:07:51 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
function createComponent({
|
2020-10-24 23:57:45 +05:30
|
|
|
props = {},
|
2020-04-22 19:07:51 +05:30
|
|
|
loading = false,
|
|
|
|
mutationRes = mutationTypes.RESOLVE,
|
|
|
|
} = {}) {
|
2020-10-24 23:57:45 +05:30
|
|
|
if (wrapper) {
|
|
|
|
throw new Error('wrapper already exists');
|
|
|
|
}
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
wrapper = shallowMount(SnippetEditApp, {
|
2020-10-24 23:57:45 +05:30
|
|
|
mocks: {
|
|
|
|
$apollo: {
|
|
|
|
queries: {
|
|
|
|
snippet: { loading },
|
|
|
|
},
|
|
|
|
mutate: mutationRes,
|
|
|
|
},
|
|
|
|
},
|
2020-04-22 19:07:51 +05:30
|
|
|
stubs: {
|
|
|
|
ApolloMutation,
|
2020-10-24 23:57:45 +05:30
|
|
|
FormFooterActions,
|
2020-04-22 19:07:51 +05:30
|
|
|
},
|
|
|
|
propsData: {
|
2020-10-24 23:57:45 +05:30
|
|
|
snippetGid: 'gid://gitlab/PersonalSnippet/42',
|
|
|
|
markdownPreviewPath: 'http://preview.foo.bar',
|
|
|
|
markdownDocsPath: 'http://docs.foo.bar',
|
2020-04-22 19:07:51 +05:30
|
|
|
...props,
|
|
|
|
},
|
2020-11-24 15:15:51 +05:30
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
snippet: {
|
|
|
|
visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
},
|
2020-04-22 19:07:51 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
beforeEach(() => {
|
2020-11-24 15:15:51 +05:30
|
|
|
gon.relative_url_root = relativeUrlRoot;
|
2020-10-24 23:57:45 +05:30
|
|
|
jest.spyOn(urlUtils, 'redirectTo').mockImplementation();
|
|
|
|
});
|
|
|
|
|
2020-04-22 19:07:51 +05:30
|
|
|
afterEach(() => {
|
|
|
|
wrapper.destroy();
|
2020-10-24 23:57:45 +05:30
|
|
|
wrapper = null;
|
2020-11-24 15:15:51 +05:30
|
|
|
gon.relative_url_root = originalRelativeUrlRoot;
|
2020-04-22 19:07:51 +05:30
|
|
|
});
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
const findBlobActions = () => wrapper.find(SnippetBlobActionsEdit);
|
2020-06-23 00:09:42 +05:30
|
|
|
const findSubmitButton = () => wrapper.find('[data-testid="snippet-submit-btn"]');
|
2020-10-24 23:57:45 +05:30
|
|
|
const findCancelButton = () => wrapper.find('[data-testid="snippet-cancel-btn"]');
|
|
|
|
const hasDisabledSubmit = () => Boolean(findSubmitButton().attributes('disabled'));
|
|
|
|
|
2020-06-23 00:09:42 +05:30
|
|
|
const clickSubmitBtn = () => wrapper.find('[data-testid="snippet-edit-form"]').trigger('submit');
|
2020-10-24 23:57:45 +05:30
|
|
|
const triggerBlobActions = actions => findBlobActions().vm.$emit('actions', actions);
|
|
|
|
const setUploadFilesHtml = paths => {
|
|
|
|
wrapper.vm.$el.innerHTML = paths.map(path => `<input name="files[]" value="${path}">`).join('');
|
|
|
|
};
|
|
|
|
const getApiData = ({
|
|
|
|
id,
|
|
|
|
title = '',
|
|
|
|
description = '',
|
|
|
|
visibilityLevel = SNIPPET_VISIBILITY_PRIVATE,
|
|
|
|
} = {}) => ({
|
|
|
|
id,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
visibilityLevel,
|
|
|
|
blobActions: [],
|
|
|
|
});
|
|
|
|
|
|
|
|
// Ideally we wouldn't call this method directly, but we don't have a way to trigger
|
|
|
|
// apollo responses yet.
|
2021-01-03 14:25:43 +05:30
|
|
|
const loadSnippet = (...nodes) => {
|
|
|
|
if (nodes.length) {
|
2020-10-24 23:57:45 +05:30
|
|
|
wrapper.setData({
|
2021-01-03 14:25:43 +05:30
|
|
|
snippet: nodes[0],
|
2020-10-24 23:57:45 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapper.vm.onSnippetFetch({
|
|
|
|
data: {
|
|
|
|
snippets: {
|
2021-01-03 14:25:43 +05:30
|
|
|
nodes,
|
2020-10-24 23:57:45 +05:30
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
};
|
2020-04-22 19:07:51 +05:30
|
|
|
|
|
|
|
describe('rendering', () => {
|
|
|
|
it('renders loader while the query is in flight', () => {
|
|
|
|
createComponent({ loading: true });
|
|
|
|
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
|
|
|
});
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
it.each([[{}], [{ snippetGid: '' }]])(
|
|
|
|
'should render all required components with %s',
|
|
|
|
props => {
|
|
|
|
createComponent(props);
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-11-24 15:15:51 +05:30
|
|
|
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);
|
2020-10-24 23:57:45 +05:30
|
|
|
expect(findBlobActions().exists()).toBe(true);
|
2020-04-22 19:07:51 +05:30
|
|
|
},
|
|
|
|
);
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
it.each`
|
2020-10-24 23:57:45 +05:30
|
|
|
title | actions | shouldDisable
|
|
|
|
${''} | ${[]} | ${true}
|
|
|
|
${''} | ${[TEST_ACTIONS.VALID]} | ${true}
|
|
|
|
${'foo'} | ${[]} | ${false}
|
|
|
|
${'foo'} | ${[TEST_ACTIONS.VALID]} | ${false}
|
|
|
|
${'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)',
|
|
|
|
async ({ title, actions, shouldDisable }) => {
|
|
|
|
createComponent();
|
2020-05-24 23:13:21 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
loadSnippet({ title });
|
|
|
|
triggerBlobActions(actions);
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
await wrapper.vm.$nextTick();
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
expect(hasDisabledSubmit()).toBe(shouldDisable);
|
|
|
|
},
|
|
|
|
);
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
it.each`
|
|
|
|
projectPath | snippetArg | expectation
|
2020-11-24 15:15:51 +05:30
|
|
|
${''} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, '-', 'snippets')}
|
|
|
|
${'project/path'} | ${[]} | ${urlUtils.joinPaths('/', relativeUrlRoot, 'project/path/-', 'snippets')}
|
2020-10-24 23:57:45 +05:30
|
|
|
${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
|
|
|
|
${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
|
|
|
|
`(
|
|
|
|
'should set cancel href when (projectPath=$projectPath, snippet=$snippetArg)',
|
|
|
|
async ({ projectPath, snippetArg, expectation }) => {
|
2020-04-22 19:07:51 +05:30
|
|
|
createComponent({
|
2020-10-24 23:57:45 +05:30
|
|
|
props: { projectPath },
|
2020-04-22 19:07:51 +05:30
|
|
|
});
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
loadSnippet(...snippetArg);
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
await wrapper.vm.$nextTick();
|
2020-06-23 00:09:42 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
expect(findCancelButton().attributes('href')).toBe(expectation);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
2020-04-22 19:07:51 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
describe('functionality', () => {
|
|
|
|
describe('form submission handling', () => {
|
2020-06-23 00:09:42 +05:30
|
|
|
it.each`
|
2020-10-24 23:57:45 +05:30
|
|
|
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}
|
2020-06-23 00:09:42 +05:30
|
|
|
`(
|
2020-10-24 23:57:45 +05:30
|
|
|
'should submit mutation with (snippet=$snippetArg, projectPath=$projectPath, uploadedFiles=$uploadedFiles)',
|
|
|
|
async ({ snippetArg, projectPath, uploadedFiles, mutation, input }) => {
|
2020-06-23 00:09:42 +05:30
|
|
|
createComponent({
|
|
|
|
props: {
|
|
|
|
projectPath,
|
|
|
|
},
|
|
|
|
});
|
2020-10-24 23:57:45 +05:30
|
|
|
loadSnippet(...snippetArg);
|
|
|
|
setUploadFilesHtml(uploadedFiles);
|
|
|
|
|
|
|
|
await wrapper.vm.$nextTick();
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
clickSubmitBtn();
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
expect(mutationTypes.RESOLVE).toHaveBeenCalledWith({
|
|
|
|
mutation,
|
|
|
|
variables: {
|
|
|
|
input,
|
|
|
|
},
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
it('should redirect to snippet view on successful mutation', async () => {
|
|
|
|
createComponent();
|
|
|
|
loadSnippet(createTestSnippet());
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
clickSubmitBtn();
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
await waitForPromises();
|
|
|
|
|
|
|
|
expect(urlUtils.redirectTo).toHaveBeenCalledWith(TEST_WEB_URL);
|
2020-04-22 19:07:51 +05:30
|
|
|
});
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
it.each`
|
2020-10-24 23:57:45 +05:30
|
|
|
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}`}
|
2020-06-23 00:09:42 +05:30
|
|
|
`(
|
2020-10-24 23:57:45 +05:30
|
|
|
'should flash error with (snippet=$snippetArg, projectPath=$projectPath)',
|
|
|
|
async ({ snippetArg, projectPath, mutationRes, expectMessage }) => {
|
2020-06-23 00:09:42 +05:30
|
|
|
createComponent({
|
2020-10-24 23:57:45 +05:30
|
|
|
props: {
|
|
|
|
projectPath,
|
2020-06-23 00:09:42 +05:30
|
|
|
},
|
2020-10-24 23:57:45 +05:30
|
|
|
mutationRes,
|
2020-06-23 00:09:42 +05:30
|
|
|
});
|
2020-10-24 23:57:45 +05:30
|
|
|
loadSnippet(...snippetArg);
|
2020-06-23 00:09:42 +05:30
|
|
|
|
|
|
|
clickSubmitBtn();
|
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
await waitForPromises();
|
|
|
|
|
|
|
|
expect(urlUtils.redirectTo).not.toHaveBeenCalled();
|
|
|
|
expect(Flash).toHaveBeenCalledWith(expectMessage);
|
2020-06-23 00:09:42 +05:30
|
|
|
},
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2020-07-28 23:09:34 +05:30
|
|
|
describe('on before unload', () => {
|
2020-10-24 23:57:45 +05:30
|
|
|
it.each`
|
|
|
|
condition | expectPrevented | action
|
|
|
|
${'there are no actions'} | ${false} | ${() => triggerBlobActions([])}
|
|
|
|
${'there are actions'} | ${true} | ${() => triggerBlobActions([testEntries.updated.diff])}
|
|
|
|
${'the snippet is being saved'} | ${false} | ${() => clickSubmitBtn()}
|
|
|
|
`(
|
|
|
|
'handles before unload prevent when $condition (expectPrevented=$expectPrevented)',
|
|
|
|
({ expectPrevented, action }) => {
|
|
|
|
createComponent();
|
|
|
|
loadSnippet();
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
action();
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
const event = new Event('beforeunload');
|
|
|
|
const returnValueSetter = jest.spyOn(event, 'returnValue', 'set');
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
window.dispatchEvent(event);
|
2020-07-28 23:09:34 +05:30
|
|
|
|
2020-10-24 23:57:45 +05:30
|
|
|
if (expectPrevented) {
|
|
|
|
expect(returnValueSetter).toHaveBeenCalledWith(
|
|
|
|
'Are you sure you want to lose unsaved changes?',
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
expect(returnValueSetter).not.toHaveBeenCalled();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
2020-07-28 23:09:34 +05:30
|
|
|
});
|
2020-04-22 19:07:51 +05:30
|
|
|
});
|
|
|
|
});
|