Compare commits

..

4 Commits

Author SHA1 Message Date
darshanr0107
256e81bcd2 fix: added E2E test
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-11-12 12:04:49 +05:30
darshanr0107
513a3eef98 fix: handle merging master/main into other branches in GitGraph
on-behalf-of: @Mermaid-Chart <hello@mermaidchart.com>
2025-11-11 20:17:40 +05:30
Sidharth Vinod
85a13da40f Merge pull request #7138 from mermaid-js/update-timings
Update E2E Timings
2025-11-07 21:42:11 +09:00
github-actions[bot]
1269486124 chore: update E2E timings 2025-11-07 04:15:01 +00:00
9 changed files with 126 additions and 217 deletions

View File

@@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Handle master/main merges correctly in GitGraph diagrams

View File

@@ -1,5 +0,0 @@
---
'mermaid': patch
---
fix: Added support for styling class diagram elements based on stereotype annotations

View File

@@ -1042,88 +1042,4 @@ class C13["With Città foreign language"]
{ logLevel: 1, htmlLabels: true }
);
});
it('should render a full class diagram using interface annotation', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
`,
{ logLevel: 1, htmlLabels: true }
);
});
it('should render a full class diagram using abstract annotation', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;abstract&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
`,
{ logLevel: 1, htmlLabels: true }
);
});
it('should render a full class diagram using enumeration annotation', () => {
imgSnapshotTest(
`
classDiagram
Class01 <|-- AveryLongClass : Cool
&lt;&lt;enumeration&gt;&gt; Class01
Class03 "0" *-- "0..n" Class04
Class05 "1" o-- "many" Class06
Class07 .. Class08
Class09 "many" --> "1" C2 : Where am i?
Class09 "0" --* "1..n" C3
Class09 --|> Class07
Class07 : equals()
Class07 : Object[] elementData
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class08 <--> C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
size()
}
`,
{ logLevel: 1, htmlLabels: true }
);
});
});

View File

@@ -1569,4 +1569,14 @@ gitGraph TB:
{}
);
});
it('77: should render a gitGraph merging main into a newly created branch', () => {
imgSnapshotTest(
`gitGraph
commit
branch stable
checkout stable
merge main`,
{}
);
});
});

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

@@ -36,46 +36,15 @@ export async function textHelper<T extends SVGGraphicsElement>(
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
if (node.annotations.length > 0) {
await addText(
annotationGroup,
{ text: `«${node.annotations[0]}»` } as unknown as ClassMember,
0,
[]
);
annotationGroup.style('opacity', '1');
const annotation = node.annotations[0];
await addText(annotationGroup, { text: `«${annotation}»` } as unknown as ClassMember, 0);
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroupHeight = annotationGroupBBox.height;
}
labelGroup = shapeSvg.insert('g').attr('class', 'label-group text');
// Determine styling based on annotations
let labelStyles = [''];
let labelClass = '';
if (node.annotations && node.annotations.length > 0) {
const annotation = node.annotations[0].toLowerCase();
switch (annotation) {
case 'abstract':
labelClass = 'abstract';
labelStyles = [];
break;
case 'enumeration':
labelClass = 'enumeration';
labelStyles = [];
break;
case 'interface':
labelClass = 'interface';
labelStyles = [];
break;
default:
labelClass = '';
labelStyles = [];
break;
}
}
// Apply the CSS class to the label group
labelGroup.attr('class', `label-group text classTitle ${labelClass}`);
await addText(labelGroup, node, 0, labelStyles);
await addText(labelGroup, node, 0, ['font-weight: bolder']);
const labelGroupBBox = labelGroup.node()!.getBBox();
labelGroupHeight = labelGroupBBox.height;
@@ -102,7 +71,7 @@ export async function textHelper<T extends SVGGraphicsElement>(
// Center annotation
if (annotationGroup !== null) {
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroup.attr('transform', `translate(${-annotationGroupBBox.width / 2}, 0)`);
annotationGroup.attr('transform', `translate(${-annotationGroupBBox.width / 2})`);
}
// Adjust label

View File

@@ -55,7 +55,7 @@ const getStyles = (options) =>
}
.classTitle {
font-weight: normal;
font-weight: bolder;
}
.node rect,
.node circle,
@@ -172,20 +172,6 @@ g.classGroup line {
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
.classTitle.abstract {
font-style: italic;
font-weight: normal;
}
.classTitle.enumeration {
text-decoration: underline;
font-weight: normal;
}
.classTitle.interface {
font-weight: bold;
}
.edgeTerminals {
font-size: 11px;

View File

@@ -1,6 +1,7 @@
import { log } from '../../logger.js';
import { db } from './gitGraphAst.js';
import { parser } from './gitGraphParser.js';
import { commitType } from './gitGraphTypes.js';
describe('when parsing a gitGraph', function () {
beforeEach(function () {
@@ -843,6 +844,39 @@ describe('when parsing a gitGraph', function () {
expect(db.getBranchesAsObjArray()).toStrictEqual([{ name: 'main' }, { name: 'testBranch' }]);
});
it('should handle merging the same branch multiple times', async () => {
const str = `gitGraph:
commit
branch stable
checkout stable
merge main
checkout main
commit
commit
checkout stable
merge main
`;
await parser.parse(str);
const commits = db.getCommits();
expect(commits.size).toBe(5);
expect(db.getCurrentBranch()).toBe('stable');
expect(db.getDirection()).toBe('LR');
expect(db.getBranches().size).toBe(2);
const commitsArray = db.getCommitsArray();
expect(commitsArray[0].branch).toBe('main');
expect(commitsArray[0].parents).toStrictEqual([]);
expect(commitsArray[1].branch).toBe('stable');
expect(commitsArray[1].type).toBe(commitType.MERGE);
expect(commitsArray[1].parents.length).toBe(2);
expect(commitsArray[2].branch).toBe('main');
expect(commitsArray[3].branch).toBe('main');
expect(commitsArray[4].branch).toBe('stable');
expect(commitsArray[4].type).toBe(commitType.MERGE);
expect(commitsArray[4].parents.length).toBe(2);
});
it('should handle merge with custom ids, tags and type', async () => {
const str = `gitGraph:
commit
@@ -1236,7 +1270,7 @@ describe('when parsing a gitGraph', function () {
);
}
});
it('should throw error when trying to merge branches having same heads', async () => {
it('should allow merging branches having same heads', async () => {
const str = `gitGraph
commit
branch testBranch
@@ -1244,13 +1278,19 @@ describe('when parsing a gitGraph', function () {
merge testBranch
`;
try {
await parser.parse(str);
// Fail test if above expression doesn't throw anything.
expect(true).toBe(false);
} catch (e: any) {
expect(e.message).toBe('Incorrect usage of "merge". Both branches have same head');
}
await parser.parse(str);
const commits = db.getCommits();
expect(commits.size).toBe(2);
expect(db.getCurrentBranch()).toBe('main');
const commitsArray = db.getCommitsArray();
expect(commitsArray[0].branch).toBe('main');
expect(commitsArray[0].parents).toStrictEqual([]);
expect(commitsArray[1].branch).toBe('main');
expect(commitsArray[1].type).toBe(commitType.MERGE);
expect(commitsArray[1].parents.length).toBe(2);
expect(commitsArray[1].parents[0]).toBe(commitsArray[0].id);
expect(commitsArray[1].parents[1]).toBe(commitsArray[0].id);
});
it('should throw error when trying to merge branch which has no commits', async () => {
const str = `gitGraph

View File

@@ -167,9 +167,6 @@ export const merge = (mergeDB: MergeDB): void => {
const otherCommit: Commit | undefined = otherBranchCheck
? state.records.commits.get(otherBranchCheck)
: undefined;
if (currentCommit && otherCommit && currentCommit.branch === otherBranch) {
throw new Error(`Cannot merge branch '${otherBranch}' into itself.`);
}
if (state.records.currBranch === otherBranch) {
const error: any = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
error.hash = {
@@ -212,15 +209,6 @@ export const merge = (mergeDB: MergeDB): void => {
};
throw error;
}
if (currentCommit === otherCommit) {
const error: any = new Error('Incorrect usage of "merge". Both branches have same head');
error.hash = {
text: `merge ${otherBranch}`,
token: `merge ${otherBranch}`,
expected: ['branch abc'],
};
throw error;
}
if (customId && state.records.commits.has(customId)) {
const error: any = new Error(
'Incorrect usage of "merge". Commit with id:' +