debian-mirror-gitlab/spec/frontend/snippets/components/snippet_header_spec.js

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

395 lines
12 KiB
JavaScript
Raw Normal View History

2023-05-27 22:25:52 +05:30
import { GlModal, GlButton, GlDropdown } from '@gitlab/ui';
2020-10-24 23:57:45 +05:30
import { mount } from '@vue/test-utils';
2023-05-27 22:25:52 +05:30
import VueApollo from 'vue-apollo';
2021-11-18 22:05:49 +05:30
import MockAdapter from 'axios-mock-adapter';
2023-05-27 22:25:52 +05:30
import Vue, { nextTick } from 'vue';
import createMockApollo from 'helpers/mock_apollo_helper';
2021-10-27 15:23:28 +05:30
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
2020-10-24 23:57:45 +05:30
import waitForPromises from 'helpers/wait_for_promises';
2021-03-11 19:13:27 +05:30
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
2020-11-24 15:15:51 +05:30
import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
2021-11-18 22:05:49 +05:30
import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue';
2022-06-21 17:19:12 +05:30
import DeleteSnippetMutation from '~/snippets/mutations/delete_snippet.mutation.graphql';
2021-11-18 22:05:49 +05:30
import axios from '~/lib/utils/axios_utils';
2023-05-27 22:25:52 +05:30
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';
2021-11-18 22:05:49 +05:30
2023-05-27 22:25:52 +05:30
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);
2020-01-01 13:55:28 +05:30
describe('Snippet header component', () => {
let wrapper;
2020-10-24 23:57:45 +05:30
let snippet;
2021-11-18 22:05:49 +05:30
let mock;
2023-05-27 22:25:52 +05:30
let mockApollo;
2020-10-24 23:57:45 +05:30
2021-10-27 15:23:28 +05:30
const reportAbusePath = '/-/snippets/42/mark_as_spam';
2021-11-18 22:05:49 +05:30
const canReportSpam = true;
2020-01-01 13:55:28 +05:30
2021-03-08 18:12:59 +05:30
const GlEmoji = { template: '<img/>' };
2020-01-01 13:55:28 +05:30
function createComponent({
permissions = {},
2020-05-24 23:13:21 +05:30
snippetProps = {},
2021-10-27 15:23:28 +05:30
provide = {},
2023-05-27 22:25:52 +05:30
canCreateProjectSnippetMock = jest.fn().mockResolvedValue(getCanCreateProjectSnippetMock()),
canCreatePersonalSnippetMock = jest.fn().mockResolvedValue(getCanCreatePersonalSnippetMock()),
deleteSnippetMock = MUTATION_TYPES.RESOLVE,
2020-01-01 13:55:28 +05:30
} = {}) {
2020-05-24 23:13:21 +05:30
const defaultProps = Object.assign(snippet, snippetProps);
2020-01-01 13:55:28 +05:30
if (permissions) {
2020-05-24 23:13:21 +05:30
Object.assign(defaultProps.userPermissions, {
2020-01-01 13:55:28 +05:30
...permissions,
});
}
2023-05-27 22:25:52 +05:30
mockApollo = createMockApollo([
[CanCreateProjectSnippet, canCreateProjectSnippetMock],
[CanCreatePersonalSnippet, canCreatePersonalSnippetMock],
[DeleteSnippetMutation, deleteSnippetMock],
]);
2020-01-01 13:55:28 +05:30
2020-10-24 23:57:45 +05:30
wrapper = mount(SnippetHeader, {
2021-10-27 15:23:28 +05:30
provide: {
reportAbusePath,
2021-11-18 22:05:49 +05:30
canReportSpam,
2021-10-27 15:23:28 +05:30
...provide,
},
2020-01-01 13:55:28 +05:30
propsData: {
2020-05-24 23:13:21 +05:30
snippet: {
...defaultProps,
},
2020-01-01 13:55:28 +05:30
},
stubs: {
2021-03-08 18:12:59 +05:30
GlEmoji,
2020-01-01 13:55:28 +05:30
},
2023-05-27 22:25:52 +05:30
apolloProvider: mockApollo,
2020-01-01 13:55:28 +05:30
});
}
2021-10-27 15:23:28 +05:30
const findAuthorEmoji = () => wrapper.findComponent(GlEmoji);
2021-03-08 18:12:59 +05:30
const findAuthoredMessage = () => wrapper.find('[data-testid="authored-message"]').text();
2021-10-27 15:23:28 +05:30
const findButtons = () => wrapper.findAllComponents(GlButton);
const findButtonsAsModel = () =>
findButtons().wrappers.map((x) => ({
text: x.text(),
href: x.attributes('href'),
category: x.props('category'),
variant: x.props('variant'),
disabled: x.props('disabled'),
}));
const findResponsiveDropdown = () => wrapper.findComponent(GlDropdown);
// We can't search by component here since we are full mounting and the attributes are applied to a child of the GlDropdownItem
const findResponsiveDropdownItems = () => findResponsiveDropdown().findAll('[role="menuitem"]');
const findResponsiveDropdownItemsAsModel = () =>
findResponsiveDropdownItems().wrappers.map((x) => ({
disabled: x.attributes('disabled'),
href: x.attributes('href'),
title: x.attributes('title'),
text: x.text(),
}));
2023-05-27 22:25:52 +05:30
const findDeleteModal = () => wrapper.findComponent(GlModal);
2021-03-08 18:12:59 +05:30
2020-10-24 23:57:45 +05:30
beforeEach(() => {
2020-11-24 15:15:51 +05:30
gon.relative_url_root = '/foo/';
2020-10-24 23:57:45 +05:30
snippet = {
id: 'gid://gitlab/PersonalSnippet/50',
title: 'The property of Thor',
visibilityLevel: 'private',
webUrl: 'http://personal.dev.null/42',
userPermissions: {
adminSnippet: true,
updateSnippet: true,
reportSnippet: false,
},
project: null,
author: {
name: 'Thor Odinson',
2021-03-08 18:12:59 +05:30
status: null,
2020-10-24 23:57:45 +05:30
},
blobs: [Blob],
2020-11-24 15:15:51 +05:30
createdAt: new Date(differenceInMilliseconds(32 * 24 * 3600 * 1000)).toISOString(),
2020-10-24 23:57:45 +05:30
};
2021-11-18 22:05:49 +05:30
mock = new MockAdapter(axios);
2020-10-24 23:57:45 +05:30
});
2020-01-01 13:55:28 +05:30
afterEach(() => {
2023-05-27 22:25:52 +05:30
mockApollo = null;
2021-11-18 22:05:49 +05:30
mock.restore();
2020-01-01 13:55:28 +05:30
});
it('renders itself', () => {
createComponent();
expect(wrapper.find('.detail-page-header').exists()).toBe(true);
});
2020-10-24 23:57:45 +05:30
it('renders a message showing snippet creation date and author', () => {
createComponent();
2021-03-08 18:12:59 +05:30
const text = findAuthoredMessage();
2020-10-24 23:57:45 +05:30
expect(text).toContain('Authored 1 month ago by');
expect(text).toContain('Thor Odinson');
});
2021-03-08 18:12:59 +05:30
describe('author status', () => {
it('is rendered when it is set', () => {
snippet.author.status = {
message: 'At work',
emoji: 'hammer',
};
createComponent();
expect(findAuthorEmoji().attributes('title')).toBe(snippet.author.status.message);
expect(findAuthorEmoji().attributes('data-name')).toBe(snippet.author.status.emoji);
});
it('is not rendered when the user has no status', () => {
createComponent();
expect(findAuthorEmoji().exists()).toBe(false);
});
});
2020-10-24 23:57:45 +05:30
it('renders a message showing only snippet creation date if author is null', () => {
snippet.author = null;
createComponent();
2021-03-08 18:12:59 +05:30
const text = findAuthoredMessage();
2020-10-24 23:57:45 +05:30
expect(text).toBe('Authored 1 month ago');
});
2021-10-27 15:23:28 +05:30
it('renders a action buttons', () => {
createComponent();
expect(findButtonsAsModel()).toEqual([
{
category: 'primary',
disabled: false,
href: `${snippet.webUrl}/edit`,
text: 'Edit',
variant: 'default',
2020-01-01 13:55:28 +05:30
},
2021-10-27 15:23:28 +05:30
{
category: 'secondary',
disabled: false,
text: 'Delete',
variant: 'danger',
},
{
category: 'primary',
disabled: false,
text: 'Submit as spam',
variant: 'default',
},
]);
});
2020-01-01 13:55:28 +05:30
2021-10-27 15:23:28 +05:30
it('renders responsive dropdown for action buttons', () => {
createComponent();
expect(findResponsiveDropdownItemsAsModel()).toEqual([
{
href: `${snippet.webUrl}/edit`,
text: 'Edit',
2020-01-01 13:55:28 +05:30
},
2021-10-27 15:23:28 +05:30
{
text: 'Delete',
},
{
text: 'Submit as spam',
title: 'Submit as spam',
},
]);
});
2020-01-01 13:55:28 +05:30
2021-10-27 15:23:28 +05:30
it.each`
permissions | buttons
${{ adminSnippet: false, updateSnippet: false }} | ${['Submit as spam']}
${{ adminSnippet: true, updateSnippet: false }} | ${['Delete', 'Submit as spam']}
${{ adminSnippet: false, updateSnippet: true }} | ${['Edit', 'Submit as spam']}
`('with permissions ($permissions), renders buttons ($buttons)', ({ permissions, buttons }) => {
2020-01-01 13:55:28 +05:30
createComponent({
permissions: {
2021-10-27 15:23:28 +05:30
...permissions,
2020-01-01 13:55:28 +05:30
},
});
2021-10-27 15:23:28 +05:30
expect(findButtonsAsModel().map((x) => x.text)).toEqual(buttons);
});
it('with canCreateSnippet permission, renders create button', async () => {
2023-05-27 22:25:52 +05:30
createComponent({
canCreateProjectSnippetMock: jest
.fn()
.mockResolvedValue(getCanCreateProjectSnippetMock(true)),
canCreatePersonalSnippetMock: jest
.fn()
.mockResolvedValue(getCanCreatePersonalSnippetMock(true)),
});
2021-10-27 15:23:28 +05:30
2023-05-27 22:25:52 +05:30
await waitForPromises();
2021-10-27 15:23:28 +05:30
expect(findButtonsAsModel()).toEqual(
expect.arrayContaining([
{
category: 'secondary',
disabled: false,
href: `/foo/-/snippets/new`,
text: 'New snippet',
2022-01-26 12:08:38 +05:30
variant: 'confirm',
2021-10-27 15:23:28 +05:30
},
]),
);
});
2021-11-18 22:05:49 +05:30
describe('submit snippet as spam', () => {
2023-06-20 00:43:36 +05:30
beforeEach(() => {
2021-11-18 22:05:49 +05:30
createComponent();
});
it.each`
2022-11-25 23:54:43 +05:30
request | variant | text
${200} | ${VARIANT_SUCCESS} | ${i18n.snippetSpamSuccess}
${500} | ${VARIANT_DANGER} | ${i18n.snippetSpamFailure}
2021-11-18 22:05:49 +05:30
`(
2023-05-27 22:25:52 +05:30
'renders a "$variant" alert message with "$text" message for a request with a "$request" response',
2021-11-18 22:05:49 +05:30
async ({ request, variant, text }) => {
const submitAsSpamBtn = findButtons().at(2);
mock.onPost(reportAbusePath).reply(request);
submitAsSpamBtn.trigger('click');
await waitForPromises();
2022-11-25 23:54:43 +05:30
expect(createAlert).toHaveBeenLastCalledWith({
2021-11-18 22:05:49 +05:30
message: expect.stringContaining(text),
2022-11-25 23:54:43 +05:30
variant,
2021-11-18 22:05:49 +05:30
});
},
);
});
2021-10-27 15:23:28 +05:30
describe('with guest user', () => {
beforeEach(() => {
createComponent({
permissions: {
adminSnippet: false,
updateSnippet: false,
},
provide: {
reportAbusePath: null,
2021-11-18 22:05:49 +05:30
canReportSpam: false,
2021-10-27 15:23:28 +05:30
},
});
2020-01-01 13:55:28 +05:30
});
2021-10-27 15:23:28 +05:30
it('does not show any action buttons', () => {
expect(findButtons()).toHaveLength(0);
2020-01-01 13:55:28 +05:30
});
2021-10-27 15:23:28 +05:30
it('does not show responsive action dropdown', () => {
expect(findResponsiveDropdown().exists()).toBe(false);
2020-01-01 13:55:28 +05:30
});
});
it('renders modal for deletion of a snippet', () => {
createComponent();
2022-11-25 23:54:43 +05:30
expect(wrapper.findComponent(GlModal).exists()).toBe(true);
2020-01-01 13:55:28 +05:30
});
2020-07-28 23:09:34 +05:30
it.each`
blobs | isDisabled | condition
${[Blob]} | ${false} | ${'no binary'}
${[Blob, BinaryBlob]} | ${true} | ${'several blobs. incl. a binary'}
${[BinaryBlob]} | ${true} | ${'binary'}
`('renders Edit button when snippet contains $condition file', ({ blobs, isDisabled }) => {
2020-05-24 23:13:21 +05:30
createComponent({
snippetProps: {
2020-07-28 23:09:34 +05:30
blobs,
2020-05-24 23:13:21 +05:30
},
});
2020-07-28 23:09:34 +05:30
expect(wrapper.find('[href*="edit"]').props('disabled')).toBe(isDisabled);
2020-05-24 23:13:21 +05:30
});
2020-01-01 13:55:28 +05:30
describe('Delete mutation', () => {
2023-05-27 22:25:52 +05:30
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 () => {
2020-01-01 13:55:28 +05:30
createComponent();
2023-05-27 22:25:52 +05:30
await deleteSnippet();
expect(MUTATION_TYPES.RESOLVE).toHaveBeenCalledWith({
id: snippet.id,
});
2020-01-01 13:55:28 +05:30
});
2020-10-24 23:57:45 +05:30
it('sets error message if mutation fails', async () => {
2023-05-27 22:25:52 +05:30
createComponent({ deleteSnippetMock: MUTATION_TYPES.REJECT });
2020-01-01 13:55:28 +05:30
expect(Boolean(wrapper.vm.errorMessage)).toBe(false);
2023-05-27 22:25:52 +05:30
await deleteSnippet();
2020-10-24 23:57:45 +05:30
2023-05-27 22:25:52 +05:30
expect(document.querySelector('[data-testid="delete-alert"').textContent.trim()).toBe(
ERROR_MSG,
);
2020-01-01 13:55:28 +05:30
});
2020-05-24 23:13:21 +05:30
describe('in case of successful mutation, closes modal and redirects to correct listing', () => {
2021-10-27 15:23:28 +05:30
useMockLocationHelper();
2022-04-04 11:22:00 +05:30
const createDeleteSnippet = async (snippetProps = {}) => {
2020-05-24 23:13:21 +05:30
createComponent({
snippetProps,
});
2020-01-01 13:55:28 +05:30
2023-05-27 22:25:52 +05:30
await deleteSnippet();
2020-05-24 23:13:21 +05:30
};
2022-04-04 11:22:00 +05:30
it('redirects to dashboard/snippets for personal snippet', async () => {
await createDeleteSnippet();
2023-05-27 22:25:52 +05:30
// Check that the modal is hidden after deleting the snippet
expect(findDeleteModal().props().visible).toBe(false);
2022-04-04 11:22:00 +05:30
expect(window.location.pathname).toBe(`${gon.relative_url_root}dashboard/snippets`);
2020-05-24 23:13:21 +05:30
});
2022-04-04 11:22:00 +05:30
it('redirects to project snippets for project snippet', async () => {
2020-05-24 23:13:21 +05:30
const fullPath = 'foo/bar';
2022-04-04 11:22:00 +05:30
await createDeleteSnippet({
2020-05-24 23:13:21 +05:30
project: {
fullPath,
},
});
2023-05-27 22:25:52 +05:30
// Check that the modal is hidden after deleting the snippet
expect(findDeleteModal().props().visible).toBe(false);
2022-04-04 11:22:00 +05:30
expect(window.location.pathname).toBe(`${fullPath}/-/snippets`);
2020-01-01 13:55:28 +05:30
});
});
});
});