diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts new file mode 100644 index 000000000000..00e2f262a0ea --- /dev/null +++ b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts @@ -0,0 +1,90 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { expect, galata, test } from '@jupyterlab/galata'; +import * as path from 'path'; +import { Locator } from '@playwright/test'; + +const fileName = 'mermaid_diagrams.ipynb'; + +const EXPECTED_MERMAID_ORDER = [ + 'flowchart', + 'sequence', + 'class', + 'state', + 'er', + 'journey', + 'gantt', + 'pie', + 'quadrant', + 'requirement', + 'c4', + 'mindmap', + 'timeline', + 'sankey', + 'xy' +]; + +/** + * Workaround for playwright not handling screenshots + * for elements larger than viewport, derived from: + * https://github.com/microsoft/playwright/issues/13486#issuecomment-1112012053 + */ +async function resizePageAndScreenshot(locator: Locator) { + const page = locator.page(); + const box = await locator.boundingBox(); + const originalSize = page.viewportSize(); + if (box.width > originalSize.width || box.height > originalSize.height) { + const scaleFactor = Math.max( + originalSize.width / box.width, + originalSize.height / box.height + ); + await page.setViewportSize({ + width: Math.ceil(box.width * scaleFactor), + height: Math.ceil(box.height * scaleFactor) + }); + } + const screenshot = await locator.screenshot(); + await page.setViewportSize(originalSize); + return screenshot; +} + +for (const theme of ['default', 'dark']) { + const dark = theme === 'dark'; + test.describe(`Notebook Mermaid Diagrams ${theme}`, () => { + test.beforeEach(async ({ page, request, tmpPath }) => { + const contents = galata.newContentsHelper(request); + await contents.uploadFile( + path.resolve(__dirname, `./notebooks/${fileName}`), + `${tmpPath}/${fileName}` + ); + await page.filebrowser.openDirectory(tmpPath); + const nbPath = `${tmpPath}/${fileName}`; + + if (dark) { + await page.theme.setDarkTheme(); + } + + await page.notebook.openByPath(nbPath); + await page.notebook.activate(fileName); + }); + + for (let i = 0; i < EXPECTED_MERMAID_ORDER.length; i++) { + let diagram = EXPECTED_MERMAID_ORDER[i]; + const iZero = `${i}`.padStart(2, '0'); + + test(`Mermaid Diagram ${i} ${diagram} in ${theme} theme`, async ({ + page + }) => { + const output = page.locator( + `.jp-Cell:nth-child(${i + 1}) .jp-RenderedMermaid` + ); + await output.waitFor(); + + expect(await resizePageAndScreenshot(output)).toMatchSnapshot( + `mermaid-diagram-${theme}-${iZero}-${diagram}.png` + ); + }); + } + }); +} diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-00-flowchart-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-00-flowchart-jupyterlab-linux.png new file mode 100644 index 000000000000..de8a9c79f4db Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-00-flowchart-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-01-sequence-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-01-sequence-jupyterlab-linux.png new file mode 100644 index 000000000000..8ec48dacd212 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-01-sequence-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-02-class-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-02-class-jupyterlab-linux.png new file mode 100644 index 000000000000..bb7712839c71 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-02-class-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-03-state-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-03-state-jupyterlab-linux.png new file mode 100644 index 000000000000..28218e4b54d3 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-03-state-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-04-er-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-04-er-jupyterlab-linux.png new file mode 100644 index 000000000000..d8dc2e6fe4d5 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-04-er-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-05-journey-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-05-journey-jupyterlab-linux.png new file mode 100644 index 000000000000..04616392001d Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-05-journey-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-06-gantt-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-06-gantt-jupyterlab-linux.png new file mode 100644 index 000000000000..bff0d80342eb Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-06-gantt-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-07-pie-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-07-pie-jupyterlab-linux.png new file mode 100644 index 000000000000..2e9598b53dd3 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-07-pie-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-08-quadrant-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-08-quadrant-jupyterlab-linux.png new file mode 100644 index 000000000000..996ad0494d23 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-08-quadrant-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-09-requirement-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-09-requirement-jupyterlab-linux.png new file mode 100644 index 000000000000..a524b611fc89 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-09-requirement-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-10-c4-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-10-c4-jupyterlab-linux.png new file mode 100644 index 000000000000..1b672e1b883d Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-10-c4-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-11-mindmap-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-11-mindmap-jupyterlab-linux.png new file mode 100644 index 000000000000..2da58fc745a5 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-11-mindmap-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-12-timeline-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-12-timeline-jupyterlab-linux.png new file mode 100644 index 000000000000..2bf39613b93b Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-12-timeline-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-13-sankey-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-13-sankey-jupyterlab-linux.png new file mode 100644 index 000000000000..fac0249423a5 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-13-sankey-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-14-xy-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-14-xy-jupyterlab-linux.png new file mode 100644 index 000000000000..908c9ff4051f Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-dark-14-xy-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-00-flowchart-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-00-flowchart-jupyterlab-linux.png new file mode 100644 index 000000000000..3f33adc1cba2 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-00-flowchart-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-01-sequence-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-01-sequence-jupyterlab-linux.png new file mode 100644 index 000000000000..afac15531676 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-01-sequence-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-02-class-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-02-class-jupyterlab-linux.png new file mode 100644 index 000000000000..3b3812dc9d28 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-02-class-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-03-state-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-03-state-jupyterlab-linux.png new file mode 100644 index 000000000000..1fdd678a9859 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-03-state-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-04-er-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-04-er-jupyterlab-linux.png new file mode 100644 index 000000000000..53e05164c16a Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-04-er-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-05-journey-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-05-journey-jupyterlab-linux.png new file mode 100644 index 000000000000..ebec53f83679 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-05-journey-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-06-gantt-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-06-gantt-jupyterlab-linux.png new file mode 100644 index 000000000000..6e03ce464e90 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-06-gantt-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-07-pie-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-07-pie-jupyterlab-linux.png new file mode 100644 index 000000000000..fea66e28fe50 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-07-pie-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-08-quadrant-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-08-quadrant-jupyterlab-linux.png new file mode 100644 index 000000000000..7fb75f9a0314 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-08-quadrant-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-09-requirement-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-09-requirement-jupyterlab-linux.png new file mode 100644 index 000000000000..8642bc39657d Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-09-requirement-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-10-c4-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-10-c4-jupyterlab-linux.png new file mode 100644 index 000000000000..96c24134eb2f Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-10-c4-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-11-mindmap-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-11-mindmap-jupyterlab-linux.png new file mode 100644 index 000000000000..dc46eeb78015 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-11-mindmap-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-12-timeline-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-12-timeline-jupyterlab-linux.png new file mode 100644 index 000000000000..5a36b47b1cb1 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-12-timeline-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-13-sankey-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-13-sankey-jupyterlab-linux.png new file mode 100644 index 000000000000..1a3d5969fb51 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-13-sankey-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-14-xy-jupyterlab-linux.png b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-14-xy-jupyterlab-linux.png new file mode 100644 index 000000000000..7cfb85e7fb27 Binary files /dev/null and b/galata/test/jupyterlab/notebook-mermaid-diagrams.test.ts-snapshots/mermaid-diagram-default-14-xy-jupyterlab-linux.png differ diff --git a/galata/test/jupyterlab/notebooks/mermaid_diagrams.ipynb b/galata/test/jupyterlab/notebooks/mermaid_diagrams.ipynb new file mode 100644 index 000000000000..ac25bfaddc53 --- /dev/null +++ b/galata/test/jupyterlab/notebooks/mermaid_diagrams.ipynb @@ -0,0 +1,546 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## flowchart\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/flowchart.html) \n", + "\n", + "```mermaid\n", + "%%{init: {\"flowchart\": {\"htmlLabels\": false}} }%%\n", + "flowchart LR\n", + " markdown[\"`This **is** _Markdown_`\"]\n", + " newLines[\"`Line1\n", + " Line 2\n", + " Line 3`\"]\n", + " markdown --> newLines\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sequence\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/sequenceDiagram.html)\n", + "\n", + "```mermaid\n", + "sequenceDiagram\n", + " Alice->John: Hello John, how are you?\n", + " Note over Alice,John: A typical interaction
But now in two lines\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## class\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/classDiagram.html)\n", + "\n", + "```mermaid\n", + "---\n", + "title: Animal example\n", + "---\n", + "classDiagram\n", + " note \"From Duck till Zebra\"\n", + " Animal <|-- Duck\n", + " note for Duck \"can fly\\ncan swim\\ncan dive\\ncan help in debugging\"\n", + " Animal <|-- Fish\n", + " Animal <|-- Zebra\n", + " Animal : +int age\n", + " Animal : +String gender\n", + " Animal: +isMammal()\n", + " Animal: +mate()\n", + " class Duck{\n", + " +String beakColor\n", + " +swim()\n", + " +quack()\n", + " }\n", + " class Fish{\n", + " -int sizeInFeet\n", + " -canEat()\n", + " }\n", + " class Zebra{\n", + " +bool is_wild\n", + " +run()\n", + " }\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## state\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/stateDiagram.html)\n", + "\n", + "```mermaid\n", + " stateDiagram-v2\n", + " State1: The state with a note\n", + " note right of State1\n", + " Important information! You can write\n", + " notes.\n", + " end note\n", + " State1 --> State2\n", + " note left of State2 : This is the note to the left.\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## er\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/entityRelationshipDiagram.html)\n", + "\n", + "```mermaid\n", + "erDiagram\n", + " CAR ||--o{ NAMED-DRIVER : allows\n", + " CAR {\n", + " string registrationNumber PK\n", + " string make\n", + " string model\n", + " string[] parts\n", + " }\n", + " PERSON ||--o{ NAMED-DRIVER : is\n", + " PERSON {\n", + " string driversLicense PK \"The license #\"\n", + " string(99) firstName \"Only 99 characters are allowed\"\n", + " string lastName\n", + " string phone UK\n", + " int age\n", + " }\n", + " NAMED-DRIVER {\n", + " string carRegistrationNumber PK, FK\n", + " string driverLicence PK, FK\n", + " }\n", + " MANUFACTURER only one to zero or more CAR : makes\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## journey\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/userJourney.html)\n", + "\n", + "```mermaid\n", + "journey\n", + " title My working day\n", + " section Go to work\n", + " Make tea: 5: Me\n", + " Go upstairs: 3: Me\n", + " Do work: 1: Me, Cat\n", + " section Go home\n", + " Go downstairs: 5: Me\n", + " Sit down: 5: Me\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## gantt\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/gantt.html)\n", + "\n", + "```mermaid\n", + "gantt\n", + " dateFormat YYYY-MM-DD\n", + " title Adding GANTT diagram functionality to mermaid\n", + " excludes weekends\n", + " %% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week (\"sunday\") or \"weekends\", but not the word \"weekdays\".)\n", + "\n", + " section A section\n", + " Completed task :done, des1, 2014-01-06,2014-01-08\n", + " Active task :active, des2, 2014-01-09, 3d\n", + " Future task : des3, after des2, 5d\n", + " Future task2 : des4, after des3, 5d\n", + "\n", + " section Critical tasks\n", + " Completed task in the critical line :crit, done, 2014-01-06,24h\n", + " Implement parser and jison :crit, done, after des1, 2d\n", + " Create tests for parser :crit, active, 3d\n", + " Future task in critical line :crit, 5d\n", + " Create tests for renderer :2d\n", + " Add to mermaid :1d\n", + " Functionality added :milestone, 2014-01-25, 0d\n", + "\n", + " section Documentation\n", + " Describe gantt syntax :active, a1, after des1, 3d\n", + " Add gantt diagram to demo page :after a1 , 20h\n", + " Add another diagram to demo page :doc1, after a1 , 48h\n", + "\n", + " section Last section\n", + " Describe gantt syntax :after doc1, 3d\n", + " Add gantt diagram to demo page :20h\n", + " Add another diagram to demo page :48h\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## pie\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/pie.html)\n", + "\n", + "```mermaid\n", + "%%{init: {\"pie\": {\"textPosition\": 0.5}, \"themeVariables\": {\"pieOuterStrokeWidth\": \"5px\"}} }%%\n", + "pie showData\n", + " title Key elements in Product X\n", + " \"Calcium\" : 42.96\n", + " \"Potassium\" : 50.05\n", + " \"Magnesium\" : 10.01\n", + " \"Iron\" : 5\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## quadrant\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/quadrantChart.html)\n", + "\n", + "```mermaid\n", + "%%{init: {\"quadrantChart\": {\"chartWidth\": 400, \"chartHeight\": 400}, \"themeVariables\": {\"quadrant1TextFill\": \"#ff0000\"} }}%%\n", + "quadrantChart\n", + " x-axis Urgent --> Not Urgent\n", + " y-axis Not Important --> \"Important ❤\"\n", + " quadrant-1 Plan\n", + " quadrant-2 Do\n", + " quadrant-3 Delegate\n", + " quadrant-4 Delete\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## requirement\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/requirementDiagram.html)\n", + "> \n", + "> note: some long text also overflows the box upstream\n", + "\n", + "```mermaid\n", + "requirementDiagram\n", + " requirement test_req {\n", + " id: 1\n", + " text: the test text.\n", + " risk: high\n", + " verifymethod: test\n", + " }\n", + "\n", + " functionalRequirement test_req2 {\n", + " id: 1.1\n", + " text: the second test text.\n", + " risk: low\n", + " verifymethod: inspection\n", + " }\n", + "\n", + " performanceRequirement test_req3 {\n", + " id: 1.2\n", + " text: the third test text.\n", + " risk: medium\n", + " verifymethod: demonstration\n", + " }\n", + "\n", + " interfaceRequirement test_req4 {\n", + " id: 1.2.1\n", + " text: the fourth test text.\n", + " risk: medium\n", + " verifymethod: analysis\n", + " }\n", + "\n", + " physicalRequirement test_req5 {\n", + " id: 1.2.2\n", + " text: the fifth test text.\n", + " risk: medium\n", + " verifymethod: analysis\n", + " }\n", + "\n", + " designConstraint test_req6 {\n", + " id: 1.2.3\n", + " text: the sixth test text.\n", + " risk: medium\n", + " verifymethod: analysis\n", + " }\n", + "\n", + " element test_entity {\n", + " type: simulation\n", + " }\n", + "\n", + " element test_entity2 {\n", + " type: word doc\n", + " docRef: reqs/test_entity\n", + " }\n", + "\n", + " element test_entity3 {\n", + " type: \"test suite\"\n", + " docRef: github.com/all_the_tests\n", + " }\n", + "\n", + "\n", + " test_entity - satisfies -> test_req2\n", + " test_req - traces -> test_req2\n", + " test_req - contains -> test_req3\n", + " test_req3 - contains -> test_req4\n", + " test_req4 - derives -> test_req5\n", + " test_req5 - refines -> test_req6\n", + " test_entity3 - verifies -> test_req5\n", + " test_req <- copies - test_entity2\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## c4\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/c4.html)\n", + "\n", + "```mermaid\n", + " C4Context\n", + " title System Context diagram for Internet Banking System\n", + " Enterprise_Boundary(b0, \"BankBoundary0\") {\n", + " Person(customerA, \"Banking Customer A\", \"A customer of the bank, with personal bank accounts.\")\n", + " Person(customerB, \"Banking Customer B\")\n", + " Person_Ext(customerC, \"Banking Customer C\", \"desc\")\n", + "\n", + " Person(customerD, \"Banking Customer D\", \"A customer of the bank,
with personal bank accounts.\")\n", + "\n", + " System(SystemAA, \"Internet Banking System\", \"Allows customers to view information about their bank accounts, and make payments.\")\n", + "\n", + " Enterprise_Boundary(b1, \"BankBoundary\") {\n", + "\n", + " SystemDb_Ext(SystemE, \"Mainframe Banking System\", \"Stores all of the core banking information about customers, accounts, transactions, etc.\")\n", + "\n", + " System_Boundary(b2, \"BankBoundary2\") {\n", + " System(SystemA, \"Banking System A\")\n", + " System(SystemB, \"Banking System B\", \"A system of the bank, with personal bank accounts. next line.\")\n", + " }\n", + "\n", + " System_Ext(SystemC, \"E-mail system\", \"The internal Microsoft Exchange e-mail system.\")\n", + " SystemDb(SystemD, \"Banking System D Database\", \"A system of the bank, with personal bank accounts.\")\n", + "\n", + " Boundary(b3, \"BankBoundary3\", \"boundary\") {\n", + " SystemQueue(SystemF, \"Banking System F Queue\", \"A system of the bank.\")\n", + " SystemQueue_Ext(SystemG, \"Banking System G Queue\", \"A system of the bank, with personal bank accounts.\")\n", + " }\n", + " }\n", + " }\n", + "\n", + " BiRel(customerA, SystemAA, \"Uses\")\n", + " BiRel(SystemAA, SystemE, \"Uses\")\n", + " Rel(SystemAA, SystemC, \"Sends e-mails\", \"SMTP\")\n", + " Rel(SystemC, customerA, \"Sends e-mails to\")\n", + "\n", + " UpdateElementStyle(customerA, $fontColor=\"red\", $bgColor=\"grey\", $borderColor=\"red\")\n", + " UpdateRelStyle(customerA, SystemAA, $textColor=\"blue\", $lineColor=\"blue\", $offsetX=\"5\")\n", + " UpdateRelStyle(SystemAA, SystemE, $textColor=\"blue\", $lineColor=\"blue\", $offsetY=\"-10\")\n", + " UpdateRelStyle(SystemAA, SystemC, $textColor=\"blue\", $lineColor=\"blue\", $offsetY=\"-40\", $offsetX=\"-50\")\n", + " UpdateRelStyle(SystemC, customerA, $textColor=\"red\", $lineColor=\"red\", $offsetX=\"-50\", $offsetY=\"20\")\n", + "\n", + " UpdateLayoutConfig($c4ShapeInRow=\"3\", $c4BoundaryInRow=\"1\")\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## mindmap\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/mindmap.html)\n", + ">\n", + "> note: icons will not be available\n", + "\n", + "```mermaid\n", + "mindmap\n", + " root((mindmap))\n", + " Origins\n", + " Long history\n", + " ::icon(fa fa-book)\n", + " Popularisation\n", + " British popular psychology author Tony Buzan\n", + " Research\n", + " On effectiveness
and features\n", + " On Automatic creation\n", + " Uses\n", + " Creative techniques\n", + " Strategic planning\n", + " Argument mapping\n", + " Tools\n", + " Pen and paper\n", + " Mermaid\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## timeline\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/timeline.html)\n", + "\n", + "```mermaid\n", + "timeline\n", + " title England's History Timeline\n", + " section Stone Age\n", + " 7600 BC : Britain's oldest known house was built in Orkney, Scotland\n", + " 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers.\n", + " section Bronze Age\n", + " 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking.\n", + " : New styles of pottery and ways of burying the dead appear.\n", + " 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles.\n", + " : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## sankey\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/sankey.html)\n", + "\n", + "```mermaid\n", + "---\n", + "config:\n", + " sankey:\n", + " showValues: false\n", + "---\n", + "sankey-beta\n", + "\n", + "Agricultural 'waste',Bio-conversion,124.729\n", + "Bio-conversion,Liquid,0.597\n", + "Bio-conversion,Losses,26.862\n", + "Bio-conversion,Solid,280.322\n", + "Bio-conversion,Gas,81.144\n", + "Biofuel imports,Liquid,35\n", + "Biomass imports,Solid,35\n", + "Coal imports,Coal,11.606\n", + "Coal reserves,Coal,63.965\n", + "Coal,Solid,75.571\n", + "District heating,Industry,10.639\n", + "District heating,Heating and cooling - commercial,22.505\n", + "District heating,Heating and cooling - homes,46.184\n", + "Electricity grid,Over generation / exports,104.453\n", + "Electricity grid,Heating and cooling - homes,113.726\n", + "Electricity grid,H2 conversion,27.14\n", + "Electricity grid,Industry,342.165\n", + "Electricity grid,Road transport,37.797\n", + "Electricity grid,Agriculture,4.412\n", + "Electricity grid,Heating and cooling - commercial,40.858\n", + "Electricity grid,Losses,56.691\n", + "Electricity grid,Rail transport,7.863\n", + "Electricity grid,Lighting & appliances - commercial,90.008\n", + "Electricity grid,Lighting & appliances - homes,93.494\n", + "Gas imports,Ngas,40.719\n", + "Gas reserves,Ngas,82.233\n", + "Gas,Heating and cooling - commercial,0.129\n", + "Gas,Losses,1.401\n", + "Gas,Thermal generation,151.891\n", + "Gas,Agriculture,2.096\n", + "Gas,Industry,48.58\n", + "Geothermal,Electricity grid,7.013\n", + "H2 conversion,H2,20.897\n", + "H2 conversion,Losses,6.242\n", + "H2,Road transport,20.897\n", + "Hydro,Electricity grid,6.995\n", + "Liquid,Industry,121.066\n", + "Liquid,International shipping,128.69\n", + "Liquid,Road transport,135.835\n", + "Liquid,Domestic aviation,14.458\n", + "Liquid,International aviation,206.267\n", + "Liquid,Agriculture,3.64\n", + "Liquid,National navigation,33.218\n", + "Liquid,Rail transport,4.413\n", + "Marine algae,Bio-conversion,4.375\n", + "Ngas,Gas,122.952\n", + "Nuclear,Thermal generation,839.978\n", + "Oil imports,Oil,504.287\n", + "Oil reserves,Oil,107.703\n", + "Oil,Liquid,611.99\n", + "Other waste,Solid,56.587\n", + "Other waste,Bio-conversion,77.81\n", + "Pumped heat,Heating and cooling - homes,193.026\n", + "Pumped heat,Heating and cooling - commercial,70.672\n", + "Solar PV,Electricity grid,59.901\n", + "Solar Thermal,Heating and cooling - homes,19.263\n", + "Solar,Solar Thermal,19.263\n", + "Solar,Solar PV,59.901\n", + "Solid,Agriculture,0.882\n", + "Solid,Thermal generation,400.12\n", + "Solid,Industry,46.477\n", + "Thermal generation,Electricity grid,525.531\n", + "Thermal generation,Losses,787.129\n", + "Thermal generation,District heating,79.329\n", + "Tidal,Electricity grid,9.452\n", + "UK land based bioenergy,Bio-conversion,182.01\n", + "Wave,Electricity grid,19.013\n", + "Wind,Electricity grid,289.366\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## xy\n", + "\n", + "> [docs](https://mermaid.js.org/syntax/xyChart.html)\n", + "\n", + "```mermaid\n", + "xychart-beta\n", + " title \"Sales Revenue\"\n", + " x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]\n", + " y-axis \"Revenue (in $)\" 4000 --> 11000\n", + " bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n", + " line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n", + "```" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.1" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/packages/mermaid/src/manager.ts b/packages/mermaid/src/manager.ts index 22b3709b3f5d..d3d2081edcf1 100644 --- a/packages/mermaid/src/manager.ts +++ b/packages/mermaid/src/manager.ts @@ -37,6 +37,13 @@ export class MermaidManager implements IMermaidManager { } } + /** + * Post-process to ensure mermaid diagrams contain only valid SVG and XHTML. + */ + static cleanMermaidSvg(svg: string): string { + return svg.replace(Private.RE_VOID_ELEMENT, Private.replaceVoidElement); + } + /** * Handle (re)-initializing mermaid based on external values. */ @@ -81,7 +88,9 @@ export class MermaidManager implements IMermaidManager { const el = document.createElement('div'); document.body.appendChild(el); try { - const { svg } = await _mermaid.render(id, text, el); + let { svg } = await _mermaid.render(id, text, el); + svg = MermaidManager.cleanMermaidSvg(svg); + const parser = new DOMParser(); const doc = parser.parseFromString(svg, 'image/svg+xml'); @@ -285,6 +294,7 @@ namespace Private { _mermaid.mermaidAPI.initialize({ theme, fontFamily, + securityLevel: 'strict', maxTextSize: 100000, maxEdges: 100000, startOnLoad: false @@ -323,4 +333,27 @@ namespace Private { _loading.resolve(_mermaid); return _mermaid; } + + /** + * A regular expression for all void elements, which may include attributes and + * a slash. + * + * @see https://developer.mozilla.org/en-US/docs/Glossary/Void_element + * + * Of these, only `
` is generated by Mermaid in place of `\n`, + * but _any_ "malformed" tag will break the SVG rendering entirely. + */ + export const RE_VOID_ELEMENT = + /<\s*(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\s*([^>]*?)\s*>/gi; + + /** + * Ensure a void element is closed with a slash, preserving any attributes. + */ + export function replaceVoidElement(match: string, tag: string, rest: string) { + rest = rest.trim(); + if (!rest.endsWith('/')) { + rest = `${rest} /`; + } + return `<${tag} ${rest}>`; + } } diff --git a/packages/mermaid/test/clean.spec.ts b/packages/mermaid/test/clean.spec.ts new file mode 100644 index 000000000000..e5dedbc1d3d2 --- /dev/null +++ b/packages/mermaid/test/clean.spec.ts @@ -0,0 +1,36 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { MermaidManager } from '@jupyterlab/mermaid'; + +import { + makeElementVariants, + SVG_MIME, + SVG_XHTML_FOOTER, + SVG_XHTML_HEADER, + VOID_ELEMENTS +} from './utils'; + +const parser = new DOMParser(); + +describe('@jupyterlab/mermaid', () => { + for (const element of VOID_ELEMENTS) { + it(`should clean void element ${element}`, () => { + const variants = makeElementVariants(element); + const raw = [...SVG_XHTML_HEADER, ...variants, ...SVG_XHTML_FOOTER].join( + '\n' + ); + + const clean = MermaidManager.cleanMermaidSvg(raw); + const parsed = parser.parseFromString(clean, SVG_MIME); + const { numberValue } = parsed.evaluate( + `count(//${element})`, + parsed, + null, + XPathResult.NUMBER_TYPE, + null + ); + expect(numberValue).toBe(variants.length); + }); + } +}); diff --git a/packages/mermaid/test/renderer.spec.ts b/packages/mermaid/test/renderer.spec.ts index c953336139f5..a01d87aa0b3b 100644 --- a/packages/mermaid/test/renderer.spec.ts +++ b/packages/mermaid/test/renderer.spec.ts @@ -12,7 +12,7 @@ import { SVG_DATA_ATTR } from './utils'; -describe('@jupyterlab/mermaid-extension', () => { +describe('@jupyterlab/mermaid', () => { describe('RenderedMermaid', () => { it('should attach an SVG and version if parsing succeeds', async () => { const model = new MimeModel({ diff --git a/packages/mermaid/test/utils.ts b/packages/mermaid/test/utils.ts index 24581f591ed4..1054cd574174 100644 --- a/packages/mermaid/test/utils.ts +++ b/packages/mermaid/test/utils.ts @@ -23,9 +23,35 @@ flowchart LR ${FAIL} --> `; -export const SVG_DATA_ATTR = 'data.image/svg+xml'; +export const SVG_MIME = 'image/svg+xml'; +export const SVG_DATA_ATTR = `data.${SVG_MIME}`; export const MERMAID_VERSION_ATTR = ['metadata', 'text/vnd.mermaid', 'version']; +export const VOID_ELEMENTS = [ + 'area', + 'base', + 'br', + 'col', + 'embed', + 'hr', + 'img', + 'input', + 'link', + 'meta', + 'param', + 'source', + 'track', + 'wbr' +]; + +export const SVG_XHTML_HEADER = [ + '', + '', + '
' +]; + +export const SVG_XHTML_FOOTER = ['
', '
', '
']; + /** * A mock mermaid that provides the minimal renderer API. * @@ -89,3 +115,20 @@ export const MERMAID_RENDERER = new RenderedMermaid({ sanitize: (s: string) => s } }); + +export function makeElementVariants(element: string) { + return [ + `<${element}>`, + `<${element}>`, + `<${element} >`, + `<${element} />`, + `<${element} / >`, + `< ${element} / >`, + `<${element} id="a" >`, + `<${element} id="b" >`, + `<${element} id="c"/>`, + `<${element} id="d" />`, + `<${element} id="e" / >`, + `< ${element} id="f" / >` + ]; +}