Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-10 15:09:12 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-10 15:09:12 +0300
commit0e0df204c1a0d859ccbbe1be83a5e09a53381f17 (patch)
treee7bf6fed5fa2b74caf31957c468b0cbc303f4c45 /spec/frontend
parenta2344dbf1942dc3919c55b0684d2566368e03852 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js34
-rw-r--r--spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js99
-rw-r--r--spec/frontend/content_editor/components/toolbar_attachment_button_spec.js13
-rw-r--r--spec/frontend/content_editor/extensions/attachment_spec.js546
-rw-r--r--spec/frontend/content_editor/extensions/link_spec.js5
-rw-r--r--spec/frontend/content_editor/services/markdown_serializer_spec.js21
-rw-r--r--spec/frontend/content_editor/test_utils.js25
-rw-r--r--spec/frontend/diffs/utils/merge_request_spec.js38
-rw-r--r--spec/frontend/lib/utils/tappable_promise_spec.js63
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js17
-rw-r--r--spec/frontend/monitoring/mock_data.js26
-rw-r--r--spec/frontend/snippets/components/__snapshots__/snippet_description_edit_spec.js.snap2
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js6
13 files changed, 750 insertions, 145 deletions
diff --git a/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
index 2a8a1b00692..c79df9c9ed8 100644
--- a/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/link_bubble_menu_spec.js
@@ -7,7 +7,7 @@ import eventHubFactory from '~/helpers/event_hub_factory';
import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
import { stubComponent } from 'helpers/stub_component';
import Link from '~/content_editor/extensions/link';
-import { createTestEditor } from '../../test_utils';
+import { createTestEditor, emitEditorEvent, createTransactionWithMeta } from '../../test_utils';
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
@@ -64,7 +64,7 @@ describe('content_editor/components/bubble_menus/link_bubble_menu', () => {
tiptapEditor
.chain()
- .insertContent(
+ .setContent(
'Download <a href="/path/to/project/-/wikis/uploads/my_file.pdf" data-canonical-src="uploads/my_file.pdf">PDF File</a>',
)
.setTextSelection(14) // put cursor in the middle of the link
@@ -90,6 +90,36 @@ describe('content_editor/components/bubble_menus/link_bubble_menu', () => {
expect(findLink().text()).toBe('uploads/my_file.pdf');
});
+ it('shows a loading percentage for a file being uploaded', async () => {
+ const setUploadProgress = async (progress) => {
+ const transaction = createTransactionWithMeta('uploadProgress', {
+ filename: 'my_file.pdf',
+ progress,
+ });
+ await emitEditorEvent({ event: 'transaction', tiptapEditor, params: { transaction } });
+ };
+
+ tiptapEditor
+ .chain()
+ .extendMarkRange('link')
+ .updateAttributes('link', { uploading: 'my_file.pdf' })
+ .run();
+
+ await buildWrapperAndDisplayMenu();
+
+ expect(findLink().exists()).toBe(false);
+ expect(wrapper.text()).toContain('Uploading: 0%');
+
+ await setUploadProgress(0.4);
+ expect(wrapper.text()).toContain('Uploading: 40%');
+
+ await setUploadProgress(0.7);
+ expect(wrapper.text()).toContain('Uploading: 70%');
+
+ await setUploadProgress(1);
+ expect(wrapper.text()).toContain('Uploading: 100%');
+ });
+
it('updates the bubble menu state when @selectionUpdate event is triggered', async () => {
const linkUrl = 'https://gitlab.com';
diff --git a/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
index e02b36fb8e9..89beb76a6f2 100644
--- a/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
+++ b/spec/frontend/content_editor/components/bubble_menus/media_bubble_menu_spec.js
@@ -1,3 +1,4 @@
+import { nextTick } from 'vue';
import { GlLink, GlForm } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import BubbleMenu from '~/content_editor/components/bubble_menus/bubble_menu.vue';
@@ -8,7 +9,12 @@ import Audio from '~/content_editor/extensions/audio';
import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
import Image from '~/content_editor/extensions/image';
import Video from '~/content_editor/extensions/video';
-import { createTestEditor, emitEditorEvent, mockChainedCommands } from '../../test_utils';
+import {
+ createTestEditor,
+ emitEditorEvent,
+ mockChainedCommands,
+ createTransactionWithMeta,
+} from '../../test_utils';
import {
PROJECT_WIKI_ATTACHMENT_IMAGE_HTML,
PROJECT_WIKI_ATTACHMENT_AUDIO_HTML,
@@ -17,7 +23,7 @@ import {
} from '../../test_constants';
const TIPTAP_AUDIO_HTML = `<p>
- <span class="media-container audio-container"><audio src="https://gitlab.com/favicon.png" controls="true" data-setup="{}" data-title="gitlab favicon"></audio><a href="https://gitlab.com/favicon.png">gitlab favicon</a></span>
+ <span class="media-container audio-container"><audio src="https://gitlab.com/favicon.png" controls="true" data-setup="{}" data-title="gitlab favicon"></audio><a href="https://gitlab.com/favicon.png" class="with-attachment-icon">gitlab favicon</a></span>
</p>`;
const TIPTAP_DIAGRAM_HTML = `<p>
@@ -29,24 +35,23 @@ const TIPTAP_IMAGE_HTML = `<p>
</p>`;
const TIPTAP_VIDEO_HTML = `<p>
- <span class="media-container video-container"><video src="https://gitlab.com/favicon.png" controls="true" data-setup="{}" data-title="gitlab favicon"></video><a href="https://gitlab.com/favicon.png">gitlab favicon</a></span>
+ <span class="media-container video-container"><video src="https://gitlab.com/favicon.png" controls="true" data-setup="{}" data-title="gitlab favicon"></video><a href="https://gitlab.com/favicon.png" class="with-attachment-icon">gitlab favicon</a></span>
</p>`;
const createFakeEvent = () => ({ preventDefault: jest.fn(), stopPropagation: jest.fn() });
describe.each`
- mediaType | mediaHTML | filePath | mediaOutputHTML
- ${'image'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${'test-file.png'} | ${TIPTAP_IMAGE_HTML}
- ${'drawio_diagram'} | ${PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML} | ${'test-file.drawio.svg'} | ${TIPTAP_DIAGRAM_HTML}
- ${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${'test-file.mp3'} | ${TIPTAP_AUDIO_HTML}
- ${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${'test-file.mp4'} | ${TIPTAP_VIDEO_HTML}
+ mediaType | mediaHTML | filePath | mediaOutputHTML
+ ${'image'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${'test-file.png'} | ${TIPTAP_IMAGE_HTML}
+ ${'drawioDiagram'} | ${PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML} | ${'test-file.drawio.svg'} | ${TIPTAP_DIAGRAM_HTML}
+ ${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${'test-file.mp3'} | ${TIPTAP_AUDIO_HTML}
+ ${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${'test-file.mp4'} | ${TIPTAP_VIDEO_HTML}
`(
'content_editor/components/bubble_menus/media_bubble_menu ($mediaType)',
({ mediaType, mediaHTML, filePath, mediaOutputHTML }) => {
let wrapper;
let tiptapEditor;
let contentEditor;
- let bubbleMenu;
let eventHub;
const buildEditor = () => {
@@ -68,6 +73,24 @@ describe.each`
});
};
+ const findBubbleMenu = () => wrapper.findComponent(BubbleMenu);
+
+ const showMenu = async () => {
+ findBubbleMenu().vm.$emit('show');
+ await emitEditorEvent({
+ event: 'transaction',
+ tiptapEditor,
+ params: { transaction: createTransactionWithMeta() },
+ });
+ await nextTick();
+ };
+
+ const buildWrapperAndDisplayMenu = () => {
+ buildWrapper();
+
+ return showMenu();
+ };
+
const selectFile = async (file) => {
const input = wrapper.findComponent({ ref: 'fileSelector' });
@@ -83,9 +106,8 @@ describe.each`
expect(wrapper.findByTestId('delete-media').exists()).toBe(exist);
};
- beforeEach(async () => {
+ beforeEach(() => {
buildEditor();
- buildWrapper();
tiptapEditor
.chain()
@@ -94,17 +116,17 @@ describe.each`
.run();
contentEditor.resolveUrl.mockResolvedValue(`/group1/project1/-/wikis/${filePath}`);
+ });
- await emitEditorEvent({ event: 'transaction', tiptapEditor });
+ it('renders bubble menu component', async () => {
+ await buildWrapperAndDisplayMenu();
- bubbleMenu = wrapper.findComponent(BubbleMenu);
+ expect(findBubbleMenu().classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
});
- it('renders bubble menu component', () => {
- expect(bubbleMenu.classes()).toEqual(['gl-shadow', 'gl-rounded-base', 'gl-bg-white']);
- });
+ it('shows a clickable link to the image', async () => {
+ await buildWrapperAndDisplayMenu();
- it('shows a clickable link to the image', () => {
const link = wrapper.findComponent(GlLink);
expect(link.attributes()).toEqual(
expect.objectContaining({
@@ -117,8 +139,41 @@ describe.each`
expect(link.text()).toBe(filePath);
});
+ it('shows a loading percentage for a file being uploaded', async () => {
+ jest.spyOn(tiptapEditor, 'isActive').mockImplementation((name) => name === mediaType);
+
+ await buildWrapperAndDisplayMenu();
+
+ const setUploadProgress = async (progress) => {
+ const transaction = createTransactionWithMeta('uploadProgress', {
+ filename: filePath,
+ progress,
+ });
+ await emitEditorEvent({ event: 'transaction', tiptapEditor, params: { transaction } });
+ };
+
+ tiptapEditor.chain().selectAll().updateAttributes(mediaType, { uploading: filePath }).run();
+
+ await emitEditorEvent({ event: 'selectionUpdate', tiptapEditor });
+
+ expect(wrapper.findComponent(GlLink).exists()).toBe(false);
+ expect(wrapper.text()).toContain('Uploading: 0%');
+
+ await setUploadProgress(0.4);
+
+ expect(wrapper.text()).toContain('Uploading: 40%');
+
+ await setUploadProgress(0.7);
+ expect(wrapper.text()).toContain('Uploading: 70%');
+
+ await setUploadProgress(1);
+ expect(wrapper.text()).toContain('Uploading: 100%');
+ });
+
describe('when BubbleMenu emits hidden event', () => {
it('resets media bubble menu state', async () => {
+ await buildWrapperAndDisplayMenu();
+
// Switch to edit mode to access component state in form fields
await wrapper.findByTestId('edit-media').vm.$emit('click');
@@ -137,6 +192,8 @@ describe.each`
describe('copy button', () => {
it(`copies the canonical link to the ${mediaType} to clipboard`, async () => {
+ await buildWrapperAndDisplayMenu();
+
jest.spyOn(navigator.clipboard, 'writeText');
await wrapper.findByTestId('copy-media-src').vm.$emit('click');
@@ -147,6 +204,8 @@ describe.each`
describe(`remove ${mediaType} button`, () => {
it(`removes the ${mediaType}`, async () => {
+ await buildWrapperAndDisplayMenu();
+
await wrapper.findByTestId('delete-media').vm.$emit('click');
expect(tiptapEditor.getHTML()).toBe('<p>\n \n</p>');
@@ -154,7 +213,9 @@ describe.each`
});
describe(`replace ${mediaType} button`, () => {
- if (mediaType !== 'drawio_diagram') {
+ beforeEach(buildWrapperAndDisplayMenu);
+
+ if (mediaType !== 'drawioDiagram') {
it('uploads and replaces the selected image when file input changes', async () => {
const commands = mockChainedCommands(tiptapEditor, [
'focus',
@@ -195,6 +256,8 @@ describe.each`
let mediaAltInput;
beforeEach(async () => {
+ await buildWrapperAndDisplayMenu();
+
await wrapper.findByTestId('edit-media').vm.$emit('click');
mediaSrcInput = wrapper.findByTestId('media-src');
diff --git a/spec/frontend/content_editor/components/toolbar_attachment_button_spec.js b/spec/frontend/content_editor/components/toolbar_attachment_button_spec.js
index 06ea863dbfa..c6793d5b01b 100644
--- a/spec/frontend/content_editor/components/toolbar_attachment_button_spec.js
+++ b/spec/frontend/content_editor/components/toolbar_attachment_button_spec.js
@@ -16,11 +16,11 @@ describe('content_editor/components/toolbar_attachment_button', () => {
});
};
- const selectFile = async (file) => {
+ const selectFiles = async (...files) => {
const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
- Object.defineProperty(input.element, 'files', { value: [file], writable: true });
+ Object.defineProperty(input.element, 'files', { value: files, writable: true });
await input.trigger('change');
};
@@ -28,6 +28,7 @@ describe('content_editor/components/toolbar_attachment_button', () => {
editor = createTestEditor({
extensions: [
Link,
+ Image,
Attachment.configure({
renderMarkdown: jest.fn(),
uploadsPath: '/uploads/',
@@ -44,12 +45,14 @@ describe('content_editor/components/toolbar_attachment_button', () => {
it('uploads the selected attachment when file input changes', async () => {
const commands = mockChainedCommands(editor, ['focus', 'uploadAttachment', 'run']);
- const file = new File(['foo'], 'foo.png', { type: 'image/png' });
+ const file1 = new File(['foo'], 'foo.png', { type: 'image/png' });
+ const file2 = new File(['bar'], 'bar.png', { type: 'image/png' });
- await selectFile(file);
+ await selectFiles(file1, file2);
expect(commands.focus).toHaveBeenCalled();
- expect(commands.uploadAttachment).toHaveBeenCalledWith({ file });
+ expect(commands.uploadAttachment).toHaveBeenCalledWith({ file: file1 });
+ expect(commands.uploadAttachment).toHaveBeenCalledWith({ file: file2 });
expect(commands.run).toHaveBeenCalled();
expect(wrapper.emitted().execute[0]).toEqual([{ contentType: 'link', value: 'upload' }]);
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js
index 3c699b05b0f..f037ac520fe 100644
--- a/spec/frontend/content_editor/extensions/attachment_spec.js
+++ b/spec/frontend/content_editor/extensions/attachment_spec.js
@@ -1,17 +1,15 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
-import waitForPromises from 'helpers/wait_for_promises';
import Attachment from '~/content_editor/extensions/attachment';
import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
import Image from '~/content_editor/extensions/image';
import Audio from '~/content_editor/extensions/audio';
import Video from '~/content_editor/extensions/video';
import Link from '~/content_editor/extensions/link';
-import Loading from '~/content_editor/extensions/loading';
import { VARIANT_DANGER } from '~/alert';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import eventHubFactory from '~/helpers/event_hub_factory';
-import { createTestEditor, createDocBuilder } from '../test_utils';
+import { createTestEditor, createDocBuilder, expectDocumentAfterTransaction } from '../test_utils';
import {
PROJECT_WIKI_ATTACHMENT_IMAGE_HTML,
PROJECT_WIKI_ATTACHMENT_IMAGE_SVG_HTML,
@@ -29,7 +27,6 @@ describe('content_editor/extensions/attachment', () => {
let audio;
let drawioDiagram;
let video;
- let loading;
let link;
let renderMarkdown;
let mock;
@@ -40,35 +37,35 @@ describe('content_editor/extensions/attachment', () => {
const imageFileSvg = new File(['foo'], 'test-file.svg', { type: 'image/svg+xml' });
const audioFile = new File(['foo'], 'test-file.mp3', { type: 'audio/mpeg' });
const videoFile = new File(['foo'], 'test-file.mp4', { type: 'video/mp4' });
+ const videoFile1 = new File(['foo'], 'test-file1.mp4', { type: 'video/mp4' });
const drawioDiagramFile = new File(['foo'], 'test-file.drawio.svg', { type: 'image/svg+xml' });
const attachmentFile = new File(['foo'], 'test-file.zip', { type: 'application/zip' });
-
- const expectDocumentAfterTransaction = ({ number, expectedDoc, action }) => {
- return new Promise((resolve) => {
- let counter = 1;
- const handleTransaction = async () => {
- if (counter === number) {
- expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON());
- tiptapEditor.off('update', handleTransaction);
- await waitForPromises();
- resolve();
- }
-
- counter += 1;
- };
-
- tiptapEditor.on('update', handleTransaction);
- action();
- });
+ const attachmentFile1 = new File(['foo'], 'test-file1.zip', { type: 'application/zip' });
+ const attachmentFile2 = new File(['foo'], 'test-file2.zip', { type: 'application/zip' });
+
+ const markdownApiResult = {
+ 'test-file.png': PROJECT_WIKI_ATTACHMENT_IMAGE_HTML,
+ 'test-file.svg': PROJECT_WIKI_ATTACHMENT_IMAGE_SVG_HTML,
+ 'test-file.mp3': PROJECT_WIKI_ATTACHMENT_AUDIO_HTML,
+ 'test-file.mp4': PROJECT_WIKI_ATTACHMENT_VIDEO_HTML,
+ 'test-file1.mp4': PROJECT_WIKI_ATTACHMENT_VIDEO_HTML.replace(/test-file/g, 'test-file1'),
+ 'test-file.zip': PROJECT_WIKI_ATTACHMENT_LINK_HTML,
+ 'test-file1.zip': PROJECT_WIKI_ATTACHMENT_LINK_HTML.replace(/test-file/g, 'test-file1'),
+ 'test-file2.zip': PROJECT_WIKI_ATTACHMENT_LINK_HTML.replace(/test-file/g, 'test-file2'),
+ 'test-file.drawio.svg': PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML,
};
+ const [, group, project] = markdownApiResult[attachmentFile.name].match(
+ /\/(group[0-9]+)\/(project[0-9]+)\//,
+ );
+ const blobUrl = 'blob:https://gitlab.com/048c7ac1-98de-4a37-ab1b-0206d0ea7e1b';
+
beforeEach(() => {
renderMarkdown = jest.fn();
eventHub = eventHubFactory();
tiptapEditor = createTestEditor({
extensions: [
- Loading,
Link,
Image,
Audio,
@@ -79,11 +76,10 @@ describe('content_editor/extensions/attachment', () => {
});
({
- builders: { doc, p, image, audio, video, loading, link, drawioDiagram },
+ builders: { doc, p, image, audio, video, link, drawioDiagram },
} = createDocBuilder({
tiptapEditor,
names: {
- loading: { markType: Loading.name },
image: { nodeType: Image.name },
link: { nodeType: Link.name },
audio: { nodeType: Audio.name },
@@ -105,6 +101,14 @@ describe('content_editor/extensions/attachment', () => {
${'paste'} | ${'handlePaste'} | ${{ clipboardData: { getData: jest.fn(), files: [] } }} | ${undefined}
${'drop'} | ${'handleDrop'} | ${{ dataTransfer: { getData: jest.fn(), files: [attachmentFile] } }} | ${true}
`('handles $eventType properly', ({ eventType, propName, eventData, output }) => {
+ mock.onPost().reply(HTTP_STATUS_OK, {
+ link: {
+ markdown: `![test-file](test-file.png)`,
+ },
+ });
+
+ renderMarkdown.mockResolvedValue(PROJECT_WIKI_ATTACHMENT_IMAGE_HTML);
+
const event = Object.assign(new Event(eventType), eventData);
const handled = tiptapEditor.view.someProp(propName, (eventHandler) => {
return eventHandler(tiptapEditor.view, event);
@@ -121,15 +125,13 @@ describe('content_editor/extensions/attachment', () => {
});
describe.each`
- nodeType | mimeType | html | file | mediaType
- ${'image (png)'} | ${'image/png'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${imageFile} | ${(attrs) => image(attrs)}
- ${'image (svg)'} | ${'image/svg+xml'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_SVG_HTML} | ${imageFileSvg} | ${(attrs) => image(attrs)}
- ${'audio'} | ${'audio/mpeg'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${audioFile} | ${(attrs) => audio(attrs)}
- ${'video'} | ${'video/mp4'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${videoFile} | ${(attrs) => video(attrs)}
- ${'drawioDiagram'} | ${'image/svg+xml'} | ${PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML} | ${drawioDiagramFile} | ${(attrs) => drawioDiagram(attrs)}
- `('when the file has $nodeType mime type', ({ mimeType, html, file, mediaType }) => {
- const base64EncodedFile = `data:${mimeType};base64,Zm9v`;
-
+ nodeType | html | file | mediaType
+ ${'image (png)'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_HTML} | ${imageFile} | ${(attrs) => image(attrs)}
+ ${'image (svg)'} | ${PROJECT_WIKI_ATTACHMENT_IMAGE_SVG_HTML} | ${imageFileSvg} | ${(attrs) => image(attrs)}
+ ${'audio'} | ${PROJECT_WIKI_ATTACHMENT_AUDIO_HTML} | ${audioFile} | ${(attrs) => audio(attrs)}
+ ${'video'} | ${PROJECT_WIKI_ATTACHMENT_VIDEO_HTML} | ${videoFile} | ${(attrs) => video(attrs)}
+ ${'drawioDiagram'} | ${PROJECT_WIKI_ATTACHMENT_DRAWIO_DIAGRAM_HTML} | ${drawioDiagramFile} | ${(attrs) => drawioDiagram(attrs)}
+ `('when the file is $nodeType', ({ html, file, mediaType }) => {
beforeEach(() => {
renderMarkdown.mockResolvedValue(html);
});
@@ -145,10 +147,13 @@ describe('content_editor/extensions/attachment', () => {
mock.onPost().reply(HTTP_STATUS_OK, successResponse);
});
- it('inserts a media content with src set to the encoded content and uploading true', async () => {
- const expectedDoc = doc(p(mediaType({ uploading: true, src: base64EncodedFile })));
+ it('inserts a media content with src set to the encoded content and uploading=file_name', async () => {
+ const expectedDoc = doc(
+ p(mediaType({ uploading: file.name, src: blobUrl, alt: file.name })),
+ );
await expectDocumentAfterTransaction({
+ tiptapEditor,
number: 1,
expectedDoc,
action: () => tiptapEditor.commands.uploadAttachment({ file }),
@@ -160,7 +165,7 @@ describe('content_editor/extensions/attachment', () => {
p(
mediaType({
canonicalSrc: file.name,
- src: base64EncodedFile,
+ src: blobUrl,
alt: expect.stringContaining('test-file'),
uploading: false,
}),
@@ -168,6 +173,7 @@ describe('content_editor/extensions/attachment', () => {
);
await expectDocumentAfterTransaction({
+ tiptapEditor,
number: 2,
expectedDoc,
action: () => tiptapEditor.commands.uploadAttachment({ file }),
@@ -175,6 +181,25 @@ describe('content_editor/extensions/attachment', () => {
});
});
+ describe('when uploading a large file', () => {
+ beforeEach(() => {
+ // Set max file size to 1 byte, our file is 3 bytes
+ gon.max_file_size = 1 / 1024 / 1024;
+ });
+
+ it('emits an alert event that includes an error message', () => {
+ tiptapEditor.commands.uploadAttachment({ file });
+
+ return new Promise((resolve) => {
+ eventHub.$on('alert', ({ message, variant }) => {
+ expect(variant).toBe(VARIANT_DANGER);
+ expect(message).toContain('File is too big');
+ resolve();
+ });
+ });
+ });
+ });
+
describe('when uploading request fails', () => {
beforeEach(() => {
mock.onPost().reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
@@ -184,6 +209,7 @@ describe('content_editor/extensions/attachment', () => {
const expectedDoc = doc(p(''));
await expectDocumentAfterTransaction({
+ tiptapEditor,
number: 2,
expectedDoc,
action: () => tiptapEditor.commands.uploadAttachment({ file }),
@@ -205,10 +231,8 @@ describe('content_editor/extensions/attachment', () => {
});
describe('when the file has a zip (or any other attachment) mime type', () => {
- const markdownApiResult = PROJECT_WIKI_ATTACHMENT_LINK_HTML;
-
beforeEach(() => {
- renderMarkdown.mockResolvedValue(markdownApiResult);
+ renderMarkdown.mockResolvedValue(markdownApiResult[attachmentFile.name]);
});
describe('when uploading succeeds', () => {
@@ -222,18 +246,20 @@ describe('content_editor/extensions/attachment', () => {
mock.onPost().reply(HTTP_STATUS_OK, successResponse);
});
- it('inserts a loading mark', async () => {
- const expectedDoc = doc(p(loading({ label: 'test-file' })));
+ it('inserts a link with a blob url', async () => {
+ const expectedDoc = doc(
+ p(link({ uploading: attachmentFile.name, href: blobUrl }, 'test-file.zip')),
+ );
await expectDocumentAfterTransaction({
+ tiptapEditor,
number: 1,
expectedDoc,
action: () => tiptapEditor.commands.uploadAttachment({ file: attachmentFile }),
});
});
- it('updates the loading mark with a link with canonicalSrc and href attrs', async () => {
- const [, group, project] = markdownApiResult.match(/\/(group[0-9]+)\/(project[0-9]+)\//);
+ it('updates the blob url link with an actual link with canonicalSrc and href attrs', async () => {
const expectedDoc = doc(
p(
link(
@@ -241,12 +267,13 @@ describe('content_editor/extensions/attachment', () => {
canonicalSrc: 'test-file.zip',
href: `/${group}/${project}/-/wikis/test-file.zip`,
},
- 'test-file',
+ 'test-file.zip',
),
),
);
await expectDocumentAfterTransaction({
+ tiptapEditor,
number: 2,
expectedDoc,
action: () => tiptapEditor.commands.uploadAttachment({ file: attachmentFile }),
@@ -263,6 +290,7 @@ describe('content_editor/extensions/attachment', () => {
const expectedDoc = doc(p(''));
await expectDocumentAfterTransaction({
+ tiptapEditor,
number: 2,
expectedDoc,
action: () => tiptapEditor.commands.uploadAttachment({ file: attachmentFile }),
@@ -279,5 +307,433 @@ describe('content_editor/extensions/attachment', () => {
});
});
});
+
+ describe('uploading multiple files', () => {
+ const uploadMultipleFiles = () => {
+ const files = [
+ attachmentFile,
+ imageFile,
+ videoFile,
+ attachmentFile1,
+ attachmentFile2,
+ videoFile1,
+ audioFile,
+ ];
+
+ for (const file of files) {
+ renderMarkdown.mockImplementation((markdown) =>
+ Promise.resolve(markdownApiResult[markdown.match(/\((.+?)\)$/)[1]]),
+ );
+
+ mock
+ .onPost()
+ .replyOnce(HTTP_STATUS_OK, { link: { markdown: `![test-file](${file.name})` } });
+
+ tiptapEditor.commands.uploadAttachment({ file });
+ }
+ };
+
+ it.each([
+ [1, () => doc(p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')))],
+ [
+ 2,
+ () =>
+ doc(
+ p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ ),
+ ],
+ [
+ 3,
+ () =>
+ doc(
+ p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ ),
+ ],
+ [
+ 4,
+ () =>
+ doc(
+ p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ ),
+ ],
+ [
+ 5,
+ () =>
+ doc(
+ p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ ),
+ ],
+ [
+ 6,
+ () =>
+ doc(
+ p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ ),
+ ],
+ [
+ 7,
+ () =>
+ doc(
+ p(link({ href: blobUrl, uploading: 'test-file.zip' }, 'test-file.zip')),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 8,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(image({ alt: 'test-file.png', src: blobUrl, uploading: 'test-file.png' })),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 9,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(
+ image({
+ alt: 'test-file.png',
+ src: blobUrl,
+ canonicalSrc: 'test-file.png',
+ uploading: false,
+ }),
+ ),
+ p(video({ alt: 'test-file.mp4', src: blobUrl, uploading: 'test-file.mp4' })),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 10,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(
+ image({
+ alt: 'test-file.png',
+ src: blobUrl,
+ canonicalSrc: 'test-file.png',
+ uploading: false,
+ }),
+ ),
+ p(
+ video({
+ alt: 'test-file.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file.mp4',
+ uploading: false,
+ }),
+ ),
+ p(link({ href: blobUrl, uploading: 'test-file1.zip' }, 'test-file1.zip')),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 11,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(
+ image({
+ alt: 'test-file.png',
+ src: blobUrl,
+ canonicalSrc: 'test-file.png',
+ uploading: false,
+ }),
+ ),
+ p(
+ video({
+ alt: 'test-file.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file.mp4',
+ uploading: false,
+ }),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file1.zip`,
+ canonicalSrc: 'test-file1.zip',
+ uploading: false,
+ },
+ 'test-file1.zip',
+ ),
+ ),
+ p(link({ href: blobUrl, uploading: 'test-file2.zip' }, 'test-file2.zip')),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 12,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(
+ image({
+ alt: 'test-file.png',
+ src: blobUrl,
+ canonicalSrc: 'test-file.png',
+ uploading: false,
+ }),
+ ),
+ p(
+ video({
+ alt: 'test-file.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file.mp4',
+ uploading: false,
+ }),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file1.zip`,
+ canonicalSrc: 'test-file1.zip',
+ uploading: false,
+ },
+ 'test-file1.zip',
+ ),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file2.zip`,
+ canonicalSrc: 'test-file2.zip',
+ uploading: false,
+ },
+ 'test-file2.zip',
+ ),
+ ),
+ p(video({ alt: 'test-file1.mp4', src: blobUrl, uploading: 'test-file1.mp4' })),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 13,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(
+ image({
+ alt: 'test-file.png',
+ src: blobUrl,
+ canonicalSrc: 'test-file.png',
+ uploading: false,
+ }),
+ ),
+ p(
+ video({
+ alt: 'test-file.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file.mp4',
+ uploading: false,
+ }),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file1.zip`,
+ canonicalSrc: 'test-file1.zip',
+ uploading: false,
+ },
+ 'test-file1.zip',
+ ),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file2.zip`,
+ canonicalSrc: 'test-file2.zip',
+ uploading: false,
+ },
+ 'test-file2.zip',
+ ),
+ ),
+ p(
+ video({
+ alt: 'test-file1.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file1.mp4',
+ uploading: false,
+ }),
+ ),
+ p(audio({ alt: 'test-file.mp3', src: blobUrl, uploading: 'test-file.mp3' })),
+ ),
+ ],
+ [
+ 14,
+ () =>
+ doc(
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file.zip`,
+ canonicalSrc: 'test-file.zip',
+ uploading: false,
+ },
+ 'test-file.zip',
+ ),
+ ),
+ p(
+ image({
+ alt: 'test-file.png',
+ src: blobUrl,
+ canonicalSrc: 'test-file.png',
+ uploading: false,
+ }),
+ ),
+ p(
+ video({
+ alt: 'test-file.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file.mp4',
+ uploading: false,
+ }),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file1.zip`,
+ canonicalSrc: 'test-file1.zip',
+ uploading: false,
+ },
+ 'test-file1.zip',
+ ),
+ ),
+ p(
+ link(
+ {
+ href: `/${group}/${project}/-/wikis/test-file2.zip`,
+ canonicalSrc: 'test-file2.zip',
+ uploading: false,
+ },
+ 'test-file2.zip',
+ ),
+ ),
+ p(
+ video({
+ alt: 'test-file1.mp4',
+ src: blobUrl,
+ canonicalSrc: 'test-file1.mp4',
+ uploading: false,
+ }),
+ ),
+ p(
+ audio({
+ alt: 'test-file.mp3',
+ src: blobUrl,
+ canonicalSrc: 'test-file.mp3',
+ uploading: false,
+ }),
+ ),
+ ),
+ ],
+ ])('uploads all files of mixed types successfully (tx %i)', async (n, document) => {
+ await expectDocumentAfterTransaction({
+ tiptapEditor,
+ number: n,
+ expectedDoc: document(),
+ action: uploadMultipleFiles,
+ });
+ });
+
+ it('cleans up the state if all uploads fail', async () => {
+ await expectDocumentAfterTransaction({
+ tiptapEditor,
+ number: 14,
+ expectedDoc: doc(p(), p(), p(), p(), p(), p(), p()),
+ action: () => {
+ // Set max file size to 1 byte, our file is 3 bytes
+ gon.max_file_size = 1 / 1024 / 1024;
+ uploadMultipleFiles();
+ },
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/content_editor/extensions/link_spec.js b/spec/frontend/content_editor/extensions/link_spec.js
index ead898554d1..3c6f28f0c32 100644
--- a/spec/frontend/content_editor/extensions/link_spec.js
+++ b/spec/frontend/content_editor/extensions/link_spec.js
@@ -31,11 +31,6 @@ describe('content_editor/extensions/link', () => {
${'[link 123](read me.md)'} | ${() => p(link({ href: 'read me.md' }, 'link 123'))}
${'text'} | ${() => p('text')}
${'documentation](readme.md'} | ${() => p('documentation](readme.md')}
- ${'http://example.com '} | ${() => p(link({ href: 'http://example.com' }, 'http://example.com'))}
- ${'https://example.com '} | ${() => p(link({ href: 'https://example.com' }, 'https://example.com'))}
- ${'www.example.com '} | ${() => p(link({ href: 'www.example.com' }, 'www.example.com'))}
- ${'example.com/ab.html '} | ${() => p('example.com/ab.html')}
- ${'https://www.google.com '} | ${() => p(link({ href: 'https://www.google.com' }, 'https://www.google.com'))}
`('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => {
const expectedDoc = doc(insertedNode());
diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js
index 506db4144fc..3729b303cc6 100644
--- a/spec/frontend/content_editor/services/markdown_serializer_spec.js
+++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js
@@ -281,17 +281,18 @@ hi
).toBe('![GitLab][gitlab-url]');
});
- it('omits image data urls when serializing', () => {
+ it.each`
+ src
+ ${'data:image/png;base64,iVBORw0KGgoAAAAN'}
+ ${'blob:https://gitlab.com/1234-5678-9012-3456'}
+ `('omits images with data/blob urls when serializing', ({ src }) => {
+ expect(serialize(paragraph(image({ src, alt: 'image' })))).toBe('');
+ });
+
+ it('does not escape url in an image', () => {
expect(
- serialize(
- paragraph(
- image({
- src: 'data:image/png;base64,iVBORw0KGgoAAAAN',
- alt: 'image',
- }),
- ),
- ),
- ).toBe('![image]()');
+ serialize(paragraph(image({ src: 'https://example.com/image__1_.png', alt: 'image' }))),
+ ).toBe('![image](https://example.com/image__1_.png)');
});
it('correctly serializes strikethrough', () => {
diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js
index 16f90a15c24..1f4a367e46c 100644
--- a/spec/frontend/content_editor/test_utils.js
+++ b/spec/frontend/content_editor/test_utils.js
@@ -5,6 +5,7 @@ import { Text } from '@tiptap/extension-text';
import { Editor } from '@tiptap/vue-2';
import { builders, eq } from 'prosemirror-test-builder';
import { nextTick } from 'vue';
+import waitForPromises from 'helpers/wait_for_promises';
import Audio from '~/content_editor/extensions/audio';
import Blockquote from '~/content_editor/extensions/blockquote';
import Bold from '~/content_editor/extensions/bold';
@@ -63,6 +64,12 @@ export const emitEditorEvent = ({ tiptapEditor, event, params = {} }) => {
return nextTick();
};
+export const createTransactionWithMeta = (metaKey, metaValue) => {
+ return {
+ getMeta: (key) => (key === metaKey ? metaValue : null),
+ };
+};
+
/**
* Creates an instance of the Tiptap Editor class
* with a minimal configuration for testing purposes.
@@ -205,6 +212,24 @@ export const waitUntilNextDocTransaction = ({ tiptapEditor, action = () => {} })
});
};
+export const expectDocumentAfterTransaction = ({ tiptapEditor, number, expectedDoc, action }) => {
+ return new Promise((resolve) => {
+ let counter = 0;
+ const handleTransaction = async () => {
+ counter += 1;
+ if (counter === number) {
+ expect(tiptapEditor.state.doc.toJSON()).toEqual(expectedDoc.toJSON());
+ tiptapEditor.off('update', handleTransaction);
+ await waitForPromises();
+ resolve();
+ }
+ };
+
+ tiptapEditor.on('update', handleTransaction);
+ action();
+ });
+};
+
export const createTiptapEditor = (extensions = []) =>
createTestEditor({
extensions: [
diff --git a/spec/frontend/diffs/utils/merge_request_spec.js b/spec/frontend/diffs/utils/merge_request_spec.js
index 21599a3be45..11c0efb9a9c 100644
--- a/spec/frontend/diffs/utils/merge_request_spec.js
+++ b/spec/frontend/diffs/utils/merge_request_spec.js
@@ -8,7 +8,7 @@ import { diffMetadata } from '../mock_data/diff_metadata';
describe('Merge Request utilities', () => {
const derivedBaseInfo = {
mrPath: '/gitlab-org/gitlab-test/-/merge_requests/4',
- userOrGroup: 'gitlab-org',
+ namespace: 'gitlab-org',
project: 'gitlab-test',
id: '4',
};
@@ -22,7 +22,7 @@ describe('Merge Request utilities', () => {
};
const unparseableEndpoint = {
mrPath: undefined,
- userOrGroup: undefined,
+ namespace: undefined,
project: undefined,
id: undefined,
...noVersion,
@@ -79,29 +79,41 @@ describe('Merge Request utilities', () => {
});
describe('getDerivedMergeRequestInformation', () => {
- let endpoint = `${diffMetadata.latest_version_path}.json?searchParam=irrelevant`;
+ const bare = diffMetadata.latest_version_path;
it.each`
- argument | response
- ${{ endpoint }} | ${{ ...derivedBaseInfo, ...noVersion }}
- ${{}} | ${unparseableEndpoint}
- ${{ endpoint: undefined }} | ${unparseableEndpoint}
- ${{ endpoint: null }} | ${unparseableEndpoint}
+ argument | response
+ ${{ endpoint: `${bare}.json?searchParam=irrelevant` }} | ${{ ...derivedBaseInfo, ...noVersion }}
+ ${{}} | ${unparseableEndpoint}
+ ${{ endpoint: undefined }} | ${unparseableEndpoint}
+ ${{ endpoint: null }} | ${unparseableEndpoint}
`('generates the correct derived results based on $argument', ({ argument, response }) => {
expect(getDerivedMergeRequestInformation(argument)).toStrictEqual(response);
});
- describe('version information', () => {
- const bare = diffMetadata.latest_version_path;
- endpoint = diffMetadata.merge_request_diffs[0].compare_path;
+ describe('sub-group namespace', () => {
+ it('extracts the entire namespace plus the project name', () => {
+ const { namespace, project } = getDerivedMergeRequestInformation({
+ endpoint: `/some/deep/path/of/groups${bare}`,
+ });
+ expect(namespace).toBe('some/deep/path/of/groups/gitlab-org');
+ expect(project).toBe('gitlab-test');
+ });
+ });
+
+ describe('version information', () => {
it('still gets the correct derived information', () => {
- expect(getDerivedMergeRequestInformation({ endpoint })).toMatchObject(derivedBaseInfo);
+ expect(
+ getDerivedMergeRequestInformation({
+ endpoint: diffMetadata.merge_request_diffs[0].compare_path,
+ }),
+ ).toMatchObject(derivedBaseInfo);
});
it.each`
url | versionPart
- ${endpoint} | ${derivedVersionInfo}
+ ${diffMetadata.merge_request_diffs[0].compare_path} | ${derivedVersionInfo}
${`${bare}?diff_id=${derivedVersionInfo.diffId}`} | ${{ ...derivedVersionInfo, startSha: undefined }}
${`${bare}?start_sha=${derivedVersionInfo.startSha}`} | ${{ ...derivedVersionInfo, diffId: undefined }}
`(
diff --git a/spec/frontend/lib/utils/tappable_promise_spec.js b/spec/frontend/lib/utils/tappable_promise_spec.js
new file mode 100644
index 00000000000..654cd20a9de
--- /dev/null
+++ b/spec/frontend/lib/utils/tappable_promise_spec.js
@@ -0,0 +1,63 @@
+import TappablePromise from '~/lib/utils/tappable_promise';
+
+describe('TappablePromise', () => {
+ it('allows a promise to have a progress indicator', () => {
+ const pseudoProgress = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1];
+ const progressCallback = jest.fn();
+ const promise = new TappablePromise((tap, resolve) => {
+ pseudoProgress.forEach(tap);
+ resolve('done');
+
+ return 'returned value';
+ });
+
+ return promise
+ .tap(progressCallback)
+ .then((val) => {
+ expect(val).toBe('done');
+ expect(val).not.toBe('returned value');
+
+ expect(progressCallback).toHaveBeenCalledTimes(pseudoProgress.length);
+
+ pseudoProgress.forEach((progress, index) => {
+ expect(progressCallback).toHaveBeenNthCalledWith(index + 1, progress);
+ });
+ })
+ .catch(() => {});
+ });
+
+ it('resolves with the value returned by the callback', () => {
+ const promise = new TappablePromise((tap) => {
+ tap(0.5);
+ return 'test';
+ });
+
+ return promise
+ .tap((progress) => {
+ expect(progress).toBe(0.5);
+ })
+ .then((value) => {
+ expect(value).toBe('test');
+ });
+ });
+
+ it('allows a promise to be rejected', () => {
+ const promise = new TappablePromise((tap, resolve, reject) => {
+ reject(new Error('test error'));
+ });
+
+ return promise.catch((e) => {
+ expect(e.message).toBe('test error');
+ });
+ });
+
+ it('rejects the promise if the callback throws an error', () => {
+ const promise = new TappablePromise(() => {
+ throw new Error('test error');
+ });
+
+ return promise.catch((e) => {
+ expect(e.message).toBe('test error');
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
index ab259249772..e54b87c307c 100644
--- a/spec/frontend/monitoring/components/dashboard_header_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -9,12 +9,7 @@ import RefreshButton from '~/monitoring/components/refresh_button.vue';
import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
-import {
- environmentData,
- dashboardGitResponse,
- selfMonitoringDashboardGitResponse,
- dashboardHeaderProps,
-} from '../mock_data';
+import { environmentData, dashboardGitResponse, dashboardHeaderProps } from '../mock_data';
import { setupAllDashboards, setupStoreWithDashboard, setupStoreWithData } from '../store_utils';
const mockProjectPath = 'https://path/to/project';
@@ -267,14 +262,8 @@ describe('Dashboard header', () => {
});
describe('actions menu', () => {
- const ootbDashboards = [
- dashboardGitResponse[0].path,
- selfMonitoringDashboardGitResponse[0].path,
- ];
- const customDashboards = [
- dashboardGitResponse[1].path,
- selfMonitoringDashboardGitResponse[1].path,
- ];
+ const ootbDashboards = [dashboardGitResponse[0].path];
+ const customDashboards = [dashboardGitResponse[1].path];
it('is rendered', () => {
createShallowWrapper();
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index 00be5868ba3..1d23190e586 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -206,32 +206,6 @@ export const dashboardGitResponse = [
...customDashboardsData,
];
-export const selfMonitoringDashboardGitResponse = [
- {
- default: true,
- display_name: 'Default',
- can_edit: false,
- system_dashboard: true,
- out_of_the_box_dashboard: true,
- project_blob_path: null,
- path: 'config/prometheus/self_monitoring_default.yml',
- starred: false,
- user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=config/prometheus/self_monitoring_default.yml`,
- },
- {
- default: false,
- display_name: 'dashboard.yml',
- can_edit: true,
- system_dashboard: false,
- out_of_the_box_dashboard: false,
- project_blob_path: `${mockProjectDir}/-/blob/main/.gitlab/dashboards/dashboard.yml`,
- path: '.gitlab/dashboards/dashboard.yml',
- starred: true,
- user_starred_path: `${mockProjectDir}/metrics_user_starred_dashboards?dashboard_path=.gitlab/dashboards/dashboard.yml`,
- },
- ...customDashboardsData,
-];
-
// Metrics mocks
export const metricsResult = [
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 c8d972b19a3..17681c08dbd 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
@@ -24,7 +24,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
</div>
<div
- class="js-vue-markdown-field md-area position-relative gfm-form js-expanded"
+ class="js-vue-markdown-field md-area position-relative gfm-form gl-border-none! gl-shadow-none! js-expanded"
data-uploads-path=""
>
<markdown-header-stub
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index b7877784a2d..62cbb1bacb6 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -303,12 +303,6 @@ describe('WorkItemDescription', () => {
expect(workItemResponseHandler).toHaveBeenCalled();
});
-
- it('skips calling the work item query when missing workItemIid', async () => {
- await createComponent({ workItemIid: null });
-
- expect(workItemResponseHandler).not.toHaveBeenCalled();
- });
},
);
});