diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ff34d24fd..f20204a71 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,5 +14,5 @@ Make sure you - [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) - [ ] :computer: have added necessary unit/e2e tests. -- [ ] :notebook: have added documentation. Make sure [`MERMAID_RELEASE_VERSION`](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/community/development.md#3-update-documentation) is used for all new features. +- [ ] :notebook: have added documentation. Make sure [`MERMAID_RELEASE_VERSION`](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/docs/community/contributing.md#update-documentation) is used for all new features. - [ ] :bookmark: targeted `develop` branch diff --git a/.github/workflows/e2e-applitools.yml b/.github/workflows/e2e-applitools.yml index 543fb5dbb..fd32e59ad 100644 --- a/.github/workflows/e2e-applitools.yml +++ b/.github/workflows/e2e-applitools.yml @@ -28,7 +28,7 @@ jobs: - if: ${{ ! env.USE_APPLI }} name: Warn if not using Applitools run: | - echo "::error,title=Not using Applitols::APPLITOOLS_API_KEY is empty, disabling Applitools for this run." + echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run." - uses: actions/checkout@v4 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 71806a9c4..c23dc88ba 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,16 +1,63 @@ +# We use github cache to save snapshots between runs. +# For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used. +# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots. +# These are then downloaded before running the E2E, providing the reference snapshots. +# If there are any errors, the diff image is uploaded to artifacts, and the user is notified. + name: E2E on: push: + branches-ignore: + - 'gh-readonly-queue/**' pull_request: merge_group: permissions: contents: read +env: + # For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used. + targetHash: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }} + jobs: + cache: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v2 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + + - name: Cache snapshots + id: cache-snapshot + uses: actions/cache@v4 + with: + save-always: true + path: ./cypress/snapshots + key: ${{ runner.os }}-snapshots-${{ env.targetHash }} + + # If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots. + - name: Switch to base branch + if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} + uses: actions/checkout@v4 + with: + ref: ${{ env.targetHash }} + + - name: Cypress run + uses: cypress-io/github-action@v4 + id: cypress-snapshot-gen + if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} + with: + start: pnpm run dev + wait-on: 'http://localhost:9000' + browser: chrome + e2e: runs-on: ubuntu-latest + needs: cache strategy: fail-fast: false matrix: @@ -27,6 +74,14 @@ jobs: with: node-version: ${{ matrix.node-version }} + # These cached snapshots are downloaded, providing the reference snapshots. + - name: Cache snapshots + id: cache-snapshot + uses: actions/cache/restore@v3 + with: + path: ./cypress/snapshots + key: ${{ runner.os }}-snapshots-${{ env.targetHash }} + # Install NPM dependencies, cache them correctly # and run all Cypress tests - name: Cypress run @@ -38,6 +93,7 @@ jobs: with: start: pnpm run dev:coverage wait-on: 'http://localhost:9000' + browser: chrome # Disable recording if we don't have an API key # e.g. if this action was run from a fork record: ${{ secrets.CYPRESS_RECORD_KEY != '' }} @@ -46,6 +102,7 @@ jobs: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} VITEST_COVERAGE: true CYPRESS_COMMIT: ${{ github.sha }} + - name: Upload Coverage to Codecov uses: codecov/codecov-action@v3 # Run step only pushes to develop and pull_requests @@ -57,9 +114,55 @@ jobs: fail_ci_if_error: false verbose: true token: 6845cc80-77ee-4e17-85a1-026cd95e0766 + + # We upload the artifacts into numbered archives to prevent overwriting - name: Upload Artifacts - uses: actions/upload-artifact@v3 - if: ${{ failure() && steps.cypress.conclusion == 'failure' }} + uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: snapshots-${{ matrix.containers }} + retention-days: 1 + path: ./cypress/snapshots + + combineArtifacts: + needs: e2e + runs-on: ubuntu-latest + if: ${{ always() }} + steps: + # Download all snapshot artifacts and merge them into a single folder + - name: Download All Artifacts + uses: actions/download-artifact@v4 + with: + path: snapshots + pattern: snapshots-* + merge-multiple: true + + # For successful push events, we save the snapshots cache + - name: Save snapshots cache + id: cache-upload + if: ${{ github.event_name == 'push' && needs.e2e.result != 'failure' }} + uses: actions/cache/save@v3 + with: + path: ./snapshots + key: ${{ runner.os }}-snapshots-${{ github.event.after }} + + - name: Flatten images to a folder + if: ${{ needs.e2e.result == 'failure' }} + run: | + mkdir errors + cd snapshots + find . -mindepth 2 -type d -name "*__diff_output__*" -exec sh -c 'mv "$0"/*.png ../errors/' {} \; + + - name: Upload Error snapshots + if: ${{ needs.e2e.result == 'failure' }} + uses: actions/upload-artifact@v4 + id: upload-artifacts with: name: error-snapshots - path: cypress/snapshots/**/__diff_output__/* + retention-days: 10 + path: errors/ + + - name: Notify Users + if: ${{ needs.e2e.result == 'failure' }} + run: | + echo "::error title=Visual tests failed::You can view images that failed by downloading the error-snapshots artifact: ${{ steps.upload-artifacts.outputs.artifact-url }}" diff --git a/.github/workflows/link-checker.yml b/.github/workflows/link-checker.yml index c3e2ee44f..3d4956945 100644 --- a/.github/workflows/link-checker.yml +++ b/.github/workflows/link-checker.yml @@ -36,7 +36,7 @@ jobs: restore-keys: cache-lychee- - name: Link Checker - uses: lycheeverse/lychee-action@v1.8.0 + uses: lycheeverse/lychee-action@v1.9.1 with: args: >- --config .github/lychee.toml diff --git a/.github/workflows/release-draft.yml b/.github/workflows/release-draft.yml index 8ad1b13ec..db1dd1f48 100644 --- a/.github/workflows/release-draft.yml +++ b/.github/workflows/release-draft.yml @@ -3,7 +3,7 @@ name: Draft Release on: push: branches: - - develop + - master permissions: contents: read diff --git a/.gitignore b/.gitignore index 6a1cc85e5..e6728b03f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ stats/ demos/dev/** !/demos/dev/example.html +tsx-0/** diff --git a/.npmrc b/.npmrc index 4c2f52b3b..e72930ead 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ +registry=https://registry.npmjs.org auto-install-peers=true strict-peer-dependencies=false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 3142c5760..000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,78 +0,0 @@ -# Contributing - -Please read in detail about how to contribute documentation and code on the [Mermaid documentation site.](https://mermaid-js.github.io/mermaid/#/development) - ---- - -# Mermaid contribution cheat-sheet - -## Requirements - -- [volta](https://volta.sh/) to manage node versions. -- [Node.js](https://nodejs.org/en/). `volta install node` -- [pnpm](https://pnpm.io/) package manager. `volta install pnpm` - -## Development Installation - -If you don't have direct access to push to mermaid repositories, make a fork first. Then clone. Or clone directly from mermaid-js: - -```bash -git clone git@github.com:mermaid-js/mermaid.git -cd mermaid -``` - -Install required packages: - -```bash -# npx is required for first install as volta support for pnpm is not added yet. -npx pnpm install -pnpm test # run unit tests -pnpm dev # starts a dev server -``` - -Open in your browser after starting the dev server. -You can also duplicate the `example.html` file in `demos/dev`, rename it and add your own mermaid code to it. -That will be served at . - -### Docker - -If you are using docker and docker-compose, you have self-documented `run` bash script, which is a convenient alias for docker-compose commands: - -```bash -./run install # npx pnpm install -./run test # pnpm test -``` - -## Testing - -```bash -# Run unit test -pnpm test -# Run unit test in watch mode -pnpm test:watch -# Run E2E test -pnpm e2e -# Debug E2E tests -pnpm dev -pnpm cypress:open # in another terminal -``` - -## Branch name format: - -```text - [feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces] -``` - -eg: `feature/2945_state-diagram-new-arrow-florbs`, `bug/1123_fix_random_ugly_red_text` - -## Documentation - -Documentation is necessary for all non bugfix/refactoring changes. - -Only make changes to files that are in [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) - -**_DO NOT CHANGE FILES IN `/docs` MANUALLY_** - -The `/docs` folder will be rebuilt and committed as part of a pre-commit hook. - -[Join our slack community if you want closer contact!](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 120000 index 000000000..885868c16 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +./packages/mermaid/src/docs/community/contributing.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..a62800109 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,2 @@ +FROM node:18.19.0-alpine3.18 AS base +RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh - diff --git a/README.md b/README.md index cf21fdb8e..5acafa95d 100644 --- a/README.md +++ b/README.md @@ -74,12 +74,12 @@ Mermaid addresses this problem by enabling users to create easily modifiable dia
Mermaid allows even non-programmers to easily create detailed diagrams through the [Mermaid Live Editor](https://mermaid.live/).
-For video tutorials, visit our [Tutorials](./docs/config/Tutorials.md) page. +For video tutorials, visit our [Tutorials](./docs/ecosystem/tutorials.md) page. Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations-community.md). You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations-community.md). -For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/getting-started.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md). +For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/getting-started.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/ecosystem/tutorials.md). In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests. diff --git a/README.zh-CN.md b/README.zh-CN.md index 98975ea33..23c9bc722 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -57,9 +57,9 @@ Mermaid 是一个基于 Javascript 的图表绘制工具,通过解析类 Markd Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它也可以作为生产脚本(或其他代码)的一部分。

Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。
-你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations-community.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 +你可以访问 [教程](./docs/ecosystem/tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations-community.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 -如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/getting-started.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md). +如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/getting-started.md), [用法](./docs/config/usage.md) 和 [教程](./docs/ecosystem/tutorials.md). diff --git a/cSpell.json b/cSpell.json index 76f395915..4b8772347 100644 --- a/cSpell.json +++ b/cSpell.json @@ -26,6 +26,7 @@ "città", "classdef", "codedoc", + "codemia", "colour", "commitlint", "cpettitt", @@ -101,6 +102,7 @@ "pathe", "pbrolin", "phpbb", + "pixelmatch", "plantuml", "playfair", "pnpm", @@ -123,6 +125,7 @@ "sidharth", "sidharthv", "sphinxcontrib", + "ssim", "startx", "starty", "statediagram", diff --git a/cypress.config.cjs b/cypress.config.cjs index 30076c56e..33633920a 100644 --- a/cypress.config.cjs +++ b/cypress.config.cjs @@ -6,10 +6,18 @@ const coverage = require('@cypress/code-coverage/task'); module.exports = defineConfig({ projectId: 'n2sma2', + viewportWidth: 1440, + viewportHeight: 1024, e2e: { - specPattern: 'cypress/integration/**/*.{js,jsx,ts,tsx}', + specPattern: 'cypress/integration/**/*.{js,ts}', setupNodeEvents(on, config) { coverage(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'); + } + return launchOptions; + }); addMatchImageSnapshotPlugin(on, config); // copy any needed variables from process.env to config.env config.env.useAppli = process.env.USE_APPLI ? true : false; diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts index c656f638d..aed5d7973 100644 --- a/cypress/helpers/util.ts +++ b/cypress/helpers/util.ts @@ -10,7 +10,7 @@ interface CypressConfig { type CypressMermaidConfig = MermaidConfig & CypressConfig; interface CodeObject { - code: string; + code: string | string[]; mermaid: CypressMermaidConfig; } @@ -25,7 +25,7 @@ const batchId: string = : Cypress.env('CYPRESS_COMMIT') || Date.now().toString()); export const mermaidUrl = ( - graphStr: string, + graphStr: string | string[], options: CypressMermaidConfig, api: boolean ): string => { @@ -82,7 +82,7 @@ export const urlSnapshotTest = ( }; export const renderGraph = ( - graphStr: string, + graphStr: string | string[], options: CypressMermaidConfig = {}, api = false ): void => { diff --git a/cypress/integration/other/configuration.spec.js b/cypress/integration/other/configuration.spec.js index 7cbc5d105..23338271f 100644 --- a/cypress/integration/other/configuration.spec.js +++ b/cypress/integration/other/configuration.spec.js @@ -117,7 +117,6 @@ describe('Configuration', () => { }); it('should not taint the initial configuration when using multiple directives', () => { const url = 'http://localhost:9000/regression/issue-1874.html'; - cy.viewport(1440, 1024); cy.visit(url); cy.get('svg'); diff --git a/cypress/integration/other/rerender.spec.js b/cypress/integration/other/rerender.spec.js index f160a2e27..d14c6257e 100644 --- a/cypress/integration/other/rerender.spec.js +++ b/cypress/integration/other/rerender.spec.js @@ -1,14 +1,12 @@ describe('Rerendering', () => { it('should be able to render after an error has occurred', () => { const url = 'http://localhost:9000/render-after-error.html'; - cy.viewport(1440, 1024); cy.visit(url); cy.get('#graphDiv').should('exist'); }); it('should be able to render and rerender a graph via API', () => { const url = 'http://localhost:9000/rerender.html'; - cy.viewport(1440, 1024); cy.visit(url); cy.get('#graph [id^=flowchart-A]').should('have.text', 'XMas'); diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index 37e9cada0..20a1aea0a 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -571,4 +571,14 @@ class C13["With Città foreign language"] { logLevel: 1, flowchart: { htmlLabels: false } } ); }); + it('should render a simple class diagram with style definition', () => { + imgSnapshotTest( + ` + classDiagram-v2 + class Class10 + style Class10 fill:#f9f,stroke:#333,stroke-width:4px + `, + { logLevel: 1, flowchart: { htmlLabels: false } } + ); + }); }); diff --git a/cypress/integration/rendering/debug.spec.js b/cypress/integration/rendering/debug.spec.js deleted file mode 100644 index 56ad0f15f..000000000 --- a/cypress/integration/rendering/debug.spec.js +++ /dev/null @@ -1,12 +0,0 @@ -import { imgSnapshotTest } from '../../helpers/util.ts'; - -describe('Flowchart', () => { - it('34: testing the label width in percy', () => { - imgSnapshotTest( - `graph TD - A[Christmas] - `, - { theme: 'forest', fontFamily: '"Noto Sans SC", sans-serif' } - ); - }); -}); diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 998a092c2..73ff4ee00 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -245,7 +245,10 @@ describe('Gantt diagram', () => { const style = svg.attr('style'); expect(style).to.match(/^max-width: [\d.]+px;$/); const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); - expect(maxWidthValue).to.be.within(984 * 0.95, 984 * 1.05); + expect(maxWidthValue).to.be.within( + Cypress.config().viewportWidth * 0.95, + Cypress.config().viewportWidth * 1.05 + ); }); }); @@ -285,11 +288,11 @@ describe('Gantt diagram', () => { { gantt: { useMaxWidth: false } } ); cy.get('svg').should((svg) => { - // const height = parseFloat(svg.attr('height')); const width = parseFloat(svg.attr('width')); - // use within because the absolute value can be slightly different depending on the environment ±5% - // expect(height).to.be.within(484 * 0.95, 484 * 1.05); - expect(width).to.be.within(984 * 0.95, 984 * 1.05); + expect(width).to.be.within( + Cypress.config().viewportWidth * 0.95, + Cypress.config().viewportWidth * 1.05 + ); expect(svg).to.not.have.attr('style'); }); }); diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index 765913824..27e03da9c 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -930,4 +930,36 @@ context('Sequence diagram', () => { }); }); }); + context('render after error', () => { + it('should render diagram after fixing destroy participant error', () => { + cy.on('uncaught:exception', (err) => { + return false; + }); + + renderGraph([ + `sequenceDiagram + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bo + Bob->>Alice: I agree`, + `sequenceDiagram + Alice->>Bob: Hello Bob, how are you ? + Bob->>Alice: Fine, thank you. And you? + create participant Carl + Alice->>Carl: Hi Carl! + create actor D as Donald + Carl->>D: Hi! + destroy Carl + Alice-xCarl: We are too many + destroy Bob + Bob->>Alice: I agree`, + ]); + }); + }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 3589640d9..6fc1fe17d 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -24,8 +24,18 @@ // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -// import '@percy/cypress'; - import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'; +// The SSIM comparison method can be used if the pixelmatch is throwing lots of false positives. +// SSIM actually does not catch minute changes in the image, so it is not as accurate as pixelmatch. +// addMatchImageSnapshotCommand({ +// comparisonMethod: 'ssim', +// failureThreshold: 0.01, +// failureThresholdType: 'percent', +// customDiffConfig: { +// ssim: 'fast', +// }, +// blur: 1, +// }); + addMatchImageSnapshotCommand(); diff --git a/demos/sequence.html b/demos/sequence.html index 3345fed17..b2733a384 100644 --- a/demos/sequence.html +++ b/demos/sequence.html @@ -164,13 +164,6 @@ end -
-    sequenceDiagram
-      actor Alice
-      actor John
-      Alice-xJohn: Hello John, how are you?
-      John--xAlice: Great!
-