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:
Diffstat (limited to 'spec/frontend/drawio/drawio_editor_spec.js')
-rw-r--r--spec/frontend/drawio/drawio_editor_spec.js479
1 files changed, 479 insertions, 0 deletions
diff --git a/spec/frontend/drawio/drawio_editor_spec.js b/spec/frontend/drawio/drawio_editor_spec.js
new file mode 100644
index 00000000000..d7d75922e1e
--- /dev/null
+++ b/spec/frontend/drawio/drawio_editor_spec.js
@@ -0,0 +1,479 @@
+import { launchDrawioEditor } from '~/drawio/drawio_editor';
+import {
+ DRAWIO_EDITOR_URL,
+ DRAWIO_FRAME_ID,
+ DIAGRAM_BACKGROUND_COLOR,
+ DRAWIO_IFRAME_TIMEOUT,
+ DIAGRAM_MAX_SIZE,
+} from '~/drawio/constants';
+import { createAlert, VARIANT_SUCCESS } from '~/alert';
+
+jest.mock('~/alert');
+
+jest.useFakeTimers();
+
+describe('drawio/drawio_editor', () => {
+ let editorFacade;
+ let drawioIFrameReceivedMessages;
+ const diagramURL = `${window.location.origin}/uploads/diagram.drawio.svg`;
+ const testSvg = '<svg></svg>';
+ const testEncodedSvg = `data:image/svg+xml;base64,${btoa(testSvg)}`;
+ const filename = 'diagram.drawio.svg';
+
+ const findDrawioIframe = () => document.getElementById(DRAWIO_FRAME_ID);
+ const waitForDrawioIFrameMessage = ({ messageNumber = 1 } = {}) =>
+ new Promise((resolve) => {
+ let messageCounter = 0;
+ const iframe = findDrawioIframe();
+
+ iframe?.contentWindow.addEventListener('message', (event) => {
+ drawioIFrameReceivedMessages.push(event);
+
+ messageCounter += 1;
+
+ if (messageCounter === messageNumber) {
+ resolve();
+ }
+ });
+ });
+ const expectDrawioIframeMessage = ({ expectation, messageNumber = 1 }) => {
+ expect(drawioIFrameReceivedMessages).toHaveLength(messageNumber);
+ expect(JSON.parse(drawioIFrameReceivedMessages[messageNumber - 1].data)).toEqual(expectation);
+ };
+ const postMessageToParentWindow = (data) => {
+ const event = new Event('message');
+
+ Object.setPrototypeOf(event, {
+ source: findDrawioIframe().contentWindow,
+ data: JSON.stringify(data),
+ });
+
+ window.dispatchEvent(event);
+ };
+
+ beforeEach(() => {
+ editorFacade = {
+ getDiagram: jest.fn(),
+ uploadDiagram: jest.fn(),
+ insertDiagram: jest.fn(),
+ updateDiagram: jest.fn(),
+ };
+ drawioIFrameReceivedMessages = [];
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ findDrawioIframe()?.remove();
+ });
+
+ describe('initializing', () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+ });
+
+ it('creates the drawio editor iframe and attaches it to the body', () => {
+ expect(findDrawioIframe().getAttribute('src')).toBe(DRAWIO_EDITOR_URL);
+ });
+
+ it('sets drawio-editor classname to the iframe', () => {
+ expect(findDrawioIframe().classList).toContain('drawio-editor');
+ });
+ });
+
+ describe(`when parent window does not receive configure event after ${DRAWIO_IFRAME_TIMEOUT} ms`, () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+ });
+
+ it('disposes draw.io iframe', () => {
+ expect(findDrawioIframe()).not.toBe(null);
+ jest.runAllTimers();
+ expect(findDrawioIframe()).toBe(null);
+ });
+
+ it('displays an alert indicating that the draw.io editor could not be loaded', () => {
+ jest.runAllTimers();
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'The diagrams.net editor could not be loaded.',
+ });
+ });
+ });
+
+ describe('when parent window receives configure event', () => {
+ beforeEach(async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'configure' });
+
+ await waitForDrawioIFrameMessage();
+ });
+
+ it('sends configure action to the draw.io iframe', () => {
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'configure',
+ config: {
+ darkColor: '#202020',
+ settingsName: 'gitlab',
+ },
+ colorSchemeMeta: false,
+ },
+ });
+ });
+
+ it('does not remove the iframe after the load error timeouts run', () => {
+ jest.runAllTimers();
+
+ expect(findDrawioIframe()).not.toBe(null);
+ });
+ });
+
+ describe('when parent window receives init event', () => {
+ describe('when there isn’t a diagram selected', () => {
+ beforeEach(() => {
+ editorFacade.getDiagram.mockResolvedValueOnce(null);
+
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('sends load action to the draw.io iframe with empty svg and title', async () => {
+ await waitForDrawioIFrameMessage();
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'load',
+ xml: null,
+ border: 8,
+ background: DIAGRAM_BACKGROUND_COLOR,
+ dark: false,
+ title: null,
+ },
+ });
+ });
+ });
+
+ describe('when there is a diagram selected', () => {
+ const diagramSvg = '<svg></svg>';
+
+ beforeEach(() => {
+ editorFacade.getDiagram.mockResolvedValueOnce({
+ diagramURL,
+ diagramSvg,
+ filename,
+ contentType: 'image/svg+xml',
+ });
+
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('sends load action to the draw.io iframe with the selected diagram svg and filename', async () => {
+ await waitForDrawioIFrameMessage();
+
+ // Step 5: The draw.io editor will send the downloaded diagram to the iframe
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'load',
+ xml: diagramSvg,
+ border: 8,
+ background: DIAGRAM_BACKGROUND_COLOR,
+ dark: false,
+ title: filename,
+ },
+ });
+ });
+
+ it('sets the drawio iframe as visible and resets cursor', async () => {
+ await waitForDrawioIFrameMessage();
+
+ expect(findDrawioIframe().style.visibility).toBe('visible');
+ expect(findDrawioIframe().style.cursor).toBe('');
+ });
+
+ it('scrolls window to the top', async () => {
+ await waitForDrawioIFrameMessage();
+
+ expect(window.scrollX).toBe(0);
+ });
+ });
+
+ describe.each`
+ description | errorMessage | diagram
+ ${'when there is an image selected that is not an svg file'} | ${'The selected image is not a valid SVG diagram'} | ${{
+ diagramURL,
+ contentType: 'image/png',
+ filename: 'image.png',
+}}
+ ${'when the selected image is not an asset upload'} | ${'The selected image is not an asset uploaded in the application'} | ${{
+ diagramSvg: '<svg></svg>',
+ filename,
+ contentType: 'image/svg+xml',
+ diagramURL: 'https://example.com/image.drawio.svg',
+}}
+ ${'when the selected image is too large'} | ${'The selected image is too large.'} | ${{
+ diagramSvg: 'x'.repeat(DIAGRAM_MAX_SIZE + 1),
+ filename,
+ contentType: 'image/svg+xml',
+ diagramURL,
+}}
+ `('$description', ({ errorMessage, diagram }) => {
+ beforeEach(() => {
+ editorFacade.getDiagram.mockResolvedValueOnce(diagram);
+
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('displays an error alert indicating that the image is not a diagram', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: errorMessage,
+ error: expect.any(Error),
+ });
+ });
+
+ it('disposes the draw.io diagram iframe', () => {
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+
+ describe('when loading a diagram fails', () => {
+ beforeEach(() => {
+ editorFacade.getDiagram.mockRejectedValueOnce(new Error());
+
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'init' });
+ });
+
+ it('displays an error alert indicating the failure', () => {
+ expect(createAlert).toHaveBeenCalledWith({
+ message: 'Cannot load the diagram into the diagrams.net editor',
+ error: expect.any(Error),
+ });
+ });
+
+ it('disposes the draw.io diagram iframe', () => {
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+ });
+
+ describe('when parent window receives prompt event', () => {
+ describe('when the filename is empty', () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+
+ postMessageToParentWindow({ event: 'prompt', value: '' });
+ });
+
+ it('sends prompt action to the draw.io iframe requesting a filename', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 1 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'prompt',
+ titleKey: 'filename',
+ okKey: 'save',
+ defaultValue: 'diagram.drawio.svg',
+ },
+ messageNumber: 1,
+ });
+ });
+
+ it('sends dialog action to the draw.io iframe indicating that the filename cannot be empty', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 2 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'dialog',
+ titleKey: 'error',
+ messageKey: 'filenameShort',
+ buttonKey: 'ok',
+ },
+ messageNumber: 2,
+ });
+ });
+ });
+
+ describe('when the event data is not empty', () => {
+ beforeEach(async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'prompt', value: 'diagram.drawio.svg' });
+
+ await waitForDrawioIFrameMessage();
+ });
+
+ it('starts the saving file process', () => {
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'spinner',
+ show: true,
+ messageKey: 'saving',
+ },
+ });
+ });
+ });
+ });
+
+ describe('when parent receives export event', () => {
+ beforeEach(() => {
+ editorFacade.uploadDiagram.mockResolvedValueOnce({});
+ });
+
+ it('reloads diagram in the draw.io editor', async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage();
+
+ expectDrawioIframeMessage({
+ expectation: expect.objectContaining({
+ action: 'load',
+ xml: expect.stringContaining(testSvg),
+ }),
+ });
+ });
+
+ it('marks the diagram as modified in the draw.io editor', async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 2 });
+
+ expectDrawioIframeMessage({
+ expectation: expect.objectContaining({
+ action: 'status',
+ modified: true,
+ }),
+ messageNumber: 2,
+ });
+ });
+
+ describe('when the diagram filename is set', () => {
+ const TEST_FILENAME = 'diagram.drawio.svg';
+
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade, filename: TEST_FILENAME });
+ });
+
+ it('displays loading spinner in the draw.io editor', async () => {
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'spinner',
+ show: true,
+ messageKey: 'saving',
+ },
+ messageNumber: 3,
+ });
+ });
+
+ it('uploads exported diagram', async () => {
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expect(editorFacade.uploadDiagram).toHaveBeenCalledWith({
+ filename: TEST_FILENAME,
+ diagramSvg: expect.stringContaining(testSvg),
+ });
+ });
+
+ describe('when uploading the exported diagram succeeds', () => {
+ it('displays an alert indicating that the diagram was uploaded successfully', async () => {
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expect(createAlert).toHaveBeenCalledWith({
+ message: expect.any(String),
+ variant: VARIANT_SUCCESS,
+ fadeTransition: true,
+ });
+ });
+
+ it('disposes iframe', () => {
+ jest.runAllTimers();
+
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+
+ describe('when uploading the exported diagram fails', () => {
+ const uploadError = new Error();
+
+ beforeEach(() => {
+ editorFacade.uploadDiagram.mockReset();
+ editorFacade.uploadDiagram.mockRejectedValue(uploadError);
+
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+ });
+
+ it('hides loading indicator in the draw.io editor', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 4 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'spinner',
+ show: false,
+ },
+ messageNumber: 4,
+ });
+ });
+
+ it('displays an error dialog in the draw.io editor', async () => {
+ await waitForDrawioIFrameMessage({ messageNumber: 5 });
+
+ expectDrawioIframeMessage({
+ expectation: {
+ action: 'dialog',
+ titleKey: 'error',
+ modified: true,
+ buttonKey: 'close',
+ messageKey: 'errorSavingFile',
+ },
+ messageNumber: 5,
+ });
+ });
+ });
+ });
+
+ describe('when diagram filename is not set', () => {
+ it('sends prompt action to the draw.io iframe', async () => {
+ launchDrawioEditor({ editorFacade });
+ postMessageToParentWindow({ event: 'export', data: testEncodedSvg });
+
+ await waitForDrawioIFrameMessage({ messageNumber: 3 });
+
+ expect(drawioIFrameReceivedMessages[2].data).toEqual(
+ JSON.stringify({
+ action: 'prompt',
+ titleKey: 'filename',
+ okKey: 'save',
+ defaultValue: 'diagram.drawio.svg',
+ }),
+ );
+ });
+ });
+ });
+
+ describe('when parent window receives exit event', () => {
+ beforeEach(() => {
+ launchDrawioEditor({ editorFacade });
+ });
+
+ it('disposes the the draw.io iframe', () => {
+ expect(findDrawioIframe()).not.toBe(null);
+
+ postMessageToParentWindow({ event: 'exit' });
+
+ expect(findDrawioIframe()).toBe(null);
+ });
+ });
+});