diff --git a/.build/common.ts b/.build/common.ts index e2190974f..86ccd15d1 100644 --- a/.build/common.ts +++ b/.build/common.ts @@ -1,3 +1,9 @@ +export interface PackageOptions { + name: string; + packageName: string; + file: string; +} + /** * Shared common options for both ESBuild and Vite */ @@ -27,4 +33,4 @@ export const packageOptions = { packageName: 'mermaid-layout-elk', file: 'layouts.ts', }, -} as const; +} as const satisfies Record; diff --git a/.changeset/add-nested-namespaces.md b/.changeset/add-nested-namespaces.md deleted file mode 100644 index 39097c49b..000000000 --- a/.changeset/add-nested-namespaces.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@mermaid': patch ---- - -Fixed an issue when the mermaid classdiagram crashes when adding a . to the namespace. -Forexample - -```mermaid - -classDiagram - namespace Company.Project.Module { - class GenericClass~T~ { - +addItem(item: T) - +getItem() T - } - } -``` diff --git a/.changeset/chilly-hotels-mix.md b/.changeset/chilly-hotels-mix.md new file mode 100644 index 000000000..642ab6ecf --- /dev/null +++ b/.changeset/chilly-hotels-mix.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Jagged edge fix for icon shape diff --git a/.changeset/dry-plums-glow.md b/.changeset/dry-plums-glow.md deleted file mode 100644 index e5b95dc3c..000000000 --- a/.changeset/dry-plums-glow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -Fix for issue with calculation of label width when using in firefox diff --git a/.changeset/dry-students-act.md b/.changeset/dry-students-act.md new file mode 100644 index 000000000..43f439f2e --- /dev/null +++ b/.changeset/dry-students-act.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +Add missing TypeScript dependencies diff --git a/.changeset/heavy-cats-mate.md b/.changeset/heavy-cats-mate.md new file mode 100644 index 000000000..c903cc47e --- /dev/null +++ b/.changeset/heavy-cats-mate.md @@ -0,0 +1,5 @@ +--- +'mermaid': patch +--- + +fix: Icon color fix for colored icons. diff --git a/.changeset/witty-rabbits-hunt.md b/.changeset/witty-rabbits-hunt.md deleted file mode 100644 index 3817bb928..000000000 --- a/.changeset/witty-rabbits-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'mermaid': patch ---- - -Ban DOMPurify v3.1.7 as a dependency diff --git a/.cspell/code-terms.txt b/.cspell/code-terms.txt index 8e4c02261..f4862006f 100644 --- a/.cspell/code-terms.txt +++ b/.cspell/code-terms.txt @@ -26,6 +26,7 @@ concat controlx controly CSSCLASS +curv CYLINDEREND CYLINDERSTART DAGA diff --git a/.cspell/cspell.config.yaml b/.cspell/cspell.config.yaml index 33d690193..b16040c8c 100644 --- a/.cspell/cspell.config.yaml +++ b/.cspell/cspell.config.yaml @@ -28,6 +28,9 @@ dictionaryDefinitions: - name: suggestions words: - none + - disp + - subproc + - tria suggestWords: - seperator:separator - vertice:vertex diff --git a/.cspell/mermaid-terms.txt b/.cspell/mermaid-terms.txt index 59a3d108f..8551bd196 100644 --- a/.cspell/mermaid-terms.txt +++ b/.cspell/mermaid-terms.txt @@ -5,6 +5,7 @@ bmatrix braintree catmull compositTitleSize +curv doublecircle elems gantt @@ -24,6 +25,7 @@ multigraph nodesep NOTEGROUP Pinterest +procs rankdir ranksep rect diff --git a/.esbuild/build.ts b/.esbuild/build.ts index 2bb42a557..423e8f047 100644 --- a/.esbuild/build.ts +++ b/.esbuild/build.ts @@ -8,7 +8,10 @@ import { defaultOptions, getBuildConfig } from './util.js'; const shouldVisualize = process.argv.includes('--visualize'); const buildPackage = async (entryName: keyof typeof packageOptions) => { - const commonOptions: MermaidBuildOptions = { ...defaultOptions, entryName } as const; + const commonOptions: MermaidBuildOptions = { + ...defaultOptions, + options: packageOptions[entryName], + } as const; const buildConfigs: MermaidBuildOptions[] = [ // package.mjs { ...commonOptions }, @@ -40,7 +43,7 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => { continue; } const fileName = Object.keys(metafile.outputs) - .find((file) => !file.includes('chunks') && file.endsWith('js')) + .find((file) => !file.includes('chunks') && file.endsWith('js'))! .replace('dist/', ''); // Upload metafile into https://esbuild.github.io/analyze/ await writeFile(`stats/${fileName}.meta.json`, JSON.stringify(metafile)); diff --git a/.esbuild/server.ts b/.esbuild/server.ts index ef61ebec2..6e1bcb460 100644 --- a/.esbuild/server.ts +++ b/.esbuild/server.ts @@ -9,13 +9,18 @@ import { generateLangium } from '../.build/generateLangium.js'; import { defaultOptions, getBuildConfig } from './util.js'; const configs = Object.values(packageOptions).map(({ packageName }) => - getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName }) + getBuildConfig({ + ...defaultOptions, + minify: false, + core: false, + options: packageOptions[packageName], + }) ); const mermaidIIFEConfig = getBuildConfig({ ...defaultOptions, minify: false, core: false, - entryName: 'mermaid', + options: packageOptions.mermaid, format: 'iife', }); configs.push(mermaidIIFEConfig); diff --git a/.esbuild/util.ts b/.esbuild/util.ts index 522176113..6d6d1d59b 100644 --- a/.esbuild/util.ts +++ b/.esbuild/util.ts @@ -3,7 +3,7 @@ import { fileURLToPath } from 'url'; import type { BuildOptions } from 'esbuild'; import { readFileSync } from 'fs'; import jsonSchemaPlugin from './jsonSchemaPlugin.js'; -import { packageOptions } from '../.build/common.js'; +import type { PackageOptions } from '../.build/common.js'; import { jisonPlugin } from './jisonPlugin.js'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -13,10 +13,10 @@ export interface MermaidBuildOptions extends BuildOptions { core: boolean; metafile: boolean; format: 'esm' | 'iife'; - entryName: keyof typeof packageOptions; + options: PackageOptions; } -export const defaultOptions: Omit = { +export const defaultOptions: Omit = { minify: false, metafile: false, core: false, @@ -52,9 +52,14 @@ const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOpt }; export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => { - const { core, entryName, metafile, format, minify } = options; + const { + core, + metafile, + format, + minify, + options: { name, file, packageName }, + } = options; const external: string[] = ['require', 'fs', 'path']; - const { name, file, packageName } = packageOptions[entryName]; const outFileName = getFileName(name, options); const output: BuildOptions = buildOptions({ absWorkingDir: resolve(__dirname, `../packages/${packageName}`), diff --git a/.github/lychee.toml b/.github/lychee.toml index 2e3b08c41..288ab054a 100644 --- a/.github/lychee.toml +++ b/.github/lychee.toml @@ -44,7 +44,10 @@ exclude = [ "https://chromewebstore.google.com", # Drupal 403 -"https://(www.)?drupal.org" +"https://(www.)?drupal.org", + +# Swimm returns 404, eventhough the link is valid +"https://docs.swimm.io" ] # Exclude all private IPs from checking. diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml index 6a43791ed..13b913c11 100644 --- a/.github/workflows/autofix.yml +++ b/.github/workflows/autofix.yml @@ -19,7 +19,7 @@ jobs: # uses version from "packageManager" field in package.json - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index eb0c4594a..79e9deea1 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -23,7 +23,7 @@ jobs: - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 65962ce64..94ede60ab 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,7 +36,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 with: config-file: ./.github/codeql/codeql-config.yml languages: ${{ matrix.language }} @@ -48,7 +48,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/autobuild@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -62,4 +62,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5 + uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 diff --git a/.github/workflows/e2e-applitools.yml b/.github/workflows/e2e-applitools.yml index 6da65afe5..b1eb70674 100644 --- a/.github/workflows/e2e-applitools.yml +++ b/.github/workflows/e2e-applitools.yml @@ -38,7 +38,7 @@ jobs: # uses version from "packageManager" field in package.json - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.node-version' diff --git a/.github/workflows/e2e-timings.yml b/.github/workflows/e2e-timings.yml new file mode 100644 index 000000000..e3f724d81 --- /dev/null +++ b/.github/workflows/e2e-timings.yml @@ -0,0 +1,53 @@ +name: E2E - Generate Timings + +on: + # run this workflow every night at 3am + schedule: + - cron: '28 3 * * *' + # or when the user triggers it from GitHub Actions page + workflow_dispatch: + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +permissions: + contents: write + +jobs: + timings: + runs-on: ubuntu-latest + container: + image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1 + options: --user 1001 + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 + - name: Setup Node.js + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 + with: + node-version-file: '.node-version' + - name: Install dependencies + uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 + with: + runTests: false + - name: Cypress run + uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 + id: cypress + with: + install: false + start: pnpm run dev:coverage + wait-on: 'http://localhost:9000' + browser: chrome + publish-summary: false + env: + VITEST_COVERAGE: true + CYPRESS_COMMIT: ${{ github.sha }} + SPLIT: 1 + SPLIT_INDEX: 0 + SPLIT_FILE: 'cypress/timings.json' + - name: Commit changes + uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4 + with: + add: 'cypress/timings.json' + author_name: 'github-actions[bot]' + author_email: '41898282+github-actions[bot]@users.noreply.github.com' + message: 'chore: update E2E timings' diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2b91d078e..c5bbc6e62 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -28,7 +28,6 @@ env: ) || github.event.before }} - shouldRunParallel: ${{ secrets.CYPRESS_RECORD_KEY != '' && !(github.event_name == 'push' && github.ref == 'refs/heads/develop') }} jobs: cache: runs-on: ubuntu-latest @@ -39,7 +38,7 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.node-version' - name: Cache snapshots @@ -59,7 +58,7 @@ jobs: - name: Install dependencies if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} - uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2 + uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 with: # just perform install runTests: false @@ -80,7 +79,7 @@ jobs: strategy: fail-fast: false matrix: - containers: [1, 2, 3, 4] + containers: [1, 2, 3, 4, 5] steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -88,7 +87,7 @@ jobs: # uses version from "packageManager" field in package.json - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: node-version-file: '.node-version' @@ -101,7 +100,7 @@ jobs: key: ${{ runner.os }}-snapshots-${{ env.targetHash }} - name: Install dependencies - uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2 + uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 with: runTests: false @@ -117,11 +116,8 @@ jobs: # Install NPM dependencies, cache them correctly # and run all Cypress tests - name: Cypress run - uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2 + uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6 id: cypress - # If CYPRESS_RECORD_KEY is set, run in parallel on all containers - # Otherwise (e.g. if running from fork), we run on a single container only - if: ${{ env.shouldRunParallel == 'true' || ( matrix.containers == 1 ) }} with: install: false start: pnpm run dev:coverage @@ -129,16 +125,18 @@ jobs: browser: chrome # Disable recording if we don't have an API key # e.g. if this action was run from a fork - record: ${{ env.shouldRunParallel == 'true' }} - parallel: ${{ env.shouldRunParallel == 'true' }} + record: ${{ secrets.CYPRESS_RECORD_KEY != '' }} env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} VITEST_COVERAGE: true CYPRESS_COMMIT: ${{ github.sha }} ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} - ARGOS_PARALLEL: ${{ env.shouldRunParallel == 'true' }} - ARGOS_PARALLEL_TOTAL: 4 + ARGOS_PARALLEL: true + ARGOS_PARALLEL_TOTAL: ${{ strategy.job-total }} ARGOS_PARALLEL_INDEX: ${{ matrix.containers }} + SPLIT: ${{ strategy.job-total }} + SPLIT_INDEX: ${{ strategy.job-index }} + SPLIT_FILE: 'cypress/timings.json' - name: Upload Coverage to Codecov uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index febd2f92d..24e7ee35d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -29,7 +29,7 @@ jobs: # uses version from "packageManager" field in package.json - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index ecb411b5c..587ddae08 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -28,7 +28,7 @@ jobs: - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/.github/workflows/release-preview-publish.yml b/.github/workflows/release-preview-publish.yml index 96556aa26..fa48d3659 100644 --- a/.github/workflows/release-preview-publish.yml +++ b/.github/workflows/release-preview-publish.yml @@ -16,7 +16,7 @@ jobs: - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/.github/workflows/release-preview.yml b/.github/workflows/release-preview.yml index 84808e44f..7b7dba987 100644 --- a/.github/workflows/release-preview.yml +++ b/.github/workflows/release-preview.yml @@ -31,7 +31,7 @@ jobs: - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3db5f6f37..4e8e9cd83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: - uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0 - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' @@ -36,7 +36,7 @@ jobs: - name: Create Release Pull Request or Publish to npm id: changesets - uses: changesets/action@aba318e9165b45b7948c60273e0b72fce0a64eb9 # v1.4.7 + uses: changesets/action@3de3850952bec538fde60aac71731376e57b9b57 # v1.4.8 with: version: pnpm changeset:version publish: pnpm changeset:publish diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 0dee2e666..528e94045 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -16,11 +16,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false - name: Run analysis - uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 with: results_file: results.sarif results_format: sarif @@ -32,6 +32,6 @@ jobs: path: results.sarif retention-days: 5 - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4 + uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 with: sarif_file: results.sarif diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 026ca0fb7..7323ec027 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: # uses version from "packageManager" field in package.json - name: Setup Node.js - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4 with: cache: pnpm node-version-file: '.node-version' diff --git a/cypress.config.ts b/cypress.config.ts index 3346b5549..b0257b9b2 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,8 +1,9 @@ -import { defineConfig } from 'cypress'; -import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'; -import coverage from '@cypress/code-coverage/task'; import eyesPlugin from '@applitools/eyes-cypress'; import { registerArgosTask } from '@argos-ci/cypress/task'; +import coverage from '@cypress/code-coverage/task'; +import { defineConfig } from 'cypress'; +import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'; +import cypressSplit from 'cypress-split'; export default eyesPlugin( defineConfig({ @@ -13,6 +14,7 @@ export default eyesPlugin( specPattern: 'cypress/integration/**/*.{js,ts}', setupNodeEvents(on, config) { coverage(on, config); + cypressSplit(on, config); on('before:browser:launch', (browser, launchOptions) => { if (browser.name === 'chrome' && browser.isHeadless) { launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1'); diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts index 74b17cf05..52da4a72e 100644 --- a/cypress/helpers/util.ts +++ b/cypress/helpers/util.ts @@ -29,6 +29,7 @@ export const mermaidUrl = ( options: CypressMermaidConfig, api: boolean ): string => { + options.handDrawnSeed = 1; const codeObject: CodeObject = { code: graphStr, mermaid: options, diff --git a/cypress/integration/rendering/architecture.spec.ts b/cypress/integration/rendering/architecture.spec.ts index 1deb1f7da..bc2276352 100644 --- a/cypress/integration/rendering/architecture.spec.ts +++ b/cypress/integration/rendering/architecture.spec.ts @@ -173,7 +173,8 @@ describe.skip('architecture diagram', () => { }); }); -describe('architecture - external', () => { +// Skipped as the layout is not deterministic, and causes issues in E2E tests. +describe.skip('architecture - external', () => { it('should allow adding external icons', () => { urlSnapshotTest('http://localhost:9000/architecture-external.html'); }); diff --git a/cypress/integration/rendering/flowchart-handDrawn.spec.js b/cypress/integration/rendering/flowchart-handDrawn.spec.js index 10d6dde87..49c55c628 100644 --- a/cypress/integration/rendering/flowchart-handDrawn.spec.js +++ b/cypress/integration/rendering/flowchart-handDrawn.spec.js @@ -12,7 +12,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -30,7 +29,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: true }, fontFamily: 'courier', } @@ -47,7 +45,7 @@ describe('Flowchart HandDrawn', () => { C -->|Two| E[iPhone] C -->|Three| F[Car] `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -62,7 +60,7 @@ describe('Flowchart HandDrawn', () => { C -->|Two| E[\\iPhone\\] C -->|Three| F[Car] `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -78,7 +76,7 @@ describe('Flowchart HandDrawn', () => { classDef processHead fill:#888888,color:white,font-weight:bold,stroke-width:3px,stroke:#001f3f class 1A,1B,D,E processHead `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -107,7 +105,7 @@ describe('Flowchart HandDrawn', () => { 35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails) 36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails) `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -178,7 +176,7 @@ describe('Flowchart HandDrawn', () => { 9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002 9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002 `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -187,7 +185,7 @@ describe('Flowchart HandDrawn', () => { ` graph TB;subgraph "number as labels";1;end; `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -199,7 +197,7 @@ describe('Flowchart HandDrawn', () => { a1-->a2 end `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -211,7 +209,7 @@ describe('Flowchart HandDrawn', () => { a1-->a2 end `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -246,7 +244,7 @@ describe('Flowchart HandDrawn', () => { style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -348,7 +346,7 @@ describe('Flowchart HandDrawn', () => { sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A; sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22; `, - { look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' } + { look: 'handDrawn', fontFamily: 'courier' } ); }); @@ -364,7 +362,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, listUrl: false, listId: 'color styling', fontFamily: 'courier', @@ -390,7 +387,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, listUrl: false, listId: 'color styling', fontFamily: 'courier', @@ -411,7 +407,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -435,7 +430,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -457,7 +451,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -471,7 +464,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -485,7 +477,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -500,7 +491,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -527,7 +517,6 @@ describe('Flowchart HandDrawn', () => { class A someclass;`, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -544,7 +533,7 @@ describe('Flowchart HandDrawn', () => { C -->|Two| E[iPhone] C -->|Three| F[fa:fa-car Car] `, - { look: 'handDrawn', handDrawnSeed: 1, flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' } + { look: 'handDrawn', flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' } ); }); @@ -560,7 +549,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { rankSpacing: '100' }, fontFamily: 'courier', } @@ -578,7 +566,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -603,7 +590,7 @@ describe('Flowchart HandDrawn', () => { click E "notes://do-your-thing/id" "other protocol test" click F "javascript:alert('test')" "script test" `, - { look: 'handDrawn', handDrawnSeed: 1, securityLevel: 'loose', fontFamily: 'courier' } + { look: 'handDrawn', securityLevel: 'loose', fontFamily: 'courier' } ); }); @@ -623,7 +610,7 @@ describe('Flowchart HandDrawn', () => { click B "index.html#link-clicked" "link test" click D testClick "click test" `, - { look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } } + { look: 'handDrawn', flowchart: { htmlLabels: true } } ); }); @@ -645,7 +632,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -664,7 +650,7 @@ describe('Flowchart HandDrawn', () => { class A myClass1 class D myClass2 `, - { look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } } + { look: 'handDrawn', flowchart: { htmlLabels: true } } ); }); @@ -682,7 +668,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -711,7 +696,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -728,7 +712,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -752,7 +735,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: false }, fontFamily: 'courier', } @@ -769,7 +751,7 @@ describe('Flowchart HandDrawn', () => { C -->|Two| E[iPhone] C -->|Three| F[fa:fa-car Car] `, - { look: 'handDrawn', handDrawnSeed: 1, flowchart: { diagramPadding: 0 } } + { look: 'handDrawn', flowchart: { diagramPadding: 0 } } ); }); @@ -804,7 +786,7 @@ describe('Flowchart HandDrawn', () => { `graph TD a["Haiya"]-->b `, - { look: 'handDrawn', handDrawnSeed: 1, htmlLabels: false, flowchart: { htmlLabels: false } } + { look: 'handDrawn', htmlLabels: false, flowchart: { htmlLabels: false } } ); }); it('FDH37: should render non-escaped with html labels', () => { @@ -814,7 +796,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -830,7 +811,7 @@ describe('Flowchart HandDrawn', () => { C -->|Two| E[iPhone] C -->|Three| F[fa:fa-car Car] `, - { look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: true } } + { look: 'handDrawn', flowchart: { useMaxWidth: true } } ); cy.get('svg').should((svg) => { expect(svg).to.have.attr('width', '100%'); @@ -853,7 +834,7 @@ describe('Flowchart HandDrawn', () => { C -->|Two| E[iPhone] C -->|Three| F[fa:fa-car Car] `, - { look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: false } } + { look: 'handDrawn', flowchart: { useMaxWidth: false } } ); cy.get('svg').should((svg) => { // const height = parseFloat(svg.attr('height')); @@ -874,7 +855,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -904,7 +884,6 @@ describe('Flowchart HandDrawn', () => { `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -919,7 +898,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -937,7 +915,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -977,7 +954,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -999,7 +975,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -1016,7 +991,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -1032,7 +1006,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', @@ -1051,7 +1024,6 @@ graph TD `, { look: 'handDrawn', - handDrawnSeed: 1, flowchart: { htmlLabels: true }, securityLevel: 'loose', } 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..86aef718c --- /dev/null +++ b/cypress/integration/rendering/flowchart-shape-alias.spec.ts @@ -0,0 +1,142 @@ +import { imgSnapshotTest } from '../../helpers/util.ts'; + +const aliasSet1 = ['process', 'rect', 'proc', 'rectangle'] as const; + +const aliasSet2 = ['event', 'rounded'] as const; + +const aliasSet3 = ['stadium', 'pill', 'terminal'] as const; + +const aliasSet4 = ['fr-rect', 'subproc', 'subprocess', 'framed-rectangle', 'subroutine'] as const; + +const aliasSet5 = ['db', 'database', 'cylinder', 'cyl'] as const; + +const aliasSet6 = ['diam', 'decision', 'diamond'] as const; + +const aliasSet7 = ['hex', 'hexagon', 'prepare'] as const; + +const aliasSet8 = ['lean-r', 'lean-right', 'in-out'] as const; + +const aliasSet9 = ['lean-l', 'lean-left', 'out-in'] as const; + +const aliasSet10 = ['trap-b', 'trapezoid-bottom', 'priority'] as const; + +const aliasSet11 = ['trap-t', 'trapezoid-top', 'manual'] as const; + +const aliasSet12 = ['dbl-circ', 'double-circle'] as const; + +const aliasSet13 = ['notched-rectangle', 'card', 'notch-rect'] as const; + +const aliasSet14 = [ + 'lin-rect', + 'lined-rectangle', + 'lin-proc', + 'lined-process', + 'shaded-process', +] as const; + +const aliasSet15 = ['sm-circ', 'small-circle', 'start'] as const; + +const aliasSet16 = ['fr-circ', 'framed-circle', 'stop'] as const; + +const aliasSet17 = ['fork', 'join'] as const; +// brace-r', 'braces' +const aliasSet18 = ['comment', 'brace-l'] as const; + +const aliasSet19 = ['bolt', 'com-link', 'lightning-bolt'] as const; + +const aliasSet20 = ['doc', 'document'] as const; + +const aliasSet21 = ['delay', 'half-rounded-rectangle'] as const; + +const aliasSet22 = ['h-cyl', 'das', 'horizontal-cylinder'] as const; + +const aliasSet23 = ['lin-cyl', 'disk', 'lined-cylinder'] as const; + +const aliasSet24 = ['curv-trap', 'display', 'curved-trapezoid'] as const; + +const aliasSet25 = ['div-rect', 'div-proc', 'divided-rectangle', 'divided-process'] as const; + +const aliasSet26 = ['extract', 'tri', 'triangle'] as const; + +const aliasSet27 = ['win-pane', 'internal-storage', 'window-pane'] as const; + +const aliasSet28 = ['f-circ', 'junction', 'filled-circle'] as const; + +const aliasSet29 = ['lin-doc', 'lined-document'] as const; + +const aliasSet30 = ['notch-pent', 'loop-limit', 'notched-pentagon'] as const; + +const aliasSet31 = ['flip-tri', 'manual-file', 'flipped-triangle'] as const; + +const aliasSet32 = ['sl-rect', 'manual-input', 'sloped-rectangle'] as const; + +const aliasSet33 = ['docs', 'documents', 'st-doc', 'stacked-document'] as const; + +const aliasSet34 = ['procs', 'processes', 'st-rect', 'stacked-rectangle'] as const; + +const aliasSet35 = ['flag', 'paper-tape'] as const; + +const aliasSet36 = ['bow-rect', 'stored-data', 'bow-tie-rectangle'] as const; + +const aliasSet37 = ['cross-circ', 'summary', 'crossed-circle'] as const; + +const aliasSet38 = ['tag-doc', 'tagged-document'] as const; + +const aliasSet39 = ['tag-rect', 'tag-proc', 'tagged-rectangle', 'tagged-process'] as const; + +const aliasSet40 = ['collate', 'hourglass'] 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..4c12c3fa3 --- /dev/null +++ b/cypress/integration/rendering/iconShape.spec.ts @@ -0,0 +1,143 @@ +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); + }); +}); + +describe('Test colored iconShape', () => { + it('with no styles', () => { + let flowchartCode = `flowchart TB\n`; + const icon = 'fluent-emoji:tropical-fish'; + flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`; + imgSnapshotTest(flowchartCode); + }); + + it('with styles', () => { + let flowchartCode = `flowchart TB\n`; + const icon = 'fluent-emoji:tropical-fish'; + flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`; + flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \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..d2e267149 --- /dev/null +++ b/cypress/integration/rendering/imageShape.spec.ts @@ -0,0 +1,103 @@ +import { imgSnapshotTest } from '../../helpers/util'; + +const looks = ['classic', 'handDrawn'] as const; +const directions = [ + 'TB', + //'BT', + 'LR', + // 'RL' +] 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 new file mode 100644 index 000000000..6c71a3846 --- /dev/null +++ b/cypress/integration/rendering/newShapes.spec.ts @@ -0,0 +1,146 @@ +import { imgSnapshotTest } from '../../helpers/util.ts'; + +const looks = ['classic', 'handDrawn'] as const; +const directions = [ + 'TB', + //'BT', + 'LR', + //'RL' +] as const; +const newShapesSet1 = [ + 'triangle', + 'sloped-rectangle', + 'horizontal-cylinder', + 'flipped-triangle', + 'hourglass', +] as const; +const newShapesSet2 = [ + 'tagged-rectangle', + 'documents', + 'lightning-bolt', + 'filled-circle', + 'window-pane', +] as const; + +const newShapesSet3 = [ + 'curved-trapezoid', + 'bow-rect', + 'tagged-document', + 'divided-rectangle', + 'crossed-circle', +] as const; + +const newShapesSet4 = [ + 'document', + 'notched-pentagon', + 'lined-cylinder', + 'stacked-document', + 'half-rounded-rectangle', +] as const; + +const newShapesSet5 = [ + 'lined-document', + 'tagged-document', + 'brace-l', + 'comment', + 'braces', + 'brace-r', +] as const; + +const newShapesSet6 = ['brace-r', 'braces'] as const; +// Aggregate all shape sets into a single array +const newShapesSets = [ + newShapesSet1, + newShapesSet2, + newShapesSet3, + newShapesSet4, + newShapesSet5, + newShapesSet6, +]; + +looks.forEach((look) => { + directions.forEach((direction) => { + newShapesSets.forEach((newShapesSet) => { + describe(`Test ${newShapesSet.join(', ')} in ${look} look and dir ${direction}`, () => { + it(`without label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`connect all shapes with each other`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`; + }); + for (let i = 0; i < newShapesSet.length; i++) { + for (let j = i + 1; j < newShapesSet.length; j++) { + flowchartCode += ` n${i}${i} --> n${j}${j}\n`; + } + } + if (!(direction === 'TB' && look === 'handDrawn' && newShapesSet === newShapesSet1)) { + //skip this test, works in real. Need to look + imgSnapshotTest(flowchartCode, { look }); + } + }); + + it(`with very long label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:true`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold**
and strong for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:false`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold**
and strong for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { + look, + htmlLabels: false, + flowchart: { htmlLabels: false }, + }); + }); + + it(`with styles`, () => { + let flowchartCode = `flowchart ${direction}\n`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`; + flowchartCode += ` style n${index}${index} 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`; + newShapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`; + flowchartCode += ` n${index}${index}:::customClazz\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + }); + }); + }); +}); diff --git a/cypress/integration/rendering/oldShapes.spec.ts b/cypress/integration/rendering/oldShapes.spec.ts new file mode 100644 index 000000000..628e70ea8 --- /dev/null +++ b/cypress/integration/rendering/oldShapes.spec.ts @@ -0,0 +1,107 @@ +import { imgSnapshotTest } from '../../helpers/util'; + +const looks = ['classic', 'handDrawn'] as const; +const directions = [ + 'TB', + //'BT', + 'LR', + //'RL' +] as const; + +const shapesSet1 = ['text', 'card', 'lin-rect', 'diamond', 'hexagon'] as const; + +// removing labelRect, need have alias for it +const shapesSet2 = ['rounded', 'rect', 'start', 'stop'] as const; + +const shapesSet3 = ['fork', 'choice', 'note', 'stadium', 'odd'] as const; + +const shapesSet4 = ['subroutine', 'cylinder', 'circle', 'doublecircle', 'odd'] as const; + +const shapesSet5 = ['anchor', 'lean-r', 'lean-l', 'trap-t', 'trap-b'] as const; + +// Aggregate all shape sets into a single array +const shapesSets = [shapesSet1, shapesSet2, shapesSet3, shapesSet4, shapesSet5] as const; + +looks.forEach((look) => { + directions.forEach((direction) => { + shapesSets.forEach((shapesSet) => { + describe(`Test ${shapesSet.join(', ')} in ${look} look and dir ${direction}`, () => { + it(`without label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape} }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`connect all shapes with each other`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index}${index}@{ shape: ${newShape}, label: 'This is a label for ${newShape} shape' }\n`; + }); + for (let i = 0; i < shapesSet.length; i++) { + for (let j = i + 1; j < shapesSet.length; j++) { + flowchartCode += ` n${i}${i} --> n${j}${j}\n`; + } + } + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with very long label`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is a very very very very very long long long label for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:true`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold**
and strong for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + + it(`with markdown htmlLabels:false`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'This is **bold**
and strong for ${newShape} shape' }\n`; + }); + imgSnapshotTest(flowchartCode, { + look, + htmlLabels: false, + flowchart: { htmlLabels: false }, + }); + }); + + it(`with styles`, () => { + let flowchartCode = `flowchart ${direction}\n`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`; + flowchartCode += ` style n${index}${index} 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`; + shapesSet.forEach((newShape, index) => { + flowchartCode += ` n${index} --> n${index}${index}@{ shape: ${newShape}, label: 'new ${newShape} shape' }\n`; + flowchartCode += ` n${index}${index}:::customClazz\n`; + }); + imgSnapshotTest(flowchartCode, { look }); + }); + }); + }); + }); +}); diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 80406b939..d93881018 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -83,7 +83,8 @@ -
+    
+
 ---
   title: hello2
   config:
@@ -242,8 +243,190 @@ stateDiagram-v2
 
 
 
-      
+
+
+---
+config:
+  look: neo
+---
+flowchart RL
+    subgraph "   "
+        A5@{ shape: manual-file, label: "a label"}
+        B5@{ shape: manual-input, label: "a label" }
+        C5@{ shape: mul-doc, label: "a label" }
+        D5@{ shape: mul-proc, label: "a label" }
+        E5@{ shape: paper-tape, label: "a label" }
+        B3@{ shape: das, label: "a label" }
+        C3@{ shape: disk, label: "a label" }
+        D4@{ shape: lin-doc, label: "a label" }
+        E4@{ shape: loop-limit, label: "a label" }
+    end
+    subgraph "   "
+        B6@{ shape: summary, label: "a label" }
+        C6@{ shape: tag-we-rect, label: "a label" }
+        D6@{ shape: tag-rect, label: "a label" }
+        A2@{ shape: fork}
+        B2@{ shape: hourglass }
+        C2@{ shape: comment, label: "I am a comment" }
+        D2@{ shape: bolt }
+        D3@{ shape: disp, label: "a label" }
+        C4@{ shape: junction, label: "a label" }
+        A4@{ shape: extract, label: "a label"}
+        B52[a fr]@{ shape: fr }
+    end
+    subgraph " "
+        A1@{ shape: text, label: This is a textblock}
+        B1@{ shape: card, label: "a label" }
+        C1@{ shape: lined-proc, label: "a label" }
+        D1@{ shape: start, label: "a label" }
+        E1@{ shape: stop, label: "a label" }
+        E2@{ shape: doc, label: "a label" }
+        A6@{ shape: stored-data, label: "a label"}
+        A3@{ shape: delay, label: "a label" }
+        E3@{ shape: div-proc, label: "a label" }
+        B4[a label]@{ shape: win-pane }
+    end
+      
+
+---
+  title: hello2
+  config:
+    look: handDrawn
+    elk:
+      
+---
+%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
+flowchart TD
+
+    A([Start]) -->|go to booking page| B("select
+    ISBS booking no")
+    A --> QQ{cancel booking}
+    A --> RR{no show}
+    A --> SS{change booking}
+    B -->C(wmpay_request_payment.request_type= 'partial',
+ wmpay_request_payment.status= 'paid',
+ pos_booking.booking_status= ‘partial’ and 'full_deposit')
+ style C text-align:left
+    C -->D{manage booking}
+
+    D -->|cancel|E[ระบบแสดงช่องให้กรอกเหตุผล]
+    E -->F{กดปุ่ม 'cancel' หรือไม่}
+    F -->|Yes|G[ระบบบันทึกค่าใหม่
+    และไม่สามารถแก้ไขข้อมูลได้]
+    F -->|No|H[กดปุ่ม 'close']
+    H -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
+    G -->|ระบบส่งข้อมูล|I[(POS_database)]
+    I -->|pos_booking.booking_status='cancel'|Z([End])
+
+
+    D -->|no show|J[ระบบแสดงช่องให้กรอกเหตุผล]
+    J -->K{กดปุ่ม 'noshow' หรือไม่}
+    K -->|Yes|L[ระบบสร้างใบเสร็จอัตโนมัติ
+    Product_id: 439,
+    ItemName: no show]
+     style L text-align:left
+
+     K -->|No|O[กดปุ่ม 'close']
+     O -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
+    L -->M[ระบบบันทึกค่าใหม่]
+    M -->|ระบบส่งข้อมูล|N[(POS_database)]
+    N -->|pos_booking.booking_status=‘noshow’|Z
+
+
+
+
+
+---
+  title: hello2
+  config:
+    look: handDrawn
+    layout: dagre
+    elk:
+        nodePlacementStrategy: BRANDES_KOEPF
+---
+flowchart
+  A --> A
+  subgraph A
+    B --> B
+    subgraph B
+      C
+    end
+  end
+
+
+
+
+---
+config:
+  look: handdrawn
+  flowchart:
+    htmlLabels: true
+---
+flowchart
+      A[I am a long text, where do I go??? handdrawn - true]
+
+ +
+
+---
+config:
+  flowchart:
+    htmlLabels: false
+---
+flowchart
+      A[I am a long text, where do I go??? classic - false]
+
+
+---
+config:
+  flowchart:
+    htmlLabels: true
+---
+flowchart
+      A[I am a long text, where do I go??? classic - true]
+
+
+
+flowchart LR
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+
+
+    
+ +
+      flowchart LR
+    A:::foo & B:::bar --> C:::foobar
+    classDef foo stroke:#f00
+    classDef bar stroke:#0f0
+    classDef ash color:red
+    class C ash
+    style C stroke:#00f, fill:black
+
+    
+ +
+      stateDiagram
+    A:::foo
+    B:::bar --> C:::foobar
+    classDef foo stroke:#f00
+    classDef bar stroke:#0f0
+    style C stroke:#00f, fill:black, color:white
+
+    
+ +
+flowchart TB
+  A@{
+    label: "aksljhf kasjdh"
+  }
+    
+ + diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js index 77da253c2..a9d4d2d81 100644 --- a/cypress/platform/viewer.js +++ b/cypress/platform/viewer.js @@ -50,6 +50,60 @@ const contentLoaded = async function () { mermaid.registerLayoutLoaders(layouts); mermaid.initialize(graphObj.mermaid); + const staticBellIconPack = { + prefix: 'fa6-regular', + icons: { + bell: { + body: '', + width: 448, + }, + }, + width: 512, + height: 512, + }; + /* MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE */ + const staticAwsLogoIconPack = { + prefix: 'fluent-emoji', + icons: { + 'tropical-fish': { + width: 32, + height: 32, + body: '', + }, + }, + width: 256, + height: 256, + }; + mermaid.registerIconPacks([ + { + name: 'fa', + loader: () => staticBellIconPack, + }, + { + name: 'fluent-emoji', + loader: () => staticAwsLogoIconPack, + }, + ]); await mermaid.run(); } }; diff --git a/cypress/timings.json b/cypress/timings.json new file mode 100644 index 000000000..3455d82fc --- /dev/null +++ b/cypress/timings.json @@ -0,0 +1,152 @@ +{ + "durations": [ + { + "spec": "cypress/integration/other/configuration.spec.js", + "duration": 4989 + }, + { + "spec": "cypress/integration/other/external-diagrams.spec.js", + "duration": 1382 + }, + { + "spec": "cypress/integration/other/ghsa.spec.js", + "duration": 3178 + }, + { + "spec": "cypress/integration/other/iife.spec.js", + "duration": 1372 + }, + { + "spec": "cypress/integration/other/interaction.spec.js", + "duration": 8998 + }, + { + "spec": "cypress/integration/other/rerender.spec.js", + "duration": 1249 + }, + { + "spec": "cypress/integration/other/xss.spec.js", + "duration": 25664 + }, + { + "spec": "cypress/integration/rendering/appli.spec.js", + "duration": 1928 + }, + { + "spec": "cypress/integration/rendering/architecture.spec.ts", + "duration": 2330 + }, + { + "spec": "cypress/integration/rendering/block.spec.js", + "duration": 11156 + }, + { + "spec": "cypress/integration/rendering/c4.spec.js", + "duration": 3418 + }, + { + "spec": "cypress/integration/rendering/classDiagram-v2.spec.js", + "duration": 14866 + }, + { + "spec": "cypress/integration/rendering/classDiagram.spec.js", + "duration": 9894 + }, + { + "spec": "cypress/integration/rendering/conf-and-directives.spec.js", + "duration": 5778 + }, + { + "spec": "cypress/integration/rendering/current.spec.js", + "duration": 1690 + }, + { + "spec": "cypress/integration/rendering/erDiagram.spec.js", + "duration": 9144 + }, + { + "spec": "cypress/integration/rendering/errorDiagram.spec.js", + "duration": 1951 + }, + { + "spec": "cypress/integration/rendering/flowchart-elk.spec.js", + "duration": 2196 + }, + { + "spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js", + "duration": 21029 + }, + { + "spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts", + "duration": 16087 + }, + { + "spec": "cypress/integration/rendering/flowchart-v2.spec.js", + "duration": 27465 + }, + { + "spec": "cypress/integration/rendering/flowchart.spec.js", + "duration": 20035 + }, + { + "spec": "cypress/integration/rendering/gantt.spec.js", + "duration": 11366 + }, + { + "spec": "cypress/integration/rendering/gitGraph.spec.js", + "duration": 34025 + }, + { + "spec": "cypress/integration/rendering/iconShape.spec.ts", + "duration": 185902 + }, + { + "spec": "cypress/integration/rendering/imageShape.spec.ts", + "duration": 41631 + }, + { + "spec": "cypress/integration/rendering/info.spec.ts", + "duration": 1736 + }, + { + "spec": "cypress/integration/rendering/journey.spec.js", + "duration": 2247 + }, + { + "spec": "cypress/integration/rendering/katex.spec.js", + "duration": 2144 + }, + { + "spec": "cypress/integration/rendering/marker_unique_id.spec.js", + "duration": 1646 + }, + { + "spec": "cypress/integration/rendering/mindmap.spec.ts", + "duration": 6406 + }, + { + "spec": "cypress/integration/rendering/newShapes.spec.ts", + "duration": 107219 + }, + { + "spec": "cypress/integration/rendering/stateDiagram.spec.js", + "duration": 15834 + }, + { + "spec": "cypress/integration/rendering/theme.spec.js", + "duration": 33240 + }, + { + "spec": "cypress/integration/rendering/timeline.spec.ts", + "duration": 7122 + }, + { + "spec": "cypress/integration/rendering/xyChart.spec.js", + "duration": 11127 + }, + { + "spec": "cypress/integration/rendering/zenuml.spec.js", + "duration": 2391 + } + ] +} diff --git a/docs/adding-new-shape.md b/docs/adding-new-shape.md new file mode 100644 index 000000000..64a177237 --- /dev/null +++ b/docs/adding-new-shape.md @@ -0,0 +1,233 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/adding-new-shape.md](../packages/mermaid/src/docs/adding-new-shape.md). + +# Custom SVG Shapes Library + +This library provides a collection of custom SVG shapes, utilities, and helpers for generating diagram components. The shapes are designed to be used within an SVG container and include a variety of common and complex shapes. + +## Overview + +## Shape Helpers and Utilities + +Before starting with shape creation, it's essential to familiarize yourself with the utilities provided in the `utils.ts` file from `packages/mermaid/src/rendering-util/rendering-elements/shapes/util.js`. These utilities are designed to assist with various aspects of SVG shape manipulation and ensure consistent and accurate rendering. + +## Available Utilities + +### 1. `labelHelper` + +- **Purpose**: This function creates and inserts labels inside SVG shapes. +- **Features**: + - Handles both HTML labels and plain text. + - Calculates the bounding box dimensions of the label. + - Ensures proper positioning of labels within shapes. + +### 2. `updateNodeBounds` + +- **Purpose**: Updates the bounding box dimensions (width and height) of a node. +- **Usage**: + - Adjusts the size of the node to fit the content or shape. + - Useful for ensuring that shapes resize appropriately based on their content. + +### 3. `insertPolygonShape` + +- **Purpose**: Inserts a polygon shape into an SVG container. +- **Features**: + - Handles the creation and insertion of complex polygonal shapes. + - Configures the shape's appearance and positioning within the SVG container. + +### 4. `getNodeClasses` + +- **Purpose**: Returns the appropriate CSS classes for a node based on its configuration. +- **Usage**: + - Dynamically applies CSS classes to nodes for styling purposes. + - Ensures that nodes adhere to the desired design and theme. + +### 5. `createPathFromPoints` + +- **Purpose**: Generates an SVG path string from an array of points. +- **Usage**: + - Converts a list of points into a smooth path. + - Useful for creating custom shapes or paths within the SVG. + +### 6. `generateFullSineWavePoints` + +- **Purpose**: Generates points for a sine wave, useful for creating wavy-edged shapes. +- **Usage**: + - Facilitates the creation of shapes with wavy or sine-wave edges. + - Can be used to add decorative or dynamic edges to shapes. + +## Getting Started + +To utilize these utilities, simply import them from the `utils.ts` file into your shape creation script. These helpers will streamline the process of building and customizing SVG shapes, ensuring consistent results across your projects. + +```typescript +import { + labelHelper, + updateNodeBounds, + insertPolygonShape, + getNodeClasses, + createPathFromPoints, + generateFullSineWavePoints, +} from './utils.ts'; +``` + +## Example Usage + +Here’s a basic example of how you might use some of these utilities: + +```typescript +import { labelHelper, insertPolygonShape } from './utils.ts'; + +const svgContainer = document.getElementById('svgContainer'); + +// Insert a polygon shape +insertPolygonShape(svgContainer /* shape-specific parameters */); + +// Create and insert a label inside the shape +labelHelper(svgContainer /* label-specific parameters */); +``` + +## Adding New Shapes + +### 1. Create the Shape Function + +To add a new shape: + +- **Create the shape function**: Create a new file of name of the shape and export a function in the `shapes` directory that generates your shape. The file and function should follow the pattern used in existing shapes and return an SVG element. + +- **Example**: + + ```typescript + import { Node, RenderOptions } from '../../types.ts'; + + export const myNewShape = async ( + parent: SVGAElement, + node: Node, + renderOptions: RenderOptions + ) => { + // Create your shape here + const shape = parent.insert('g').attr('class', 'my-new-shape'); + // Add other elements or styles as needed + return shape; + }; + ``` + +### 2. Register the Shape + +- **Register the shape**: Add your shape to the `shapes` object in the [main shapes module](../rendering-util/rendering-elements/shapes.ts). This allows your shape to be recognized and used within the system. + +- **Example**: + + ```typescript + import { myNewShape } from './shapes/myNewShape'; + + const shapes = { + ..., + { + semanticName: 'My Shape', + name: 'Shape Name', + shortName: '', + description: '', + aliases: ['', '', '', ''], + handler: myNewShape, + }, + }; + ``` + +# Shape Intersection Algorithms + +This contains algorithms and utilities for calculating intersection points for various shapes in SVG. Arrow intersection points are crucial for accurately determining where arrows connect with shapes. Ensuring precise intersection points enhances the clarity and accuracy of flowcharts and diagrams. + +## Shape Intersection Functions + +### 1. `Ellipse` + +Calculates the intersection points for an ellipse. + +**Usage**: + +```javascript +import intersectEllipse from './intersect-ellipse.js'; + +const intersection = intersectEllipse(node, rx, ry, point); +``` + +- **Parameters**: + - `node`: The SVG node element. + - `rx`: The x-radius of the ellipse. + - `ry`: The y-radius of the ellipse. + - `point`: The point from which the intersection is calculated. + +### 2. `intersectRect` + +Calculates the intersection points for a rectangle. + +**Usage**: + +```javascript +import intersectRect from './intersect-rect.js'; + +const intersection = intersectRect(node, point); +``` + +- **Parameters**: + - `node`: The SVG node element. + - `point`: The point from which the intersection is calculated. + +### 3. `intersectPolygon` + +Calculates the intersection points for a polygon. + +**Usage**: + +```javascript +import intersectPolygon from './intersect-polygon.js'; + +const intersection = intersectPolygon(node, polyPoints, point); +``` + +- **Parameters**: + - `node`: The SVG node element. + - `polyPoints`: Array of points defining the polygon. + - `point`: The point from which the intersection is calculated. + +## Cypress Tests + +To ensure the robustness of the flowchart shapes, there are implementation of comprehensive Cypress test cases in `newShapes.spec.ts` file. These tests cover various aspects such as: + +- **Shapes**: Testing new shapes like `bowTieRect`, `waveRectangle`, `trapezoidalPentagon`, etc. +- **Looks**: Verifying shapes under different visual styles (`classic` and `handDrawn`). +- **Directions**: Ensuring correct rendering in all flow directions of arrows : + - `TB` `(Top -> Bottom)` + - `BT` `(Bottom -> Top)` + - `LR` `(Left -> Right)` + - `RL` `(Right -> Left)` +- **Labels**: Testing shapes with different labels, including: + - No labels + - Short labels + - Very long labels + - Markdown with `htmlLabels:true` and `htmlLabels:false` +- **Styles**: Applying custom styles to shapes and verifying correct rendering. +- **Class Definitions**: Using `classDef` to apply custom classes and testing their impact. + +### Running the Tests + +To run the Cypress tests, follow these steps: + +1. Ensure you have all dependencies installed by running: + + ```bash + pnpm install + ``` + +2. Start the Cypress test runner: + + ```bash + cypress open --env updateSnapshots=true + + ``` + +3. Select the test suite from the Cypress interface to run all the flowchart shape tests. diff --git a/docs/config/icons.md b/docs/config/icons.md new file mode 100644 index 000000000..729afa1d5 --- /dev/null +++ b/docs/config/icons.md @@ -0,0 +1,55 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/icons.md](../../packages/mermaid/src/docs/config/icons.md). + +# Registering icon pack in mermaid + +The icon packs available can be found at [icones.js.org](https://icones.js.org/). +We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram. + +Using JSON file directly from CDN: + +```js +import mermaid from 'CDN/mermaid.esm.mjs'; +mermaid.registerIconPacks([ + { + name: 'logos', + loader: () => + fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()), + }, +]); +``` + +Using packages and a bundler: + +```bash +npm install @iconify-json/logos@1 +``` + +With lazy loading + +```js +import mermaid from 'mermaid'; + +mermaid.registerIconPacks([ + { + name: 'logos', + loader: () => import('@iconify-json/logos').then((module) => module.icons), + }, +]); +``` + +Without lazy loading + +```js +import mermaid from 'mermaid'; +import { icons } from '@iconify-json/logos'; +mermaid.registerIconPacks([ + { + name: icons.prefix, // To use the prefix defined in the icon pack + icons, + }, +]); +``` diff --git a/docs/config/setup/classes/mermaid.UnknownDiagramError.md b/docs/config/setup/classes/mermaid.UnknownDiagramError.md index 53ca9fc32..a3359c9d0 100644 --- a/docs/config/setup/classes/mermaid.UnknownDiagramError.md +++ b/docs/config/setup/classes/mermaid.UnknownDiagramError.md @@ -127,7 +127,7 @@ Error.prepareStackTrace #### Defined in -node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:28 +node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:98 --- @@ -141,7 +141,7 @@ Error.stackTraceLimit #### Defined in -node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:30 +node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:100 ## Methods @@ -168,4 +168,4 @@ Error.captureStackTrace #### Defined in -node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:21 +node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:91 diff --git a/docs/config/setup/interfaces/mermaid.LayoutData.md b/docs/config/setup/interfaces/mermaid.LayoutData.md index f45d5f0e7..4e5b631ff 100644 --- a/docs/config/setup/interfaces/mermaid.LayoutData.md +++ b/docs/config/setup/interfaces/mermaid.LayoutData.md @@ -20,7 +20,7 @@ #### Defined in -[packages/mermaid/src/rendering-util/types.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L117) +[packages/mermaid/src/rendering-util/types.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L125) --- @@ -30,7 +30,7 @@ #### Defined in -[packages/mermaid/src/rendering-util/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L116) +[packages/mermaid/src/rendering-util/types.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L124) --- @@ -40,4 +40,4 @@ #### Defined in -[packages/mermaid/src/rendering-util/types.ts:115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L115) +[packages/mermaid/src/rendering-util/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L123) diff --git a/docs/config/setup/interfaces/mermaid.ParseOptions.md b/docs/config/setup/interfaces/mermaid.ParseOptions.md index 54a1dfcaa..52b4af49e 100644 --- a/docs/config/setup/interfaces/mermaid.ParseOptions.md +++ b/docs/config/setup/interfaces/mermaid.ParseOptions.md @@ -19,4 +19,4 @@ The `parseError` function will not be called. #### Defined in -[packages/mermaid/src/types.ts:45](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L45) +[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56) diff --git a/docs/config/setup/interfaces/mermaid.ParseResult.md b/docs/config/setup/interfaces/mermaid.ParseResult.md index e6ff154c8..ef371d094 100644 --- a/docs/config/setup/interfaces/mermaid.ParseResult.md +++ b/docs/config/setup/interfaces/mermaid.ParseResult.md @@ -18,7 +18,7 @@ The config passed as YAML frontmatter or directives #### Defined in -[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56) +[packages/mermaid/src/types.ts:67](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L67) --- @@ -30,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. #### Defined in -[packages/mermaid/src/types.ts:52](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L52) +[packages/mermaid/src/types.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L63) diff --git a/docs/config/setup/interfaces/mermaid.RenderResult.md b/docs/config/setup/interfaces/mermaid.RenderResult.md index 369243331..4c5604022 100644 --- a/docs/config/setup/interfaces/mermaid.RenderResult.md +++ b/docs/config/setup/interfaces/mermaid.RenderResult.md @@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present. #### Defined in -[packages/mermaid/src/types.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L79) +[packages/mermaid/src/types.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L90) --- @@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc. #### Defined in -[packages/mermaid/src/types.ts:69](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L69) +[packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80) --- @@ -63,4 +63,4 @@ The svg code for the rendered graph. #### Defined in -[packages/mermaid/src/types.ts:65](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L65) +[packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L76) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 0a3e15855..68486467c 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[packages/mermaid/src/defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266) +[packages/mermaid/src/defaultConfig.ts:267](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L267) --- diff --git a/docs/ecosystem/integrations-community.md b/docs/ecosystem/integrations-community.md index ef51b423c..9b6b3ccd9 100644 --- a/docs/ecosystem/integrations-community.md +++ b/docs/ecosystem/integrations-community.md @@ -64,7 +64,7 @@ To add an integration to this list, see the [Integrations - create page](./integ - [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅ - [Mermerd](https://github.com/KarnerTh/mermerd) - [Slab](https://slab.com) ✅ -- [Swimm](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-) ✅ +- [Swimm](https://docs.swimm.io/features/diagrams-and-charts) ✅ - [NotesHub](https://noteshub.app) ✅ - [Notion](https://notion.so) ✅ - [Observable](https://observablehq.com/@observablehq/mermaid) ✅ diff --git a/docs/ecosystem/mermaid-chart.md b/docs/ecosystem/mermaid-chart.md index 9b3440a0a..32bf3376f 100644 --- a/docs/ecosystem/mermaid-chart.md +++ b/docs/ecosystem/mermaid-chart.md @@ -12,7 +12,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
-Mermaid Chart - A smarter way to create diagrams | Product Hunt +Mermaid Whiteboard - Drag & Drop your Nodes with Mermaid's new Whiteboard! | Product Hunt ## About @@ -22,17 +22,20 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun - **Editor** - A web based editor for creating and editing Mermaid diagrams. -- **Visual Editor** - The Visual Editor enables users of all skill levels to create diagrams easily and efficiently, with both GUI and code-based editing options. +- **Mermaid AI** - Use our embedded AI Chat to generate diagrams from natural language descriptions. -- **AI Chat** - Use our embedded AI Chat to generate diagrams from natural language descriptions. +- **Whiteboard** - A virtual whiteboard for creating and editing Mermaid diagrams. - **Plugins** - A plugin system for extending the functionality of Mermaid. Official Mermaid Chart plugins: - [Mermaid Chart GPT](https://chat.openai.com/g/g-1IRFKwq4G-mermaid-chart) + - [Confluence](https://marketplace.atlassian.com/apps/1234056/mermaid-chart-for-confluence?hosting=cloud&tab=overview) + - [Jira](https://marketplace.atlassian.com/apps/1234810/mermaid-chart-for-jira?tab=overview&hosting=cloud) - [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart) - [JetBrains IDE](https://plugins.jetbrains.com/plugin/23043-mermaid-chart) + - [Google Docs](https://gsuite.google.com/marketplace/app/mermaidchart/947683068472) - [Microsoft PowerPoint and Word](https://appsource.microsoft.com/en-us/product/office/WA200006214?tab=Overview) Visit our [Plugins](https://www.mermaidchart.com/plugins) page for more information. diff --git a/docs/news/blog.md b/docs/news/blog.md index 4d33f67f5..589665254 100644 --- a/docs/news/blog.md +++ b/docs/news/blog.md @@ -6,6 +6,12 @@ # Blog +## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/) + +24 September 2024 · 5 mins + +Discover 30 new shapes in Mermaid flowcharts, offering enhanced clarity, customization, and versatility for more dynamic and expressive visualizations. + ## [Introducing Architecture Diagrams in Mermaid](https://www.mermaidchart.com/blog/posts/mermaid-supports-architecture-diagrams/) 2 September 2024 · 2 mins diff --git a/docs/syntax/architecture.md b/docs/syntax/architecture.md index 12a7cf409..f0f0e9ac7 100644 --- a/docs/syntax/architecture.md +++ b/docs/syntax/architecture.md @@ -79,15 +79,15 @@ service {service id}({icon name})[{title}] (in {parent id})? Put together: ``` -service database(db)[Database] +service database1(database)[My Database] ``` -creates the service identified as `database`, using the icon `db`, with the label `Database`. +creates the service identified as `database1`, using the icon `database`, with the label `My Database`. If the service belongs to a group, it can be placed inside it through the optional `in` keyword ``` -service database(db)[Database] in private_api +service database1(database)[My Database] in private_api ``` ### Edges @@ -194,55 +194,7 @@ architecture-beta ## Icons By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`. -Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below. - -The icon packs available can be found at [icones.js.org](https://icones.js.org/). -We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram. - -Using JSON file directly from CDN: - -```js -import mermaid from 'CDN/mermaid.esm.mjs'; -mermaid.registerIconPacks([ - { - name: 'logos', - loader: () => - fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()), - }, -]); -``` - -Using packages and a bundler: - -```bash -npm install @iconify-json/logos -``` - -With lazy loading - -```js -import mermaid from 'mermaid'; - -mermaid.registerIconPacks([ - { - name: 'logos', - loader: () => import('@iconify-json/logos').then((module) => module.icons), - }, -]); -``` - -Without lazy loading - -```js -import mermaid from 'mermaid'; -import { icons } from '@iconify-json/logos'; -mermaid.registerIconPacks([ - { - name: icons.prefix, // To use the prefix defined in the icon pack - icons, - }, -]); -``` +Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps [here](../config/icons.md). After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack. diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index 7efc5497b..3837e77de 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -298,6 +298,694 @@ flowchart TD id1(((This is the text in the circle))) ``` +## Expanded Node Shapes in Mermaid Flowcharts (v11.3.0+) + +Mermaid introduces 30 new shapes to enhance the flexibility and precision of flowchart creation. These new shapes provide more options to represent processes, decisions, events, data storage visually, and other elements within your flowcharts, improving clarity and semantic meaning. + +New Syntax for Shape Definition + +Mermaid now supports a general syntax for defining shape types to accommodate the growing number of shapes. This syntax allows you to assign specific shapes to nodes using a clear and flexible format: + +``` +A@{ shape: rect } +``` + +This syntax creates a node A as a rectangle. It renders in the same way as `A["A"]`, or `A`. + +### Complete List of New Shapes + +Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases: + +| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** | +| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- | +| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` | +| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` | +| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` | +| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` | +| Comment Right | Curly Brace | `brace-r` | Adds a comment | | +| Comment with braces on both sides | Curly Braces | `braces` | Adds a comment | | +| Data Input/Output | Lean Right | `lean-r` | Represents input or output | `in-out`, `lean-right` | +| Data Input/Output | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` | +| Database | Cylinder | `cyl` | Database storage | `cylinder`, `database`, `db` | +| Decision | Diamond | `diam` | Decision-making step | `decision`, `diamond`, `question` | +| Delay | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` | +| Direct Access Storage | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` | +| Disk Storage | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` | +| Display | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` | +| Divided Process | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-process`, `divided-rectangle` | +| Document | Document | `doc` | Represents a document | `doc`, `document` | +| Event | Rounded Rectangle | `rounded` | Represents an event | `event` | +| Extract | Triangle | `tri` | Extraction process | `extract`, `triangle` | +| Fork/Join | Filled Rectangle | `fork` | Fork or join in process flow | `join` | +| Internal Storage | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` | +| Junction | Filled Circle | `f-circ` | Junction point | `filled-circle`, `junction` | +| Lined Document | Lined Document | `lin-doc` | Lined document | `lined-document` | +| Lined/Shaded Process | Lined Rectangle | `lin-rect` | Lined process shape | `lin-proc`, `lined-process`, `lined-rectangle`, `shaded-process` | +| Loop Limit | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` | +| Manual File | Flipped Triangle | `flip-tri` | Manual file operation | `flipped-triangle`, `manual-file` | +| Manual Input | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` | +| Manual Operation | Trapezoid Base Top | `trap-t` | Represents a manual task | `inv-trapezoid`, `manual`, `trapezoid-top` | +| Multi-Document | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` | +| Multi-Process | Stacked Rectangle | `st-rect` | Multiple processes | `processes`, `procs`, `stacked-rectangle` | +| Odd | Odd | `odd` | Odd shape | | +| Paper Tape | Flag | `flag` | Paper tape | `paper-tape` | +| Prepare Conditional | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` | +| Priority Action | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid`, `trapezoid-bottom` | +| Process | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` | +| Start | Circle | `circle` | Starting point | `circ` | +| Start | Small Circle | `sm-circ` | Small starting point | `small-circle`, `start` | +| Stop | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` | +| Stop | Framed Circle | `fr-circ` | Stop point | `framed-circle`, `stop` | +| Stored Data | Bow Tie Rectangle | `bow-rect` | Stored data | `bow-tie-rectangle`, `stored-data` | +| Subprocess | Framed Rectangle | `fr-rect` | Subprocess | `framed-rectangle`, `subproc`, `subprocess`, `subroutine` | +| Summary | Crossed Circle | `cross-circ` | Summary | `crossed-circle`, `summary` | +| Tagged Document | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` | +| Tagged Process | Tagged Rectangle | `tag-rect` | Tagged process | `tag-proc`, `tagged-process`, `tagged-rectangle` | +| Terminal Point | Stadium | `stadium` | Terminal point | `pill`, `terminal` | +| Text Block | Text Block | `text` | Text block | | + +### Example Flowchart with New Shapes + +Here’s an example flowchart that utilizes some of the newly introduced shapes: + +```mermaid-example +flowchart RL + A@{ shape: manual-file, label: "File Handling"} + B@{ shape: manual-input, label: "User Input"} + C@{ shape: docs, label: "Multiple Documents"} + D@{ shape: procs, label: "Process Automation"} + E@{ shape: paper-tape, label: "Paper Records"} +``` + +```mermaid +flowchart RL + A@{ shape: manual-file, label: "File Handling"} + B@{ shape: manual-input, label: "User Input"} + C@{ shape: docs, label: "Multiple Documents"} + D@{ shape: procs, label: "Process Automation"} + E@{ shape: paper-tape, label: "Paper Records"} +``` + +### Process + +```mermaid-example +flowchart TD + A@{ shape: rect, label: "This is a process" } +``` + +```mermaid +flowchart TD + A@{ shape: rect, label: "This is a process" } +``` + +### Event + +```mermaid-example +flowchart TD + A@{ shape: rounded, label: "This is an event" } +``` + +```mermaid +flowchart TD + A@{ shape: rounded, label: "This is an event" } +``` + +### Terminal Point (Stadium) + +```mermaid-example +flowchart TD + A@{ shape: stadium, label: "Terminal point" } +``` + +```mermaid +flowchart TD + A@{ shape: stadium, label: "Terminal point" } +``` + +### Subprocess + +```mermaid-example +flowchart TD + A@{ shape: subproc, label: "This is a subprocess" } +``` + +```mermaid +flowchart TD + A@{ shape: subproc, label: "This is a subprocess" } +``` + +### Database (Cylinder) + +```mermaid-example +flowchart TD + A@{ shape: cyl, label: "Database" } +``` + +```mermaid +flowchart TD + A@{ shape: cyl, label: "Database" } +``` + +### Start (Circle) + +```mermaid-example +flowchart TD + A@{ shape: circle, label: "Start" } +``` + +```mermaid +flowchart TD + A@{ shape: circle, label: "Start" } +``` + +### Odd + +```mermaid-example +flowchart TD + A@{ shape: odd, label: "Odd shape" } +``` + +```mermaid +flowchart TD + A@{ shape: odd, label: "Odd shape" } +``` + +### Decision (Diamond) + +```mermaid-example +flowchart TD + A@{ shape: diamond, label: "Decision" } +``` + +```mermaid +flowchart TD + A@{ shape: diamond, label: "Decision" } +``` + +### Prepare Conditional (Hexagon) + +```mermaid-example +flowchart TD + A@{ shape: hex, label: "Prepare conditional" } +``` + +```mermaid +flowchart TD + A@{ shape: hex, label: "Prepare conditional" } +``` + +### Data Input/Output (Lean Right) + +```mermaid-example +flowchart TD + A@{ shape: lean-r, label: "Input/Output" } +``` + +```mermaid +flowchart TD + A@{ shape: lean-r, label: "Input/Output" } +``` + +### Data Input/Output (Lean Left) + +```mermaid-example +flowchart TD + A@{ shape: lean-l, label: "Output/Input" } +``` + +```mermaid +flowchart TD + A@{ shape: lean-l, label: "Output/Input" } +``` + +### Priority Action (Trapezoid Base Bottom) + +```mermaid-example +flowchart TD + A@{ shape: trap-b, label: "Priority action" } +``` + +```mermaid +flowchart TD + A@{ shape: trap-b, label: "Priority action" } +``` + +### Manual Operation (Trapezoid Base Top) + +```mermaid-example +flowchart TD + A@{ shape: trap-t, label: "Manual operation" } +``` + +```mermaid +flowchart TD + A@{ shape: trap-t, label: "Manual operation" } +``` + +### Stop (Double Circle) + +```mermaid-example +flowchart TD + A@{ shape: dbl-circ, label: "Stop" } +``` + +```mermaid +flowchart TD + A@{ shape: dbl-circ, label: "Stop" } +``` + +### Text Block + +```mermaid-example +flowchart TD + A@{ shape: text, label: "This is a text block" } +``` + +```mermaid +flowchart TD + A@{ shape: text, label: "This is a text block" } +``` + +### Card (Notched Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: notch-rect, label: "Card" } +``` + +```mermaid +flowchart TD + A@{ shape: notch-rect, label: "Card" } +``` + +### Lined/Shaded Process + +```mermaid-example +flowchart TD + A@{ shape: lin-rect, label: "Lined process" } +``` + +```mermaid +flowchart TD + A@{ shape: lin-rect, label: "Lined process" } +``` + +### Start (Small Circle) + +```mermaid-example +flowchart TD + A@{ shape: sm-circ, label: "Small start" } +``` + +```mermaid +flowchart TD + A@{ shape: sm-circ, label: "Small start" } +``` + +### Stop (Framed Circle) + +```mermaid-example +flowchart TD + A@{ shape: framed-circle, label: "Stop" } +``` + +```mermaid +flowchart TD + A@{ shape: framed-circle, label: "Stop" } +``` + +### Fork/Join (Long Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: fork, label: "Fork or Join" } +``` + +```mermaid +flowchart TD + A@{ shape: fork, label: "Fork or Join" } +``` + +### Collate (Hourglass) + +```mermaid-example +flowchart TD + A@{ shape: hourglass, label: "Collate" } +``` + +```mermaid +flowchart TD + A@{ shape: hourglass, label: "Collate" } +``` + +### Comment (Curly Brace) + +```mermaid-example +flowchart TD + A@{ shape: comment, label: "Comment" } +``` + +```mermaid +flowchart TD + A@{ shape: comment, label: "Comment" } +``` + +### Comment Right (Curly Brace Right) + +```mermaid-example +flowchart TD + A@{ shape: brace-r, label: "Comment" } +``` + +```mermaid +flowchart TD + A@{ shape: brace-r, label: "Comment" } +``` + +### Comment with braces on both sides + +```mermaid-example +flowchart TD + A@{ shape: braces, label: "Comment" } +``` + +```mermaid +flowchart TD + A@{ shape: braces, label: "Comment" } +``` + +### Com Link (Lightning Bolt) + +```mermaid-example +flowchart TD + A@{ shape: bolt, label: "Communication link" } +``` + +```mermaid +flowchart TD + A@{ shape: bolt, label: "Communication link" } +``` + +### Document + +```mermaid-example +flowchart TD + A@{ shape: doc, label: "Document" } +``` + +```mermaid +flowchart TD + A@{ shape: doc, label: "Document" } +``` + +### Delay (Half-Rounded Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: delay, label: "Delay" } +``` + +```mermaid +flowchart TD + A@{ shape: delay, label: "Delay" } +``` + +### Direct Access Storage (Horizontal Cylinder) + +```mermaid-example +flowchart TD + A@{ shape: das, label: "Direct access storage" } +``` + +```mermaid +flowchart TD + A@{ shape: das, label: "Direct access storage" } +``` + +### Disk Storage (Lined Cylinder) + +```mermaid-example +flowchart TD + A@{ shape: lin-cyl, label: "Disk storage" } +``` + +```mermaid +flowchart TD + A@{ shape: lin-cyl, label: "Disk storage" } +``` + +### Display (Curved Trapezoid) + +```mermaid-example +flowchart TD + A@{ shape: curv-trap, label: "Display" } +``` + +```mermaid +flowchart TD + A@{ shape: curv-trap, label: "Display" } +``` + +### Divided Process (Divided Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: div-rect, label: "Divided process" } +``` + +```mermaid +flowchart TD + A@{ shape: div-rect, label: "Divided process" } +``` + +### Extract (Small Triangle) + +```mermaid-example +flowchart TD + A@{ shape: tri, label: "Extract" } +``` + +```mermaid +flowchart TD + A@{ shape: tri, label: "Extract" } +``` + +### Internal Storage (Window Pane) + +```mermaid-example +flowchart TD + A@{ shape: win-pane, label: "Internal storage" } +``` + +```mermaid +flowchart TD + A@{ shape: win-pane, label: "Internal storage" } +``` + +### Junction (Filled Circle) + +```mermaid-example +flowchart TD + A@{ shape: f-circ, label: "Junction" } +``` + +```mermaid +flowchart TD + A@{ shape: f-circ, label: "Junction" } +``` + +### Lined Document + +```mermaid-example +flowchart TD + A@{ shape: lin-doc, label: "Lined document" } +``` + +```mermaid +flowchart TD + A@{ shape: lin-doc, label: "Lined document" } +``` + +### Loop Limit (Notched Pentagon) + +```mermaid-example +flowchart TD + A@{ shape: notch-pent, label: "Loop limit" } +``` + +```mermaid +flowchart TD + A@{ shape: notch-pent, label: "Loop limit" } +``` + +### Manual File (Flipped Triangle) + +```mermaid-example +flowchart TD + A@{ shape: flip-tri, label: "Manual file" } +``` + +```mermaid +flowchart TD + A@{ shape: flip-tri, label: "Manual file" } +``` + +### Manual Input (Sloped Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: sl-rect, label: "Manual input" } +``` + +```mermaid +flowchart TD + A@{ shape: sl-rect, label: "Manual input" } +``` + +### Multi-Document (Stacked Document) + +```mermaid-example +flowchart TD + A@{ shape: docs, label: "Multiple documents" } +``` + +```mermaid +flowchart TD + A@{ shape: docs, label: "Multiple documents" } +``` + +### Multi-Process (Stacked Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: processes, label: "Multiple processes" } +``` + +```mermaid +flowchart TD + A@{ shape: processes, label: "Multiple processes" } +``` + +### Paper Tape (Flag) + +```mermaid-example +flowchart TD + A@{ shape: flag, label: "Paper tape" } +``` + +```mermaid +flowchart TD + A@{ shape: flag, label: "Paper tape" } +``` + +### Stored Data (Bow Tie Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: bow-rect, label: "Stored data" } +``` + +```mermaid +flowchart TD + A@{ shape: bow-rect, label: "Stored data" } +``` + +### Summary (Crossed Circle) + +```mermaid-example +flowchart TD + A@{ shape: cross-circ, label: "Summary" } +``` + +```mermaid +flowchart TD + A@{ shape: cross-circ, label: "Summary" } +``` + +### Tagged Document + +```mermaid-example +flowchart TD + A@{ shape: tag-doc, label: "Tagged document" } +``` + +```mermaid +flowchart TD + A@{ shape: tag-doc, label: "Tagged document" } +``` + +### Tagged Process (Tagged Rectangle) + +```mermaid-example +flowchart TD + A@{ shape: tag-rect, label: "Tagged process" } +``` + +```mermaid +flowchart TD + A@{ shape: tag-rect, label: "Tagged process" } +``` + +## Special shapes in Mermaid Flowcharts (v11.3.0+) + +Mermaid also introduces 2 special shapes to enhance your flowcharts: **icon** and **image**. These shapes allow you to include icons and images directly within your flowcharts, providing more visual context and clarity. + +### Icon Shape + +You can use the `icon` shape to include an icon in your flowchart. To use icons, you need to register the icon pack first. Follow the instructions provided [here](../config/icons.md). The syntax for defining an icon shape is as follows: + +```mermaid-example +flowchart TD + A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 } +``` + +```mermaid +flowchart TD + A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 } +``` + +### Parameters + +- **icon**: The name of the icon from the registered icon pack. +- **form**: Specifies the background shape of the icon. If not defined there will be no background to icon. Options include: + - `square` + - `circle` + - `rounded` +- **label**: The text label associated with the icon. This can be any string. If not defined, no label will be displayed. +- **pos**: The position of the label. If not defined label will default to bottom of icon. Possible values are: + - `t` + - `b` +- **h**: The height of the icon. If not defined this will default to 48 which is minimum. + +### Image Shape + +You can use the `image` shape to include an image in your flowchart. The syntax for defining an image shape is as follows: + +```mermaid-example +flowchart TD + A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" } +``` + +```mermaid +flowchart TD + A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" } +``` + +### Parameters + +- **img**: The URL of the image to be displayed. +- **label**: The text label associated with the image. This can be any string. If not defined, no label will be displayed. +- **pos**: The position of the label. If not defined, the label will default to the bottom of the image. Possible values are: + - `t` + - `b` +- **w**: The width of the image. If not defined, this will default to the natural width of the image. +- **h**: The height of the image. If not defined, this will default to the natural height of the image. +- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are: + - `on` + - `off` + +These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging. + ## Links between nodes Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link. diff --git a/package.json b/package.json index 9b592394d..c4c692d85 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ }, "devDependencies": { "@applitools/eyes-cypress": "^3.44.4", - "@argos-ci/cypress": "^2.1.0", + "@argos-ci/cypress": "^2.2.2", "@changesets/changelog-github": "^0.5.0", "@changesets/cli": "^2.27.7", "@cspell/eslint-plugin": "^8.8.4", @@ -91,6 +91,7 @@ "cspell": "^8.6.0", "cypress": "^13.14.1", "cypress-image-snapshot": "^4.0.1", + "cypress-split": "^1.24.0", "esbuild": "^0.21.5", "eslint": "^9.4.0", "eslint-config-prettier": "^9.1.0", @@ -103,7 +104,7 @@ "eslint-plugin-markdown": "^5.0.0", "eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-tsdoc": "^0.3.0", - "eslint-plugin-unicorn": "^55.0.0", + "eslint-plugin-unicorn": "^56.0.0", "express": "^4.19.1", "globals": "^15.4.0", "globby": "^14.0.1", @@ -131,5 +132,10 @@ }, "nyc": { "report-dir": "coverage/cypress" + }, + "pnpm": { + "patchedDependencies": { + "roughjs": "patches/roughjs.patch" + } } } diff --git a/packages/mermaid-layout-elk/CHANGELOG.md b/packages/mermaid-layout-elk/CHANGELOG.md index f1a025d7d..ce023eb65 100644 --- a/packages/mermaid-layout-elk/CHANGELOG.md +++ b/packages/mermaid-layout-elk/CHANGELOG.md @@ -1,5 +1,14 @@ # @mermaid-js/layout-elk +## 0.1.5 + +### Patch Changes + +- [#5825](https://github.com/mermaid-js/mermaid/pull/5825) [`233e36c`](https://github.com/mermaid-js/mermaid/commit/233e36c9884fcce141a72ce7c845179781e18632) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - chore: Update render options + +- Updated dependencies [[`6c5b7ce`](https://github.com/mermaid-js/mermaid/commit/6c5b7ce9f41c0fbd59fe03dbefc8418d97697f0a), [`9e3aa70`](https://github.com/mermaid-js/mermaid/commit/9e3aa705ae21fd4898504ab22d775a9e437b898e), [`de2c05c`](https://github.com/mermaid-js/mermaid/commit/de2c05cd5463af68d19dd7b6b3f1303d69ddb2dd)]: + - mermaid@11.3.0 + ## 0.1.4 ### Patch Changes diff --git a/packages/mermaid-layout-elk/package.json b/packages/mermaid-layout-elk/package.json index e411de278..26e06b2b1 100644 --- a/packages/mermaid-layout-elk/package.json +++ b/packages/mermaid-layout-elk/package.json @@ -1,6 +1,6 @@ { "name": "@mermaid-js/layout-elk", - "version": "0.1.4", + "version": "0.1.5", "description": "ELK layout engine for mermaid", "module": "dist/mermaid-layout-elk.core.mjs", "types": "dist/layouts.d.ts", diff --git a/packages/mermaid-layout-elk/src/render.ts b/packages/mermaid-layout-elk/src/render.ts index e647422ec..4e9f66a81 100644 --- a/packages/mermaid-layout-elk/src/render.ts +++ b/packages/mermaid-layout-elk/src/render.ts @@ -33,10 +33,11 @@ export const render = async ( }; graph.children.push(child); nodeDb[node.id] = child; + const config = getConfig(); // Add the element to the DOM if (!node.isGroup) { - const childNodeEl = await insertNode(nodeEl, node, node.dir); + const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir }); boundingBox = childNodeEl.node().getBBox(); child.domId = childNodeEl; child.width = boundingBox.width; @@ -50,7 +51,7 @@ export const render = async ( // @ts-ignore TODO: fix this const { shapeSvg, bbox } = await labelHelper(nodeEl, node, undefined, true); labelData.width = bbox.width; - labelData.wrappingWidth = getConfig().flowchart!.wrappingWidth; + labelData.wrappingWidth = config.flowchart!.wrappingWidth; // Give some padding for elk labelData.height = bbox.height - 2; labelData.labelNode = shapeSvg.node(); diff --git a/packages/mermaid/CHANGELOG.md b/packages/mermaid/CHANGELOG.md index c810d2798..733108172 100644 --- a/packages/mermaid/CHANGELOG.md +++ b/packages/mermaid/CHANGELOG.md @@ -1,5 +1,35 @@ # mermaid +## 11.3.0 + +### Minor Changes + +- [#5825](https://github.com/mermaid-js/mermaid/pull/5825) [`9e3aa70`](https://github.com/mermaid-js/mermaid/commit/9e3aa705ae21fd4898504ab22d775a9e437b898e) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - New Flowchart Shapes (with new syntax) + +### Patch Changes + +- [#5849](https://github.com/mermaid-js/mermaid/pull/5849) [`6c5b7ce`](https://github.com/mermaid-js/mermaid/commit/6c5b7ce9f41c0fbd59fe03dbefc8418d97697f0a) Thanks [@ReneLombard](https://github.com/ReneLombard)! - Fixed an issue when the mermaid classdiagram crashes when adding a . to the namespace. + Forexample + + ```mermaid + + classDiagram + namespace Company.Project.Module { + class GenericClass~T~ { + +addItem(item: T) + +getItem() T + } + } + ``` + +- [#5914](https://github.com/mermaid-js/mermaid/pull/5914) [`de2c05c`](https://github.com/mermaid-js/mermaid/commit/de2c05cd5463af68d19dd7b6b3f1303d69ddb2dd) Thanks [@aloisklink](https://github.com/aloisklink)! - Ban DOMPurify v3.1.7 as a dependency + +## 11.2.1 + +### Patch Changes + +- [#5856](https://github.com/mermaid-js/mermaid/pull/5856) [`bfd8c63`](https://github.com/mermaid-js/mermaid/commit/bfd8c63daaa8420e57da9953922b9f0c94123064) Thanks [@knsv](https://github.com/knsv)! - Fix for issue with calculation of label width when using in firefox + ## 11.2.0 ### Minor Changes diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 9c0756b81..afee6aab5 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,6 +1,6 @@ { "name": "mermaid", - "version": "11.2.0", + "version": "11.3.0", "description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.", "type": "module", "module": "./dist/mermaid.core.mjs", @@ -35,8 +35,8 @@ "clean": "rimraf dist", "dev": "pnpm -w dev", "docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaid.ts && prettier --write ./src/docs/config/setup", - "docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts", - "docs:verify": "pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts --verify", + "docs:build": "rimraf ../../docs && pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts", + "docs:verify": "pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts --verify", "docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && tsx scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts", "docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing", "docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"tsx scripts/docs.cli.mts --watch --vitepress\"", @@ -70,12 +70,14 @@ "@braintree/sanitize-url": "^7.0.1", "@iconify/utils": "^2.1.32", "@mermaid-js/parser": "workspace:^", + "@types/d3": "^7.4.3", + "@types/dompurify": "^3.0.5", "cytoscape": "^3.29.2", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.10", + "dagre-d3-es": "7.0.11", "dayjs": "^1.11.10", "dompurify": "^3.0.11 <3.1.7", "katex": "^0.16.9", @@ -92,13 +94,11 @@ "@iconify/types": "^2.0.0", "@types/cytoscape": "^3.21.4", "@types/cytoscape-fcose": "^2.2.4", - "@types/d3": "^7.4.3", "@types/d3-sankey": "^0.12.4", "@types/d3-scale": "^4.0.8", "@types/d3-scale-chromatic": "^3.0.3", "@types/d3-selection": "^3.0.10", "@types/d3-shape": "^3.1.6", - "@types/dompurify": "^3.0.5", "@types/jsdom": "^21.1.6", "@types/katex": "^0.16.7", "@types/lodash-es": "^4.17.12", diff --git a/packages/mermaid/scripts/docs.mts b/packages/mermaid/scripts/docs.mts index 374e78870..073a3c1a9 100644 --- a/packages/mermaid/scripts/docs.mts +++ b/packages/mermaid/scripts/docs.mts @@ -41,7 +41,8 @@ import { exec } from 'child_process'; import { globby } from 'globby'; import { JSDOM } from 'jsdom'; import { dump, load, JSON_SCHEMA } from 'js-yaml'; -import type { Code, ListItem, Root, Text, YAML } from 'mdast'; +import type { Code, ListItem, PhrasingContent, Root, Text, YAML } from 'mdast'; +import { register } from 'node:module'; import { posix, dirname, relative, join } from 'path'; import prettier from 'prettier'; import { remark } from 'remark'; @@ -53,6 +54,10 @@ import mm from 'micromatch'; import flatmap from 'unist-util-flatmap'; import { visit } from 'unist-util-visit'; +// short-circuit `.schema.yaml` imports, so that we can safely import `shapes.js` +register('./loadHook.mjs', import.meta.url); +const { shapesDefs } = await import('../src/rendering-util/rendering-elements/shapes.js'); + export const MERMAID_RELEASE_VERSION = JSON.parse(readFileSync('../mermaid/package.json', 'utf8')) .version as string; const MERMAID_MAJOR_VERSION = MERMAID_RELEASE_VERSION.split('.')[0]; @@ -103,6 +108,60 @@ const generateHeader = (file: string): string => { > ## Please edit the corresponding file in [${filePathFromRoot}](${sourcePathRelativeToGenerated}).`; }; +/** + * Builds a markdown list of shapes supported in flowcharts. + */ +export function buildShapeDoc() { + const data = shapesDefs + .sort((a, b) => a.semanticName.localeCompare(b.semanticName)) + .map((shape): PhrasingContent[][] => { + const { name, semanticName, description, shortName, aliases = [] } = shape; + return [ + [{ type: 'text', value: semanticName }], + [{ type: 'text', value: name }], + [{ type: 'inlineCode', value: shortName }], + [{ type: 'text', value: description }], + aliases.sort().flatMap((alias, index) => [ + ...(index !== 0 ? ([{ type: 'text', value: ', ' }] as const) : []), + { + type: 'inlineCode', + value: alias, + }, + ]), + ]; + }); + + // don't prettify this table, since we'd do it later + return remark() + .use(remarkGfm) + .stringify({ + type: 'root', + children: [ + { + type: 'table', + children: [ + ['Semantic Name', 'Shape Name', 'Short Name', 'Description', 'Alias Supported'].map( + (s): PhrasingContent[] => [ + { + type: 'strong', + children: [{ type: 'text', value: s }], + }, + ] + ), + ...data, + ].map((row) => ({ + type: 'tableRow', + children: row.map((cell) => ({ + type: 'tableCell', + children: cell, + })), + })), + }, + ], + }) + .toString(); +} + /** * Given a source file name and path, return the documentation destination full path and file name * Create the destination path if it does not already exist. @@ -192,10 +251,22 @@ export const transformToBlockQuote = ( const injectPlaceholders = (text: string): string => text.replace(//g, MERMAID_MAJOR_VERSION).replace(//g, CDN_URL); +const virtualGenerators: Record string> = { + shapesTable: buildShapeDoc, +}; + const transformIncludeStatements = (file: string, text: string): string => { // resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76 - return text.replace(includesRE, (m, m1) => { + return text.replace(includesRE, (m, m1: string) => { try { + if (m1.startsWith('virtual:')) { + const key = m1.replace('virtual:', ''); + const generator = virtualGenerators[key]; + if (!generator) { + throw new Error(`Unknown virtual generator: ${key} in "${file}"`); + } + return generator(); + } const includePath = join(dirname(file), m1).replaceAll('\\', '/'); const content = readSyncedUTF8file(includePath); includedFiles.add(changeToFinalDocDir(includePath)); diff --git a/packages/mermaid/scripts/docs.spec.ts b/packages/mermaid/scripts/docs.spec.ts index c84bc1bac..68677d4c9 100644 --- a/packages/mermaid/scripts/docs.spec.ts +++ b/packages/mermaid/scripts/docs.spec.ts @@ -1,4 +1,4 @@ -import { transformMarkdownAst, transformToBlockQuote } from './docs.mjs'; +import { buildShapeDoc, transformMarkdownAst, transformToBlockQuote } from './docs.mjs'; import { remark } from 'remark'; // import it this way so we can mock it import remarkFrontmatter from 'remark-frontmatter'; @@ -165,4 +165,59 @@ This Markdown should be kept. }); }); }); + + describe('buildShapeDoc', () => { + it('should build shapesTable based on the shapeDefs', () => { + expect(buildShapeDoc()).toMatchInlineSnapshot(` + "| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** | + | --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- | + | Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` | + | Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` | + | Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` | + | Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` | + | Comment Right | Curly Brace | \`brace-r\` | Adds a comment | | + | Comment with braces on both sides | Curly Braces | \`braces\` | Adds a comment | | + | Data Input/Output | Lean Right | \`lean-r\` | Represents input or output | \`in-out\`, \`lean-right\` | + | Data Input/Output | Lean Left | \`lean-l\` | Represents output or input | \`lean-left\`, \`out-in\` | + | Database | Cylinder | \`cyl\` | Database storage | \`cylinder\`, \`database\`, \`db\` | + | Decision | Diamond | \`diam\` | Decision-making step | \`decision\`, \`diamond\`, \`question\` | + | Delay | Half-Rounded Rectangle | \`delay\` | Represents a delay | \`half-rounded-rectangle\` | + | Direct Access Storage | Horizontal Cylinder | \`h-cyl\` | Direct access storage | \`das\`, \`horizontal-cylinder\` | + | Disk Storage | Lined Cylinder | \`lin-cyl\` | Disk storage | \`disk\`, \`lined-cylinder\` | + | Display | Curved Trapezoid | \`curv-trap\` | Represents a display | \`curved-trapezoid\`, \`display\` | + | Divided Process | Divided Rectangle | \`div-rect\` | Divided process shape | \`div-proc\`, \`divided-process\`, \`divided-rectangle\` | + | Document | Document | \`doc\` | Represents a document | \`doc\`, \`document\` | + | Event | Rounded Rectangle | \`rounded\` | Represents an event | \`event\` | + | Extract | Triangle | \`tri\` | Extraction process | \`extract\`, \`triangle\` | + | Fork/Join | Filled Rectangle | \`fork\` | Fork or join in process flow | \`join\` | + | Internal Storage | Window Pane | \`win-pane\` | Internal storage | \`internal-storage\`, \`window-pane\` | + | Junction | Filled Circle | \`f-circ\` | Junction point | \`filled-circle\`, \`junction\` | + | Lined Document | Lined Document | \`lin-doc\` | Lined document | \`lined-document\` | + | Lined/Shaded Process | Lined Rectangle | \`lin-rect\` | Lined process shape | \`lin-proc\`, \`lined-process\`, \`lined-rectangle\`, \`shaded-process\` | + | Loop Limit | Trapezoidal Pentagon | \`notch-pent\` | Loop limit step | \`loop-limit\`, \`notched-pentagon\` | + | Manual File | Flipped Triangle | \`flip-tri\` | Manual file operation | \`flipped-triangle\`, \`manual-file\` | + | Manual Input | Sloped Rectangle | \`sl-rect\` | Manual input step | \`manual-input\`, \`sloped-rectangle\` | + | Manual Operation | Trapezoid Base Top | \`trap-t\` | Represents a manual task | \`inv-trapezoid\`, \`manual\`, \`trapezoid-top\` | + | Multi-Document | Stacked Document | \`docs\` | Multiple documents | \`documents\`, \`st-doc\`, \`stacked-document\` | + | Multi-Process | Stacked Rectangle | \`st-rect\` | Multiple processes | \`processes\`, \`procs\`, \`stacked-rectangle\` | + | Odd | Odd | \`odd\` | Odd shape | | + | Paper Tape | Flag | \`flag\` | Paper tape | \`paper-tape\` | + | Prepare Conditional | Hexagon | \`hex\` | Preparation or condition step | \`hexagon\`, \`prepare\` | + | Priority Action | Trapezoid Base Bottom | \`trap-b\` | Priority action | \`priority\`, \`trapezoid\`, \`trapezoid-bottom\` | + | Process | Rectangle | \`rect\` | Standard process shape | \`proc\`, \`process\`, \`rectangle\` | + | Start | Circle | \`circle\` | Starting point | \`circ\` | + | Start | Small Circle | \`sm-circ\` | Small starting point | \`small-circle\`, \`start\` | + | Stop | Double Circle | \`dbl-circ\` | Represents a stop point | \`double-circle\` | + | Stop | Framed Circle | \`fr-circ\` | Stop point | \`framed-circle\`, \`stop\` | + | Stored Data | Bow Tie Rectangle | \`bow-rect\` | Stored data | \`bow-tie-rectangle\`, \`stored-data\` | + | Subprocess | Framed Rectangle | \`fr-rect\` | Subprocess | \`framed-rectangle\`, \`subproc\`, \`subprocess\`, \`subroutine\` | + | Summary | Crossed Circle | \`cross-circ\` | Summary | \`crossed-circle\`, \`summary\` | + | Tagged Document | Tagged Document | \`tag-doc\` | Tagged document | \`tag-doc\`, \`tagged-document\` | + | Tagged Process | Tagged Rectangle | \`tag-rect\` | Tagged process | \`tag-proc\`, \`tagged-process\`, \`tagged-rectangle\` | + | Terminal Point | Stadium | \`stadium\` | Terminal point | \`pill\`, \`terminal\` | + | Text Block | Text Block | \`text\` | Text block | | + " + `); + }); + }); }); diff --git a/packages/mermaid/scripts/loadHook.mjs b/packages/mermaid/scripts/loadHook.mjs new file mode 100644 index 000000000..50129861b --- /dev/null +++ b/packages/mermaid/scripts/loadHook.mjs @@ -0,0 +1,22 @@ +import { fileURLToPath } from 'node:url'; +/** @import import { LoadHook } from "node:module"; */ +/** + * @type {LoadHook} + * + * Load hook that short circuits the loading of `.schema.yaml` files with `export default {}`. + * These would normally be loaded using ESBuild, but that doesn't work for these local scripts. + * + * @see https://nodejs.org/api/module.html#loadurl-context-nextload + */ +export const load = async (url, context, nextLoad) => { + const filePath = url.startsWith('file://') ? fileURLToPath(url) : url; + if (filePath.endsWith('.schema.yaml')) { + return { + format: 'module', + shortCircuit: true, + source: `export default {}`, + }; + } else { + return await nextLoad(url, context); + } +}; diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index fa2b70ca2..86ae7e284 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -87,7 +87,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit // insertCluster(clusters, graph.node(v)); } else { log.info('Node - the non recursive path', v, node.id, node); - await insertNode(nodes, graph.node(v), dir); + await insertNode(nodes, graph.node(v), { config: siteConfig, dir }); } } }) diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index b841064b6..2677fd785 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -1131,7 +1131,7 @@ const shapes = { let nodeElems = {}; -export const insertNode = async (elem, node, dir) => { +export const insertNode = async (elem, node, renderOptions) => { let newEl; let el; @@ -1144,9 +1144,9 @@ export const insertNode = async (elem, node, dir) => { target = node.linkTarget || '_blank'; } newEl = elem.insert('svg:a').attr('xlink:href', node.link).attr('target', target); - el = await shapes[node.shape](newEl, node, dir); + el = await shapes[node.shape](newEl, node, renderOptions); } else { - el = await shapes[node.shape](elem, node, dir); + el = await shapes[node.shape](elem, node, renderOptions); newEl = el; } if (node.tooltip) { diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 97f3e0bb1..feae37f52 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -21,8 +21,9 @@ const config: RequiredDeep = { // TODO: Should we replace these with `null` so that they can go in the JSON Schema? deterministicIDSeed: undefined, elk: { + // mergeEdges is needed here to be considered mergeEdges: false, - nodePlacementStrategy: 'SIMPLE', + nodePlacementStrategy: 'BRANDES_KOEPF', }, themeCSS: undefined, diff --git a/packages/mermaid/src/diagrams/block/renderHelpers.ts b/packages/mermaid/src/diagrams/block/renderHelpers.ts index 97eca4074..8957b4d8f 100644 --- a/packages/mermaid/src/diagrams/block/renderHelpers.ts +++ b/packages/mermaid/src/diagrams/block/renderHelpers.ts @@ -124,7 +124,8 @@ async function calculateBlockSize( } // Add the element to the DOM to size it - const nodeEl = await insertNode(elem, node); + const config = getConfig(); + const nodeEl = await insertNode(elem, node, { config }); const boundingBox = nodeEl.node().getBBox(); const obj = db.getBlock(node.id); obj.size = { width: boundingBox.width, height: boundingBox.height, x: 0, y: 0, node: nodeEl }; @@ -138,7 +139,8 @@ export async function insertBlockPositioned(elem: any, block: Block, db: any) { // Add the element to the DOM to size it const obj = db.getBlock(node.id); if (obj.type !== 'space') { - await insertNode(elem, node); + const config = getConfig(); + await insertNode(elem, node, { config }); block.intersect = node?.intersect; positionNode(node); } diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts index 3b1b3f32d..8d8245e67 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts @@ -4,6 +4,7 @@ import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js'; import common from '../common/common.js'; import type { Node, Edge } from '../../rendering-util/types.js'; import { log } from '../../logger.js'; +import * as yaml from 'js-yaml'; import { setAccTitle, getAccTitle, @@ -14,6 +15,7 @@ import { getDiagramTitle, } from '../common/commonDb.js'; import type { FlowVertex, FlowClass, FlowSubGraph, FlowText, FlowEdge, FlowLink } from './types.js'; +import type { NodeMetaData } from '../../types.js'; const MERMAID_DOM_ID_PREFIX = 'flowchart-'; let vertexCounter = 0; @@ -60,8 +62,10 @@ export const addVertex = function ( style: string[], classes: string[], dir: string, - props = {} + props = {}, + shapeData: any ) { + // console.log('addVertex', id, shapeData); if (!id || id.trim().length === 0) { return; } @@ -115,6 +119,59 @@ export const addVertex = function ( } else if (props !== undefined) { Object.assign(vertex.props, props); } + + if (shapeData !== undefined) { + let yamlData; + // detect if shapeData contains a newline character + // console.log('shapeData', shapeData); + if (!shapeData.includes('\n')) { + // console.log('yamlData shapeData has no new lines', shapeData); + yamlData = '{\n' + shapeData + '\n}'; + } else { + // console.log('yamlData shapeData has new lines', shapeData); + yamlData = shapeData + '\n'; + } + // console.log('yamlData', yamlData); + const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData; + if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) { + throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`); + } + + // console.log('yamlData doc', doc); + if (doc?.shape) { + vertex.type = doc?.shape; + } + if (doc?.label) { + vertex.text = doc?.label; + } + if (doc?.icon) { + vertex.icon = doc?.icon; + if (!doc.label?.trim() && vertex.text === id) { + vertex.text = ''; + } + } + if (doc?.form) { + vertex.form = doc?.form; + } + if (doc?.pos) { + vertex.pos = doc?.pos; + } + if (doc?.img) { + vertex.img = doc?.img; + if (!doc.label?.trim() && vertex.text === id) { + vertex.text = ''; + } + } + if (doc?.constraint) { + vertex.constraint = doc.constraint; + } + if (doc.w) { + vertex.assetWidth = Number(doc.w); + } + if (doc.h) { + vertex.assetHeight = Number(doc.h); + } + } }; /** @@ -760,6 +817,21 @@ export const lex = { }; const getTypeFromVertex = (vertex: FlowVertex) => { + if (vertex.img) { + return 'imageSquare'; + } + if (vertex.icon) { + if (vertex.form === 'circle') { + return 'iconCircle'; + } + if (vertex.form === 'square') { + return 'iconSquare'; + } + if (vertex.form === 'rounded') { + return 'iconRounded'; + } + return 'icon'; + } if (vertex.type === 'square') { return 'squareRect'; } @@ -825,6 +897,12 @@ const addNodeFromVertex = ( link: vertex.link, linkTarget: vertex.linkTarget, tooltip: getTooltip(vertex.id), + icon: vertex.icon, + pos: vertex.pos, + img: vertex.img, + assetWidth: vertex.assetWidth, + assetHeight: vertex.assetHeight, + constraint: vertex.constraint, }); } }; diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js new file mode 100644 index 000000000..42e3bbbb4 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-node-data.spec.js @@ -0,0 +1,278 @@ +import flowDb from '../flowDb.js'; +import flow from './flow.jison'; +import { setConfig } from '../../../config.js'; + +setConfig({ + securityLevel: 'strict', +}); + +describe('when parsing directions', function () { + beforeEach(function () { + flow.parser.yy = flowDb; + flow.parser.yy.clear(); + flow.parser.yy.setGen('gen-2'); + }); + + it('should handle basic shape data statements', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded}`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + }); + it('should handle basic shape data statements', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded }`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + }); + + it('should handle basic shape data statements with &', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } & E`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(2); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should handle shape data statements with edges', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } --> E`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(2); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should handle basic shape data statements with amp and edges 1', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } & E --> F`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(3); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should handle basic shape data statements with amp and edges 2', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } & E@{ shape: rounded } --> F`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(3); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should handle basic shape data statements with amp and edges 3', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } & E@{ shape: rounded } --> F & G@{ shape: rounded }`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(4); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should handle basic shape data statements with amp and edges 4', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } & E@{ shape: rounded } --> F@{ shape: rounded } & G@{ shape: rounded }`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(4); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should handle basic shape data statements with amp and edges 5, trailing space', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded } & E@{ shape: rounded } --> F{ shape: rounded } & G{ shape: rounded } `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(4); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + expect(data4Layout.nodes[1].label).toEqual('E'); + }); + it('should no matter of there are no leading spaces', function () { + const res = flow.parser.parse(`flowchart TB + D@{shape: rounded}`); + + const data4Layout = flow.parser.yy.getData(); + + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + }); + + it('should no matter of there are many leading spaces', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded}`); + + const data4Layout = flow.parser.yy.getData(); + + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + }); + + it('should be forgiving with many spaces before teh end', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded }`); + + const data4Layout = flow.parser.yy.getData(); + + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('D'); + }); + it('should be possible to add multiple properties on the same line', function () { + const res = flow.parser.parse(`flowchart TB + D@{ shape: rounded , label: "DD"}`); + + const data4Layout = flow.parser.yy.getData(); + + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('rounded'); + expect(data4Layout.nodes[0].label).toEqual('DD'); + }); + it('should be possible to link to a node with more data', function () { + const res = flow.parser.parse(`flowchart TB + A --> D@{ + shape: circle + other: "clock" + } + + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(2); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('A'); + expect(data4Layout.nodes[1].label).toEqual('D'); + expect(data4Layout.nodes[1].shape).toEqual('circle'); + + expect(data4Layout.edges.length).toBe(1); + }); + it('should not disturb adding multiple nodes after each other', function () { + const res = flow.parser.parse(`flowchart TB + A[hello] + B@{ + shape: circle + other: "clock" + } + C[Hello]@{ + shape: circle + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(3); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('hello'); + expect(data4Layout.nodes[1].shape).toEqual('circle'); + expect(data4Layout.nodes[1].label).toEqual('B'); + expect(data4Layout.nodes[2].shape).toEqual('circle'); + expect(data4Layout.nodes[2].label).toEqual('Hello'); + }); + it('should use handle bracket end (}) character inside the shape data', function () { + const res = flow.parser.parse(`flowchart TB + A@{ + label: "This is }" + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('This is }'); + }); + it('Diamond shapes should work as usual', function () { + const res = flow.parser.parse(`flowchart TB + A{This is a label} +`); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('diamond'); + expect(data4Layout.nodes[0].label).toEqual('This is a label'); + }); + it('Multi line strings should be supported', function () { + const res = flow.parser.parse(`flowchart TB + A@{ + label: | + This is a + multiline string + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('This is a\nmultiline string\n'); + }); + it('Multi line strings should be supported', function () { + const res = flow.parser.parse(`flowchart TB + A@{ + label: "This is a + multiline string" + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('This is a
multiline string'); + }); + it(' should be possible to use } in strings', function () { + const res = flow.parser.parse(`flowchart TB + A@{ + label: "This is a string with }" + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('This is a string with }'); + }); + it(' should be possible to use @ in strings', function () { + const res = flow.parser.parse(`flowchart TB + A@{ + label: "This is a string with @" + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('This is a string with @'); + }); + it(' should be possible to use @ in strings', function () { + const res = flow.parser.parse(`flowchart TB + A@{ + label: "This is a string with}" + other: "clock" + } + `); + + const data4Layout = flow.parser.yy.getData(); + expect(data4Layout.nodes.length).toBe(1); + expect(data4Layout.nodes[0].shape).toEqual('squareRect'); + expect(data4Layout.nodes[0].label).toEqual('This is a string with}'); + }); +}); diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index 54949bfae..b3df82fa5 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -23,6 +23,9 @@ %x href %x callbackname %x callbackargs +%x shapeData +%x shapeDataStr +%x shapeDataEndBracket %% accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } @@ -34,6 +37,32 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multilin [^\}]* return "acc_descr_multiline_value"; // .*[^\n]* { return "acc_descr_line"} + +\@\{ { + // console.log('=> shapeData', yytext); + this.pushState("shapeData"); yytext=""; return 'SHAPE_DATA' } +["] { + // console.log('=> shapeDataStr', yytext); + this.pushState("shapeDataStr"); + return 'SHAPE_DATA'; + } +["] { + // console.log('shapeData <==', yytext); + this.popState(); return 'SHAPE_DATA'} +[^\"]+ { + // console.log('shapeData', yytext); + const re = /\n\s*/g; + yytext = yytext.replace(re,"
"); + return 'SHAPE_DATA'} +[^}^"]+ { + // console.log('shapeData', yytext); + return 'SHAPE_DATA'; + } +"}" { + // console.log('<== root', yytext) + this.popState(); + } + /* ---interactivity command--- 'call' adds a callback to the specified node. 'call' can only be specified when @@ -49,10 +78,11 @@ Function arguments are optional: 'call ()' simply executes 'callba \) this.popState(); [^)]* return 'CALLBACKARGS'; + [^`"]+ { return "MD_STR";} [`]["] { this.popState();} <*>["][`] { this.begin("md_string");} -[^"]+ return "STR"; +[^"]+ { return "STR"; } ["] this.popState(); <*>["] this.pushState("string"); "style" return 'STYLE'; @@ -62,6 +92,8 @@ Function arguments are optional: 'call ()' simply executes 'callba "classDef" return 'CLASSDEF'; "class" return 'CLASS'; + + /* ---interactivity command--- 'href' adds a link to the specified node. 'href' can only be specified when the @@ -356,23 +388,38 @@ statement separator: NEWLINE | SEMI | EOF ; +shapeData: + shapeData SHAPE_DATA + { $$ = $1 + $2; } + | SHAPE_DATA + { $$ = $1; } + ; -vertexStatement: vertexStatement link node - { /* console.warn('vs',$vertexStatement.stmt,$node); */ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } } +vertexStatement: vertexStatement link node shapeData + { /* console.warn('vs shapeData',$vertexStatement.stmt,$node, $shapeData);*/ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } } + | vertexStatement link node + { /*console.warn('vs',$vertexStatement.stmt,$node);*/ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } } | vertexStatement link node spaceList { /* console.warn('vs',$vertexStatement.stmt,$node); */ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } } - |node spaceList {/*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }} - |node { /*console.warn('noda', $node);*/ $$ = {stmt: $node, nodes:$node }} + |node spaceList { /*console.warn('vertexStatement: node spaceList', $node);*/ $$ = {stmt: $node, nodes:$node }} + |node shapeData { + /*console.warn('vertexStatement: node shapeData', $node[0], $shapeData);*/ + yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); + $$ = {stmt: $node, nodes:$node, shapeData: $shapeData} + } + |node { /* console.warn('vertexStatement: single node', $node); */ $$ = {stmt: $node, nodes:$node }} ; node: styledVertex - { /* console.warn('nod', $styledVertex); */ $$ = [$styledVertex];} + { /*console.warn('nod', $styledVertex);*/ $$ = [$styledVertex];} + | node shapeData spaceList AMP spaceList styledVertex + { yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/ } | node spaceList AMP spaceList styledVertex - { $$ = $node.concat($styledVertex); /* console.warn('pip', $node[0], $styledVertex, $$); */ } + { $$ = $node.concat($styledVertex); /*console.warn('pip', $node[0], $styledVertex, $$);*/ } ; styledVertex: vertex - { /* console.warn('nod', $vertex); */ $$ = $vertex;} + { /* console.warn('nodc', $vertex);*/ $$ = $vertex;} | vertex STYLE_SEPARATOR idString {$$ = $vertex;yy.setClass($vertex,$idString)} ; diff --git a/packages/mermaid/src/diagrams/flowchart/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts index 0ca2c1321..ade9613fb 100644 --- a/packages/mermaid/src/diagrams/flowchart/styles.ts +++ b/packages/mermaid/src/diagrams/flowchart/styles.ts @@ -59,7 +59,7 @@ const getStyles = (options: FlowChartStyleOptions) => stroke: ${options.nodeBorder}; stroke-width: 1px; } - .rough-node .label text , .node .label text { + .rough-node .label text , .node .label text, .image-shape .label, .icon-shape .label { text-anchor: middle; } // .flowchart-label .text-outer-tspan { @@ -75,13 +75,20 @@ const getStyles = (options: FlowChartStyleOptions) => stroke-width: 1px; } - .node .label { + .rough-node .label,.node .label, .image-shape .label, .icon-shape .label { text-align: center; } .node.clickable { cursor: pointer; } + + .root .anchor path { + fill: ${options.lineColor} !important; + stroke-width: 0; + stroke: ${options.lineColor}; + } + .arrowheadPath { fill: ${options.arrowheadColor}; } @@ -151,6 +158,25 @@ const getStyles = (options: FlowChartStyleOptions) => font-size: 18px; fill: ${options.textColor}; } + + rect.text { + fill: none; + stroke-width: 0; + } + + .icon-shape, .image-shape { + background-color: ${options.edgeLabelBackground}; + p { + background-color: ${options.edgeLabelBackground}; + padding: 2px; + } + rect { + opacity: 0.5; + background-color: ${options.edgeLabelBackground}; + fill: ${options.edgeLabelBackground}; + } + text-align: center; + } `; export default getStyles; diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts index ee64ae56b..770ee24b4 100644 --- a/packages/mermaid/src/diagrams/flowchart/types.ts +++ b/packages/mermaid/src/diagrams/flowchart/types.ts @@ -11,6 +11,15 @@ export interface FlowVertex { styles: string[]; text?: string; type?: string; + icon?: string; + form?: string; + pos?: 't' | 'b'; + img?: string; + assetWidth?: number; + assetHeight?: number; + defaultWidth?: number; + imageAspectRatio?: number; + constraint?: 'on' | 'off'; } export interface FlowText { diff --git a/packages/mermaid/src/docs/.vitepress/components/ProductHuntBadge.vue b/packages/mermaid/src/docs/.vitepress/components/ProductHuntBadge.vue index b283c644c..0f246db71 100644 --- a/packages/mermaid/src/docs/.vitepress/components/ProductHuntBadge.vue +++ b/packages/mermaid/src/docs/.vitepress/components/ProductHuntBadge.vue @@ -1,11 +1,11 @@