diff --git a/.changeset/rude-meals-invite.md b/.changeset/rude-meals-invite.md new file mode 100644 index 000000000..8d43692f8 --- /dev/null +++ b/.changeset/rude-meals-invite.md @@ -0,0 +1,5 @@ +--- +'mermaid': minor +--- + +New Flowchart Shapes (with new syntax) diff --git a/.changeset/slow-goats-act.md b/.changeset/slow-goats-act.md new file mode 100644 index 000000000..a21565ec3 --- /dev/null +++ b/.changeset/slow-goats-act.md @@ -0,0 +1,5 @@ +--- +'@mermaid-js/layout-elk': patch +--- + +chore: fix render types diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml new file mode 100644 index 000000000..283c316fb --- /dev/null +++ b/.github/workflows/release-preview.yml @@ -0,0 +1,42 @@ +name: Preview release + +on: + pull_request: + branches: [develop] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }} + cancel-in-progress: true + +permissions: + contents: read + actions: write + +jobs: + preview: + if: ${{ github.repository_owner == 'mermaid-js' }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + issues: write + pull-requests: write + name: Publish preview release + timeout-minutes: 5 + steps: + - name: Checkout Repo + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + + - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + + - name: Setup Node.js + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + with: + cache: pnpm + node-version-file: '.node-version' + + - name: Install Packages + run: pnpm install --frozen-lockfile + + - name: Publish packages + run: pnpx pkg-pr-new publish --pnpm './packages/*' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 375d5fada..026ca0fb7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,6 +38,10 @@ jobs: run: | pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage + - name: Verify out-of-tree build with TypeScript + run: | + pnpm test:check:tsc + - name: Upload Coverage to Codecov uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 # Run step only pushes to develop and pull_requests diff --git a/Dockerfile b/Dockerfile index fa933f999..7be53d24e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,9 @@ USER 0:0 RUN corepack enable \ && corepack enable pnpm +RUN apk add --no-cache git~=2.43.4 \ + && git config --add --system safe.directory /mermaid + ENV NODE_OPTIONS="--max_old_space_size=8192" EXPOSE 9000 3333 diff --git a/cypress/integration/rendering/flowchart-shape-alias.spec.ts b/cypress/integration/rendering/flowchart-shape-alias.spec.ts new file mode 100644 index 000000000..3e6d9092d --- /dev/null +++ b/cypress/integration/rendering/flowchart-shape-alias.spec.ts @@ -0,0 +1,134 @@ +import { imgSnapshotTest } from '../../helpers/util.ts'; + +const aliasSet1 = ['process', 'rect', 'proc', 'rectangle'] as const; + +const aliasSet2 = ['event', 'rounded'] as const; + +const aliasSet3 = ['stadium', 'pill', 'term'] as const; + +const aliasSet4 = ['fr', 'subproc', 'framed-rectangle', 'subroutine'] as const; + +const aliasSet5 = ['db', 'cylinder', 'cyl'] as const; + +const aliasSet6 = ['diam', 'decision', 'diamond'] as const; + +const aliasSet7 = ['hex', 'hexagon', 'prepare'] as const; + +const aliasSet8 = ['l-r', 'lean-right', 'in-out'] as const; + +const aliasSet9 = ['l-l', 'lean-left', 'out-in'] as const; + +const aliasSet10 = ['trap-b', 'trapezoid-bottom', 'priority', 'trapezoid'] as const; + +const aliasSet11 = ['trap-t', 'trapezoid-top', 'manual', 'inv-trapezoid'] as const; + +const aliasSet12 = ['dc', 'double-circle'] as const; + +const aliasSet13 = ['notched-rect', 'card', 'notch-rect'] as const; + +const aliasSet14 = ['lined-rect', 'lined-proc', 'shaded-proc'] as const; + +const aliasSet15 = ['sm-circ', 'small-circle', 'start'] as const; + +const aliasSet16 = ['framed-circle', 'stop'] as const; + +const aliasSet17 = ['fork', 'join', 'long-rect'] as const; + +const aliasSet18 = ['brace', 'comment', 'brace-l'] as const; + +const aliasSet19 = ['bolt', 'com-link', 'lightning-bolt'] as const; + +const aliasSet20 = ['we-rect', 'doc', 'wave-edge-rect', 'wave-edged-rectangle'] as const; + +const aliasSet21 = ['delay', 'half-rounded-rect'] as const; + +const aliasSet22 = ['t-cyl', 'das', 'tilted-cylinder'] as const; + +const aliasSet23 = ['l-cyl', 'disk', 'lined-cylinder'] as const; + +const aliasSet24 = ['cur-trap', 'disp', 'display', 'curved-trapezoid'] as const; + +const aliasSet25 = ['div-rect', 'div-proc', 'divided-rectangle'] as const; + +const aliasSet26 = ['sm-tri', 'extract', 'small-triangle', 'triangle'] as const; + +const aliasSet27 = ['win-pane', 'internal-storage', 'window-pane'] as const; + +const aliasSet28 = ['fc', 'junction', 'filled-circle'] as const; + +const aliasSet29 = ['lin-we-rect', 'lin-doc', 'lined-wave-edged-rect'] as const; + +const aliasSet30 = ['notch-pent', 'loop-limit', 'notched-pentagon'] as const; + +const aliasSet31 = ['flip-tri', 'manual-file', 'flipped-triangle'] as const; + +const aliasSet32 = ['sloped-rect', 'manual-input', 'sloped-rectangle'] as const; + +const aliasSet33 = ['mul-we-rect', 'mul-doc', 'multi-wave-edged-rectangle'] as const; + +const aliasSet34 = ['mul-rect', 'mul-proc', 'multi-rect'] as const; + +const aliasSet35 = ['flag', 'paper-tape'] as const; + +const aliasSet36 = ['bt-rect', 'stored-data', 'bow-tie-rect'] as const; + +const aliasSet37 = ['cross-circle', 'summary', 'crossed-circle'] as const; + +const aliasSet38 = ['tag-we-rect', 'tag-doc', 'tagged-wave-edged-rectangle'] as const; + +const aliasSet39 = ['tag-rect', 'tag-proc', 'tagged-rect'] as const; + +// Aggregate all alias sets into a single array +const aliasSets = [ + aliasSet1, + aliasSet2, + aliasSet3, + aliasSet4, + aliasSet5, + aliasSet6, + aliasSet7, + aliasSet8, + aliasSet9, + aliasSet10, + aliasSet11, + aliasSet12, + aliasSet13, + aliasSet14, + aliasSet15, + aliasSet16, + aliasSet17, + aliasSet18, + aliasSet19, + aliasSet20, + aliasSet21, + aliasSet22, + aliasSet23, + aliasSet24, + aliasSet25, + aliasSet26, + aliasSet27, + aliasSet28, + aliasSet29, + aliasSet30, + aliasSet31, + aliasSet32, + aliasSet33, + aliasSet34, + aliasSet35, + aliasSet36, + aliasSet37, + aliasSet38, + aliasSet39, +] as const; + +aliasSets.forEach((aliasSet) => { + describe(`Test ${aliasSet.join(',')} `, () => { + it(`All ${aliasSet.join(',')} should render same shape`, () => { + let flowchartCode = `flowchart \n`; + aliasSet.forEach((alias, index) => { + flowchartCode += ` n${index}@{ shape: ${alias}, label: "${alias}" }@\n`; + }); + imgSnapshotTest(flowchartCode); + }); + }); +}); diff --git a/cypress/integration/rendering/iconShape.spec.ts b/cypress/integration/rendering/iconShape.spec.ts new file mode 100644 index 000000000..9a01932fb --- /dev/null +++ b/cypress/integration/rendering/iconShape.spec.ts @@ -0,0 +1,121 @@ +import { imgSnapshotTest } from '../../helpers/util'; + +const looks = ['classic', 'handDrawn'] as const; +const directions = ['TB', 'BT', 'LR', 'RL'] as const; +const forms = [undefined, 'square', 'circle', 'rounded'] as const; +const labelPos = [undefined, 't', 'b'] as const; + +looks.forEach((look) => { + directions.forEach((direction) => { + forms.forEach((form) => { + labelPos.forEach((pos) => { + describe(`Test iconShape in ${form ? `${form} form,` : ''} ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => { + it(`without label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a label for icon shape'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with very long label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is a very very very very very long long long label for icon shape'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:true`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** and strong for icon shape'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:false`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'This is **bold** and strong for icon shape'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { + look, + htmlLabels: false, + flowchart: { htmlLabels: false }, + }); + }); + + it(`with styles`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with classDef`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n`; + flowchartCode += ` nA --> nAA@{ icon: 'fa:bell', label: 'new icon shape'`; + if (form) { + flowchartCode += `, form: '${form}'`; + } + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + flowchartCode += ` nAA:::customClazz\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + }); + }); + }); + }); +}); + +describe('Test iconShape with different h', () => { + it('with different h', () => { + let flowchartCode = `flowchart TB\n`; + const icon = 'fa:bell'; + const iconHeight = 64; + flowchartCode += ` nA --> nAA@{ icon: '${icon}', label: 'icon with different h', h: ${iconHeight} }@\n`; + imgSnapshotTest(flowchartCode); + }); +}); diff --git a/cypress/integration/rendering/imageShape.spec.ts b/cypress/integration/rendering/imageShape.spec.ts new file mode 100644 index 000000000..228ca5d4e --- /dev/null +++ b/cypress/integration/rendering/imageShape.spec.ts @@ -0,0 +1,98 @@ +import { imgSnapshotTest } from '../../helpers/util'; + +const looks = ['classic', 'handDrawn'] as const; +const directions = ['TB', 'LR'] as const; +const labelPos = [undefined, 't', 'b'] as const; + +looks.forEach((look) => { + directions.forEach((direction) => { + labelPos.forEach((pos) => { + describe(`Test imageShape in ${look} look and dir ${direction} with label position ${pos ? pos : 'not defined'}`, () => { + it(`without label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', w: '100', h: '100' }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a label for image shape'`; + + flowchartCode += `, w: '100', h: '200'`; + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with very long label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is a very very very very very long long long label for image shape'`; + + flowchartCode += `, w: '100', h: '250'`; + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:true`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** and strong for image shape'`; + + flowchartCode += `, w: '550', h: '200'`; + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { look, htmlLabels: true }); + }); + + it(`with markdown htmlLabels:false`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'This is **bold** and strong for image shape'`; + flowchartCode += `, w: '250', h: '200'`; + + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + imgSnapshotTest(flowchartCode, { + look, + htmlLabels: false, + flowchart: { htmlLabels: false }, + }); + }); + + it(`with styles`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`; + flowchartCode += `, w: '550', h: '200'`; + + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + flowchartCode += ` style A fill:#f9f,stroke:#333,stroke-width:4px \n`; + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with classDef`, () => { + let flowchartCode = `flowchart ${direction}\n`; + flowchartCode += ` classDef customClazz fill:#bbf,stroke:#f66,stroke-width:2px,color:#000000,stroke-dasharray: 5 5\n`; + flowchartCode += ` nA --> A@{ img: 'https://cdn.pixabay.com/photo/2020/02/22/18/49/paper-4871356_1280.jpg', label: 'new image shape'`; + + flowchartCode += `, w: '500', h: '550'`; + if (pos) { + flowchartCode += `, pos: '${pos}'`; + } + flowchartCode += ` }@\n`; + flowchartCode += ` A:::customClazz\n`; + imgSnapshotTest(flowchartCode, { look }); + }); + }); + }); + }); +}); diff --git a/cypress/integration/rendering/newShapes.spec.ts b/cypress/integration/rendering/newShapes.spec.ts index 4e382f829..906a04da3 100644 --- a/cypress/integration/rendering/newShapes.spec.ts +++ b/cypress/integration/rendering/newShapes.spec.ts @@ -4,45 +4,53 @@ const looks = ['classic'] as const; const directions = ['TB'] as const; const newShapesSet1 = [ 'triangle', - 'slopedRect', - 'tiltedCylinder', - 'flippedTriangle', + 'sloped-rect', + 'tilted-cylinder', + 'flipped-triangle', 'hourglass', ] as const; const newShapesSet2 = [ - 'taggedRect', - 'multiRect', - 'lightningBolt', - 'filledCircle', - 'windowPane', + 'tagged-rect', + 'multi-rect', + 'lightning-bolt', + 'filled-circle', + 'window-pane', ] as const; const newShapesSet3 = [ - 'curvedTrapezoid', - 'bowTieRect', - 'waveEdgedRectangle', - 'dividedRectangle', - 'crossedCircle', + 'curved-trapezoid', + 'bow-tie-rect', + 'wave-edge-rect', + 'divided-rectangle', + 'crossed-circle', ] as const; const newShapesSet4 = [ - 'waveRectangle', - 'trapezoidalPentagon', - 'linedCylinder', - 'multiWaveEdgedRectangle', - 'halfRoundedRectangle', + 'wave-rectangle', + 'notched-pentagon', + 'lined-cylinder', + 'multi-wave-edged-rectangle', + 'half-rounded-rect', ] as const; const newShapesSet5 = [ - 'linedWaveEdgedRect', - 'taggedWaveEdgedRectangle', - 'curlyBraceLeft', - 'curvedTrapezoid', - 'waveRectangle', + 'lined-wave-edged-rect', + 'tagged-wave-edged-rectangle', + 'brace-l', + 'curved-trapezoid', + 'wave-rectangle', ] as const; +const newShapesSet6 = ['brace-r', 'braces'] as const; // Aggregate all shape sets into a single array -const newShapesSets = [newShapesSet2] as const; +const newShapesSets = [ + newShapesSet1, + newShapesSet2, + newShapesSet3, + newShapesSet4, + newShapesSet5, + newShapesSet6, +]; looks.forEach((look) => { directions.forEach((direction) => { diff --git a/cypress/platform/saurabh.html b/cypress/platform/saurabh.html index aaa2bbc71..76c635084 100644 --- a/cypress/platform/saurabh.html +++ b/cypress/platform/saurabh.html @@ -1,54 +1,40 @@ -
- - - - - - - - - - - - + } + + - --flowchart -A - B@{ shape: multiRect, label: "title aduwab whgdawhbd wajhdbawj" }@ - F@{ shape: multiRect, label: "title " }@ - G@{ shape: multiRect, label: "title \n duawd \n duawd \n duawd \n duawd" }@ - C - D - E - C -->B - B --> D - B --> E - F --> A - A --> F -+ +
+ flowchart TD + B2@{ icon: "fa:bell", form: "square", label: "B2 agsyua duadu", pos: "t", h: 80 }@ + - - - + W --> B2 + X --> B2 + Y --> B2 + Z --> B2 + B2 --sas--> C + + ++
+ flowchart TB + A --test2--> B2@{ icon: "fa:bell", form: "rounded", label: "B2 aiduaid uyawduad uaduabd uyduadb", pos: "b" }@ + B2 --test--> C + D --> B2 --> E + style B2 fill:#f9f,stroke:#333,stroke-width:4px ++
+ flowchart BT + A --test2--> B2@{ icon: "fa:bell", form: "square", label: "B2", pos: "t", h: 40, w: 30 }@ + B2 --test--> C + D --> B2 --> E ++
+ flowchart BT + A --test2--> B2@{ icon: "fa:bell", label: "B2 awiugdawu uydgayuiwd wuydguy", pos: "b", h: 40, w: 30 }@ + B2 --test--> C ++
+ flowchart BT + A --test2--> B2@{ icon: "fa:bell", label: "B2 dawuygd ayuwgd uy", pos: "t", h: 40, w: 30 }@ + B2 --test--> C ++
+ flowchart TB + A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "t", w: 200, h: 100 }@ --> C ++
+ flowchart TB + A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "b", w: 200, h: 100 }@ --> C + D --> B2 --> E ++ +