mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-31 10:54:15 +01:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			7079-c4con
			...
			fix-edge-d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f7d7fe42aa | ||
|   | fed8a523a4 | ||
|   | 33b4946e21 | ||
|   | 3d768f3adf | ||
|   | 76e17ffd20 | ||
|   | 60f633101c | ||
|   | 7def6eecbf | ||
|   | ac411a7d7e | 
							
								
								
									
										5
									
								
								.changeset/chilly-words-march.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/chilly-words-march.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| --- | ||||
| 'mermaid': patch | ||||
| --- | ||||
|  | ||||
| fix: Correct viewBox casing and make SVGs responsive | ||||
							
								
								
									
										5
									
								
								.changeset/lucky-cases-switch.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/lucky-cases-switch.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| --- | ||||
| 'mermaid': patch | ||||
| --- | ||||
|  | ||||
| fix: Allow IDs starting with L, R, T, or B in parser | ||||
| @@ -1,5 +0,0 @@ | ||||
| --- | ||||
| 'mermaid': patch | ||||
| --- | ||||
|  | ||||
| fix: Support ComponentQueue_Ext to prevent parsing error | ||||
| @@ -99,6 +99,7 @@ export const openURLAndVerifyRendering = ( | ||||
|   cy.visit(url); | ||||
|   cy.window().should('have.property', 'rendered', true); | ||||
|   cy.get('svg').should('be.visible'); | ||||
|   cy.get('svg').should('not.have.attr', 'viewbox'); | ||||
|  | ||||
|   if (validation) { | ||||
|     cy.get('svg').should(validation); | ||||
|   | ||||
| @@ -21,7 +21,7 @@ title: Animal example | ||||
| classDiagram | ||||
|     note "From Duck till Zebra" | ||||
|     Animal <|-- Duck | ||||
|     note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging" | ||||
|     note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging" | ||||
|     Animal <|-- Fish | ||||
|     Animal <|-- Zebra | ||||
|     Animal : +int age | ||||
| @@ -50,7 +50,7 @@ title: Animal example | ||||
| classDiagram | ||||
|     note "From Duck till Zebra" | ||||
|     Animal <|-- Duck | ||||
|     note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging" | ||||
|     note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging" | ||||
|     Animal <|-- Fish | ||||
|     Animal <|-- Zebra | ||||
|     Animal : +int age | ||||
|   | ||||
| @@ -1,58 +0,0 @@ | ||||
| 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); | ||||
|   }); | ||||
| }); | ||||
| @@ -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_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();} | ||||
| <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();} | ||||
|  | ||||
| <attribute>",,"                           { return 'ATTRIBUTE_EMPTY';} | ||||
| <attribute>","                            { } | ||||
|   | ||||
| @@ -16,7 +16,7 @@ const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => { | ||||
|   const svgWidth = bitWidth * bitsPerRow + 2; | ||||
|   const svg: SVG = selectSvgElement(id); | ||||
|  | ||||
|   svg.attr('viewbox', `0 0 ${svgWidth} ${svgHeight}`); | ||||
|   svg.attr('viewBox', `0 0 ${svgWidth} ${svgHeight}`); | ||||
|   configureSvgSize(svg, svgHeight, svgWidth, config.useMaxWidth); | ||||
|  | ||||
|   for (const [word, packet] of words.entries()) { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ import type { Diagram } from '../../Diagram.js'; | ||||
| import type { RadarDiagramConfig } from '../../config.type.js'; | ||||
| import type { DiagramRenderer, DrawDefinition, SVG, SVGGroup } from '../../diagram-api/types.js'; | ||||
| import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; | ||||
| import { configureSvgSize } from '../../setupGraphViewbox.js'; | ||||
| import type { RadarDB, RadarAxis, RadarCurve } from './types.js'; | ||||
|  | ||||
| const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => { | ||||
| @@ -53,11 +54,9 @@ const drawFrame = (svg: SVG, config: Required<RadarDiagramConfig>): SVGGroup => | ||||
|     x: config.marginLeft + config.width / 2, | ||||
|     y: config.marginTop + config.height / 2, | ||||
|   }; | ||||
|   // Initialize the SVG | ||||
|   svg | ||||
|     .attr('viewbox', `0 0 ${totalWidth} ${totalHeight}`) | ||||
|     .attr('width', totalWidth) | ||||
|     .attr('height', totalHeight); | ||||
|   configureSvgSize(svg, totalHeight, totalWidth, config.useMaxWidth ?? true); | ||||
|  | ||||
|   svg.attr('viewBox', `0 0 ${totalWidth} ${totalHeight}`); | ||||
|   // g element to center the radar chart | ||||
|   return svg.append('g').attr('transform', `translate(${center.x}, ${center.y})`); | ||||
| }; | ||||
|   | ||||
| @@ -15,7 +15,7 @@ title: Animal example | ||||
| classDiagram | ||||
|     note "From Duck till Zebra" | ||||
|     Animal <|-- Duck | ||||
|     note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging" | ||||
|     note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging" | ||||
|     Animal <|-- Fish | ||||
|     Animal <|-- Zebra | ||||
|     Animal : +int age | ||||
|   | ||||
| @@ -20,11 +20,11 @@ fragment Statement: | ||||
| ; | ||||
|  | ||||
| fragment LeftPort: | ||||
|     ':'lhsDir=ARROW_DIRECTION | ||||
|     ':' lhsDir=ID | ||||
| ; | ||||
|  | ||||
| fragment RightPort: | ||||
|     rhsDir=ARROW_DIRECTION':' | ||||
|     rhsDir=ID ':' | ||||
| ; | ||||
|  | ||||
| fragment Arrow: | ||||
| @@ -47,6 +47,5 @@ Edge: | ||||
|     lhsId=ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ID rhsGroup?=ARROW_GROUP? EOL | ||||
| ; | ||||
|  | ||||
| terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B'; | ||||
| terminal ARROW_GROUP: /\{group\}/; | ||||
| terminal ARROW_INTO: /<|>/; | ||||
|   | ||||
| @@ -19,6 +19,64 @@ describe('architecture', () => { | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('should handle services', () => { | ||||
|     it('should handle service with icon', () => { | ||||
|       const context = `architecture-beta | ||||
|         service TH(disk) | ||||
|       `; | ||||
|       const result = parse(context); | ||||
|       expectNoErrorsOrAlternatives(result); | ||||
|       expect(result.value.$type).toBe(Architecture); | ||||
|       expect(result.value.services).toHaveLength(1); | ||||
|       expect(result.value.services?.[0].id).toBe('TH'); | ||||
|       expect(result.value.services?.[0].icon).toBe('disk'); | ||||
|     }); | ||||
|  | ||||
|     it('should handle service with icon starting with arrow direction letters', () => { | ||||
|       const context = `architecture-beta | ||||
|         service T(disk) | ||||
|         service TH(database) | ||||
|         service L(server) | ||||
|         service R(cloud) | ||||
|         service B(internet) | ||||
|         service TOP(disk) | ||||
|         service LEFT(disk) | ||||
|         service RIGHT(disk) | ||||
|         service BOTTOM(disk) | ||||
|       `; | ||||
|       const result = parse(context); | ||||
|       expectNoErrorsOrAlternatives(result); | ||||
|       expect(result.value.$type).toBe(Architecture); | ||||
|       expect(result.value.services).toHaveLength(9); | ||||
|     }); | ||||
|  | ||||
|     it('should handle service with icon and title', () => { | ||||
|       const context = `architecture-beta | ||||
|         service db(database)[Database] | ||||
|       `; | ||||
|       const result = parse(context); | ||||
|       expectNoErrorsOrAlternatives(result); | ||||
|       expect(result.value.$type).toBe(Architecture); | ||||
|       expect(result.value.services).toHaveLength(1); | ||||
|       expect(result.value.services?.[0].id).toBe('db'); | ||||
|       expect(result.value.services?.[0].icon).toBe('database'); | ||||
|       expect(result.value.services?.[0].title).toBe('Database'); | ||||
|     }); | ||||
|  | ||||
|     it('should handle service in a group', () => { | ||||
|       const context = `architecture-beta | ||||
|         group api(cloud)[API] | ||||
|         service db(database)[Database] in api | ||||
|       `; | ||||
|       const result = parse(context); | ||||
|       expectNoErrorsOrAlternatives(result); | ||||
|       expect(result.value.$type).toBe(Architecture); | ||||
|       expect(result.value.services).toHaveLength(1); | ||||
|       expect(result.value.services?.[0].id).toBe('db'); | ||||
|       expect(result.value.services?.[0].in).toBe('api'); | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   describe('should handle TitleAndAccessibilities', () => { | ||||
|     it.each([ | ||||
|       `architecture-beta title sample title`, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user