Merge branch 'develop' into feat/sequence-alias-support-new-participant-syntax

This commit is contained in:
omkarht
2025-11-14 12:16:07 +05:30
committed by GitHub
15 changed files with 1729 additions and 1201 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Prevent HTML tags from being escaped in sandbox label rendering

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Support ComponentQueue_Ext to prevent parsing error

View File

@@ -58,7 +58,7 @@ jobs:
echo "EOF" >> $GITHUB_OUTPUT
- name: Commit and create pull request
uses: peter-evans/create-pull-request@915d841dae6a4f191bb78faf61a257411d7be4d2
uses: peter-evans/create-pull-request@0edc001d28a2959cd7a6b505629f1d82f0a6e67d
with:
add-paths: |
cypress/timings.json

View File

@@ -98,12 +98,21 @@ export const openURLAndVerifyRendering = (
cy.visit(url);
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
// cspell:ignore viewbox
cy.get('svg').should('not.have.attr', 'viewbox');
if (validation) {
cy.get('svg').should(validation);
// Handle sandbox mode where SVG is inside an iframe
if (options.securityLevel === 'sandbox') {
cy.get('iframe').should('be.visible');
if (validation) {
cy.get('iframe').should(validation);
}
} else {
cy.get('svg').should('be.visible');
// cspell:ignore viewbox
cy.get('svg').should('not.have.attr', 'viewbox');
if (validation) {
cy.get('svg').should(validation);
}
}
if (screenshot) {

View File

@@ -114,4 +114,28 @@ describe('C4 diagram', () => {
{}
);
});
it('C4.6 should render C4Context diagram with ComponentQueue_Ext', () => {
imgSnapshotTest(
`
C4Context
title System Context diagram with ComponentQueue_Ext
Enterprise_Boundary(b0, "BankBoundary0") {
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
Enterprise_Boundary(b1, "BankBoundary") {
ComponentQueue_Ext(msgQueue, "Message Queue", "RabbitMQ", "External message queue system for processing banking transactions")
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
}
}
BiRel(customerA, SystemAA, "Uses")
Rel(SystemAA, msgQueue, "Sends messages to")
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
`,
{}
);
});
});

View File

@@ -79,6 +79,18 @@ describe('Flowchart v2', () => {
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
);
});
it('6a: should render complex HTML in labels with sandbox security', () => {
imgSnapshotTest(
`flowchart TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Laptop]
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ securityLevel: 'sandbox', flowchart: { htmlLabels: true } }
);
});
it('7: should render a flowchart when useMaxWidth is true (default)', () => {
renderGraph(
`flowchart TD

View File

@@ -2,227 +2,227 @@
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 5841
"duration": 6099
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 2138
"duration": 2236
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3370
"duration": 3405
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 2052
"duration": 2176
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 12243
"duration": 12300
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 2065
"duration": 2089
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 31288
"duration": 32033
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 3421
"duration": 3672
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
"duration": 97
"duration": 103
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 18500
"duration": 18135
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 5793
"duration": 5661
},
{
"spec": "cypress/integration/rendering/classDiagram-elk-v3.spec.js",
"duration": 40966
"duration": 41456
},
{
"spec": "cypress/integration/rendering/classDiagram-handDrawn-v3.spec.js",
"duration": 39176
"duration": 38910
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 23468
"duration": 24120
},
{
"spec": "cypress/integration/rendering/classDiagram-v3.spec.js",
"duration": 38291
"duration": 38454
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 16949
"duration": 17099
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 9480
"duration": 9844
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 2753
"duration": 2951
},
{
"spec": "cypress/integration/rendering/erDiagram-unified.spec.js",
"duration": 88028
"duration": 90081
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 15615
"duration": 19496
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 3706
"duration": 3829
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 43905
"duration": 42517
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 31217
"duration": 31541
},
{
"spec": "cypress/integration/rendering/flowchart-icon.spec.js",
"duration": 7531
"duration": 7749
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 25423
"duration": 25230
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 49664
"duration": 49359
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 32525
"duration": 33028
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 20915
"duration": 22271
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 53556
"duration": 51837
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 283038
"duration": 285060
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 59434
"duration": 59517
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 3101
"duration": 3501
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
"duration": 7099
"duration": 7405
},
{
"spec": "cypress/integration/rendering/kanban.spec.ts",
"duration": 7567
"duration": 7975
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 3817
"duration": 4312
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 2624
"duration": 2630
},
{
"spec": "cypress/integration/rendering/mindmap-tidy-tree.spec.js",
"duration": 4246
"duration": 4541
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 11967
"duration": 12134
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 151914
"duration": 151160
},
{
"spec": "cypress/integration/rendering/oldShapes.spec.ts",
"duration": 116698
"duration": 118044
},
{
"spec": "cypress/integration/rendering/packet.spec.ts",
"duration": 4967
"duration": 5166
},
{
"spec": "cypress/integration/rendering/pie.spec.ts",
"duration": 6700
"duration": 7074
},
{
"spec": "cypress/integration/rendering/quadrantChart.spec.js",
"duration": 8963
"duration": 9518
},
{
"spec": "cypress/integration/rendering/radar.spec.js",
"duration": 5540
"duration": 5846
},
{
"spec": "cypress/integration/rendering/requirement.spec.js",
"duration": 2782
"duration": 3089
},
{
"spec": "cypress/integration/rendering/requirementDiagram-unified.spec.js",
"duration": 54797
"duration": 55361
},
{
"spec": "cypress/integration/rendering/sankey.spec.ts",
"duration": 6914
"duration": 7236
},
{
"spec": "cypress/integration/rendering/sequencediagram-v2.spec.js",
"duration": 20481
"duration": 26057
},
{
"spec": "cypress/integration/rendering/sequencediagram.spec.js",
"duration": 38490
"duration": 48401
},
{
"spec": "cypress/integration/rendering/stateDiagram-v2.spec.js",
"duration": 30766
"duration": 30364
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 16705
"duration": 16862
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 30928
"duration": 30553
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 8424
"duration": 8962
},
{
"spec": "cypress/integration/rendering/treemap.spec.ts",
"duration": 12533
"duration": 12486
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 21197
"duration": 21718
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 3455
"duration": 3882
}
]
}

View File

@@ -63,11 +63,11 @@
]
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.55.4",
"@argos-ci/cypress": "^6.1.5",
"@applitools/eyes-cypress": "^3.56.3",
"@argos-ci/cypress": "^6.2.1",
"@changesets/changelog-github": "^0.5.1",
"@changesets/cli": "^2.29.7",
"@cspell/eslint-plugin": "^8.19.4",
"@cspell/eslint-plugin": "^9.3.0",
"@cypress/code-coverage": "^3.14.7",
"@eslint/js": "^9.26.0",
"@rollup/plugin-typescript": "^12.1.4",
@@ -77,7 +77,7 @@
"@types/jsdom": "^21.1.7",
"@types/lodash": "^4.17.20",
"@types/mdast": "^4.0.4",
"@types/node": "^22.18.13",
"@types/node": "^22.19.0",
"@types/rollup-plugin-visualizer": "^5.0.3",
"@vitest/coverage-v8": "^3.2.4",
"@vitest/spy": "^3.2.4",
@@ -88,23 +88,23 @@
"cors": "^2.8.5",
"cpy-cli": "^5.0.0",
"cross-env": "^7.0.3",
"cspell": "^9.2.2",
"cspell": "^9.3.0",
"cypress": "^14.5.4",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.25",
"esbuild": "^0.25.12",
"eslint": "^9.26.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-cypress": "^4.3.0",
"eslint-plugin-cypress": "^5.2.0",
"eslint-plugin-html": "^8.1.3",
"eslint-plugin-jest": "^28.14.0",
"eslint-plugin-jsdoc": "^50.8.0",
"eslint-plugin-jest": "^29.0.1",
"eslint-plugin-jsdoc": "^61.1.12",
"eslint-plugin-json": "^4.0.1",
"eslint-plugin-lodash": "^8.0.0",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-no-only-tests": "^3.3.0",
"eslint-plugin-tsdoc": "^0.4.0",
"eslint-plugin-unicorn": "^59.0.1",
"eslint-plugin-unicorn": "^62.0.0",
"express": "^5.1.0",
"globals": "^16.4.0",
"globby": "^14.1.0",
@@ -127,7 +127,7 @@
"tsx": "^4.20.6",
"typescript": "~5.7.3",
"typescript-eslint": "^8.38.0",
"vite": "^7.0.7",
"vite": "^7.0.8",
"vite-plugin-istanbul": "^7.0.0",
"vitest": "^3.2.4"
},

View File

@@ -78,7 +78,7 @@
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.13",
"dayjs": "^1.11.19",
"dompurify": "^3.2.5",
"dompurify": "^3.3.0",
"katex": "^0.16.25",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",

View File

@@ -0,0 +1,58 @@
import c4Db from '../c4Db.js';
import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config.js';
setConfig({
securityLevel: 'strict',
});
describe.each([
['Component', 'component'],
['ComponentDb', 'component_db'],
['ComponentQueue', 'component_queue'],
['Component_Ext', 'external_component'],
['ComponentDb_Ext', 'external_component_db'],
['ComponentQueue_Ext', 'external_component_queue'],
])('parsing a C4 %s', function (macroName, elementName) {
beforeEach(function () {
c4.parser.yy = c4Db;
c4.parser.yy.clear();
});
it('should parse a C4 diagram with one Component correctly', function () {
c4.parser.parse(`C4Component
title Component diagram for Internet Banking Component
${macroName}(ComponentAA, "Internet Banking Component", "Technology", "Allows customers to view information about their bank accounts, and make payments.")`);
const yy = c4.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
const onlyShape = shapes[0];
expect(onlyShape).toMatchObject({
alias: 'ComponentAA',
descr: {
text: 'Allows customers to view information about their bank accounts, and make payments.',
},
label: {
text: 'Internet Banking Component',
},
techn: {
text: 'Technology',
},
typeC4Shape: {
text: elementName,
},
});
});
it('should handle a trailing whitespaces after Component', function () {
const whitespace = ' ';
const rendered = c4.parser.parse(`C4Component${whitespace}
title Component diagram for Internet Banking Component${whitespace}
${macroName}(ComponentAA, "Internet Banking Component", "Technology", "Allows customers to view information about their bank accounts, and make payments.")${whitespace}`);
expect(rendered).toBe(true);
});
});

View File

@@ -158,10 +158,10 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config><<EOF>> return "EOF_IN_STRUCT";
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config>[(] { this.begin("attribute"); }
<person,person_ext,system_ext_queue,system_ext_db,system_ext,system_queue,system_db,system,boundary,enterprise_boundary,system_boundary,container_ext_db,container_ext_queue,container_ext,container_queue,container_db,container,container_boundary,component_ext_db,component_ext_queue,component_ext,component_queue,component_db,component,node,node_l,node_r,rel,birel,rel_u,rel_d,rel_l,rel_r,rel_b,rel_index,update_el_style,update_rel_style,update_layout_config,attribute>[)] { this.popState();this.popState();}
<attribute>",," { return 'ATTRIBUTE_EMPTY';}
<attribute>"," { }

View File

@@ -70,6 +70,31 @@ describe('Sanitize text', () => {
});
expect(result).not.toContain('javascript:alert(1)');
});
it('should allow HTML tags in sandbox mode', () => {
const htmlStr = '<p>This is a <strong>bold</strong> text</p>';
const result = sanitizeText(htmlStr, {
securityLevel: 'sandbox',
flowchart: { htmlLabels: true },
});
expect(result).toContain('<p>');
expect(result).toContain('<strong>');
expect(result).toContain('</strong>');
expect(result).toContain('</p>');
});
it('should remove script tags in sandbox mode', () => {
const maliciousStr = '<p>Hello <script>alert(1)</script> world</p>';
const result = sanitizeText(maliciousStr, {
securityLevel: 'sandbox',
flowchart: { htmlLabels: true },
});
expect(result).not.toContain('<script>');
expect(result).not.toContain('alert(1)');
expect(result).toContain('<p>');
expect(result).toContain('Hello');
expect(result).toContain('world');
});
});
describe('generic parser', () => {

View File

@@ -66,7 +66,7 @@ export const removeScript = (txt: string): string => {
const sanitizeMore = (text: string, config: MermaidConfig) => {
if (config.flowchart?.htmlLabels !== false) {
const level = config.securityLevel;
if (level === 'antiscript' || level === 'strict') {
if (level === 'antiscript' || level === 'strict' || level === 'sandbox') {
text = removeScript(text);
} else if (level !== 'loose') {
text = breakToPlaceholder(text);

View File

@@ -21,19 +21,19 @@
"font-awesome": "^4.7.0",
"jiti": "^2.4.2",
"mermaid": "workspace:^",
"vue": "^3.5.23"
"vue": "^3.5.24"
},
"devDependencies": {
"@iconify-json/carbon": "^1.2.14",
"@unocss/reset": "^66.5.4",
"@unocss/reset": "^66.5.5",
"@vite-pwa/vitepress": "^1.0.1",
"@vitejs/plugin-vue": "^6.0.1",
"fast-glob": "^3.3.3",
"https-localhost": "^4.7.1",
"pathe": "^2.0.3",
"unocss": "^66.5.4",
"unocss": "^66.5.5",
"unplugin-vue-components": "^28.8.0",
"vite": "^7.0.7",
"vite": "^7.0.8",
"vite-plugin-pwa": "^1.0.3",
"vitepress": "1.6.4",
"workbox-window": "^7.3.0"

2628
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff