mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 06:19:24 +02:00
Merge remote-tracking branch 'origin/develop' into feature/3508_color-user-journey-title
Co-authored-by: Pranav Mishra <mishrap@dickinson.edu>
This commit is contained in:
@@ -559,6 +559,10 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
|
||||
* Margin between actors
|
||||
*/
|
||||
leftMargin?: number;
|
||||
/**
|
||||
* Maximum width of actor labels
|
||||
*/
|
||||
maxLabelWidth?: number;
|
||||
/**
|
||||
* Width of actor boxes
|
||||
*/
|
||||
|
@@ -0,0 +1,70 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
import { db } from './architectureDb.js';
|
||||
import { parser } from './architectureParser.js';
|
||||
|
||||
const {
|
||||
clear,
|
||||
getDiagramTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
getServices,
|
||||
getGroups,
|
||||
getEdges,
|
||||
getJunctions,
|
||||
} = db;
|
||||
|
||||
describe('architecture diagrams', () => {
|
||||
beforeEach(() => {
|
||||
clear();
|
||||
});
|
||||
|
||||
describe('architecture diagram definitions', () => {
|
||||
it('should handle the architecture keyword', async () => {
|
||||
const str = `architecture-beta`;
|
||||
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle an simple radar definition', async () => {
|
||||
const str = `architecture-beta
|
||||
service db
|
||||
`;
|
||||
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle TitleAndAccessibilities', () => {
|
||||
it('should handle title on the first line', async () => {
|
||||
const str = `architecture-beta title Simple Architecture Diagram`;
|
||||
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||
expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
|
||||
});
|
||||
|
||||
it('should handle title on another line', async () => {
|
||||
const str = `architecture-beta
|
||||
title Simple Architecture Diagram
|
||||
`;
|
||||
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||
expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
|
||||
});
|
||||
|
||||
it('should handle accessibility title and description', async () => {
|
||||
const str = `architecture-beta
|
||||
accTitle: Accessibility Title
|
||||
accDescr: Accessibility Description
|
||||
`;
|
||||
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||
expect(getAccTitle()).toBe('Accessibility Title');
|
||||
expect(getAccDescription()).toBe('Accessibility Description');
|
||||
});
|
||||
|
||||
it('should handle multiline accessibility description', async () => {
|
||||
const str = `architecture-beta
|
||||
accDescr {
|
||||
Accessibility Description
|
||||
}
|
||||
`;
|
||||
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||
expect(getAccDescription()).toBe('Accessibility Description');
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,6 +1,6 @@
|
||||
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import { getConfig as commonGetConfig } from '../../config.js';
|
||||
import type { D3Element } from '../../types.js';
|
||||
import { ImperativeState } from '../../utils/imperativeState.js';
|
||||
import {
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
isArchitectureService,
|
||||
shiftPositionByArchitectureDirectionPair,
|
||||
} from './architectureTypes.js';
|
||||
import { cleanAndMerge } from '../../utils.js';
|
||||
|
||||
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
|
||||
DEFAULT_CONFIG.architecture;
|
||||
@@ -316,6 +317,14 @@ const setElementForId = (id: string, element: D3Element) => {
|
||||
};
|
||||
const getElementById = (id: string) => state.records.elements[id];
|
||||
|
||||
const getConfig = (): Required<ArchitectureDiagramConfig> => {
|
||||
const config = cleanAndMerge({
|
||||
...DEFAULT_ARCHITECTURE_CONFIG,
|
||||
...commonGetConfig().architecture,
|
||||
});
|
||||
return config;
|
||||
};
|
||||
|
||||
export const db: ArchitectureDB = {
|
||||
clear,
|
||||
setDiagramTitle,
|
||||
@@ -324,6 +333,7 @@ export const db: ArchitectureDB = {
|
||||
getAccTitle,
|
||||
setAccDescription,
|
||||
getAccDescription,
|
||||
getConfig,
|
||||
|
||||
addService,
|
||||
getServices,
|
||||
@@ -348,9 +358,5 @@ export const db: ArchitectureDB = {
|
||||
export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
|
||||
field: T
|
||||
): Required<ArchitectureDiagramConfig>[T] {
|
||||
const arch = getConfig().architecture;
|
||||
if (arch?.[field]) {
|
||||
return arch[field] as Required<ArchitectureDiagramConfig>[T];
|
||||
}
|
||||
return DEFAULT_ARCHITECTURE_CONFIG[field];
|
||||
return getConfig()[field];
|
||||
}
|
||||
|
@@ -500,6 +500,8 @@ function layoutArchitecture(
|
||||
}
|
||||
|
||||
export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram) => {
|
||||
// TODO: Add title support for architecture diagrams
|
||||
|
||||
const db = diagObj.db as ArchitectureDB;
|
||||
|
||||
const services = db.getServices();
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
import type { DiagramDBBase } from '../../diagram-api/types.js';
|
||||
import type { ArchitectureDiagramConfig } from '../../config.type.js';
|
||||
import type { D3Element } from '../../types.js';
|
||||
import type cytoscape from 'cytoscape';
|
||||
@@ -242,7 +242,7 @@ export interface ArchitectureEdge<DT = ArchitectureDirection> {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface ArchitectureDB extends DiagramDB {
|
||||
export interface ArchitectureDB extends DiagramDBBase<ArchitectureDiagramConfig> {
|
||||
clear: () => void;
|
||||
addService: (service: Omit<ArchitectureService, 'edges'>) => void;
|
||||
getServices: () => ArchitectureService[];
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import { it, describe, expect } from 'vitest';
|
||||
import { db } from './db.js';
|
||||
import { parser } from './parser.js';
|
||||
import { renderer, relativeRadius, closedRoundCurve } from './renderer.js';
|
||||
import { relativeRadius, closedRoundCurve } from './renderer.js';
|
||||
import { Diagram } from '../../Diagram.js';
|
||||
import mermaidAPI from '../../mermaidAPI.js';
|
||||
import { a } from 'vitest/dist/chunks/suite.qtkXWc6R.js';
|
||||
import { buildRadarStyleOptions } from './styles.js';
|
||||
|
||||
const {
|
||||
clear,
|
||||
|
@@ -13,15 +13,17 @@ export const setConf = function (cnf) {
|
||||
};
|
||||
|
||||
const actors = {};
|
||||
let maxWidth = 0;
|
||||
|
||||
/** @param diagram - The diagram to draw to. */
|
||||
function drawActorLegend(diagram) {
|
||||
const conf = getConfig().journey;
|
||||
// Draw the actors
|
||||
const maxLabelWidth = conf.maxLabelWidth;
|
||||
maxWidth = 0;
|
||||
let yPos = 60;
|
||||
|
||||
Object.keys(actors).forEach((person) => {
|
||||
const colour = actors[person].color;
|
||||
|
||||
const circleData = {
|
||||
cx: 20,
|
||||
cy: yPos,
|
||||
@@ -32,21 +34,90 @@ function drawActorLegend(diagram) {
|
||||
};
|
||||
svgDraw.drawCircle(diagram, circleData);
|
||||
|
||||
const labelData = {
|
||||
x: 40,
|
||||
y: yPos + 7,
|
||||
fill: '#666',
|
||||
text: person,
|
||||
textMargin: conf.boxTextMargin | 5,
|
||||
};
|
||||
svgDraw.drawText(diagram, labelData);
|
||||
// First, measure the full text width without wrapping.
|
||||
let measureText = diagram.append('text').attr('visibility', 'hidden').text(person);
|
||||
const fullTextWidth = measureText.node().getBoundingClientRect().width;
|
||||
measureText.remove();
|
||||
|
||||
yPos += 20;
|
||||
let lines = [];
|
||||
|
||||
// If the text is naturally within the max width, use it as a single line.
|
||||
if (fullTextWidth <= maxLabelWidth) {
|
||||
lines = [person];
|
||||
} else {
|
||||
// Otherwise, wrap the text using the knuth-plass algorithm.
|
||||
const words = person.split(' '); // Split the text into words.
|
||||
let currentLine = '';
|
||||
measureText = diagram.append('text').attr('visibility', 'hidden');
|
||||
|
||||
words.forEach((word) => {
|
||||
// check the width of the line with the new word.
|
||||
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
||||
measureText.text(testLine);
|
||||
const textWidth = measureText.node().getBoundingClientRect().width;
|
||||
|
||||
if (textWidth > maxLabelWidth) {
|
||||
// If adding the new word exceeds max width, push the current line.
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
currentLine = word; // Start a new line with the current word.
|
||||
|
||||
// If the word itself is too long, break it with a hyphen.
|
||||
measureText.text(word);
|
||||
if (measureText.node().getBoundingClientRect().width > maxLabelWidth) {
|
||||
let brokenWord = '';
|
||||
for (const char of word) {
|
||||
brokenWord += char;
|
||||
measureText.text(brokenWord + '-');
|
||||
if (measureText.node().getBoundingClientRect().width > maxLabelWidth) {
|
||||
// Push the broken part with a hyphen.
|
||||
lines.push(brokenWord.slice(0, -1) + '-');
|
||||
brokenWord = char;
|
||||
}
|
||||
}
|
||||
currentLine = brokenWord;
|
||||
}
|
||||
} else {
|
||||
// If the line with the new word fits, add the new word to the current line.
|
||||
currentLine = testLine;
|
||||
}
|
||||
});
|
||||
|
||||
// Push the last line.
|
||||
if (currentLine) {
|
||||
lines.push(currentLine);
|
||||
}
|
||||
measureText.remove(); // Remove the text element used for measuring.
|
||||
}
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
const labelData = {
|
||||
x: 40,
|
||||
y: yPos + 7 + index * 20,
|
||||
fill: '#666',
|
||||
text: line,
|
||||
textMargin: conf.boxTextMargin ?? 5,
|
||||
};
|
||||
|
||||
// Draw the text and measure the width.
|
||||
const textElement = svgDraw.drawText(diagram, labelData);
|
||||
const lineWidth = textElement.node().getBoundingClientRect().width;
|
||||
|
||||
// Use conf.leftMargin as the initial spacing baseline,
|
||||
// but expand maxWidth if the line is wider.
|
||||
if (lineWidth > maxWidth && lineWidth > conf.leftMargin - lineWidth) {
|
||||
maxWidth = lineWidth;
|
||||
}
|
||||
});
|
||||
|
||||
yPos += Math.max(20, lines.length * 20);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Cleanup?
|
||||
const conf = getConfig().journey;
|
||||
const LEFT_MARGIN = conf.leftMargin;
|
||||
let leftMargin = 0;
|
||||
export const draw = function (text, id, version, diagObj) {
|
||||
const configObject = getConfig();
|
||||
const titleColor = configObject.journey.titleColor;
|
||||
@@ -87,7 +158,8 @@ export const draw = function (text, id, version, diagObj) {
|
||||
});
|
||||
|
||||
drawActorLegend(diagram);
|
||||
bounds.insert(0, 0, LEFT_MARGIN, Object.keys(actors).length * 50);
|
||||
leftMargin = conf.leftMargin + maxWidth;
|
||||
bounds.insert(0, 0, leftMargin, Object.keys(actors).length * 50);
|
||||
drawTasks(diagram, tasks, 0);
|
||||
|
||||
const box = bounds.getBounds();
|
||||
@@ -95,7 +167,7 @@ export const draw = function (text, id, version, diagObj) {
|
||||
diagram
|
||||
.append('text')
|
||||
.text(title)
|
||||
.attr('x', LEFT_MARGIN)
|
||||
.attr('x', leftMargin)
|
||||
.attr('font-size', titleFontSize)
|
||||
.attr('font-weight', 'bold')
|
||||
.attr('y', 25)
|
||||
@@ -104,16 +176,16 @@ export const draw = function (text, id, version, diagObj) {
|
||||
}
|
||||
|
||||
const height = box.stopy - box.starty + 2 * conf.diagramMarginY;
|
||||
const width = LEFT_MARGIN + box.stopx + 2 * conf.diagramMarginX;
|
||||
const width = leftMargin + box.stopx + 2 * conf.diagramMarginX;
|
||||
|
||||
configureSvgSize(diagram, height, width, conf.useMaxWidth);
|
||||
|
||||
// Draw activity line
|
||||
diagram
|
||||
.append('line')
|
||||
.attr('x1', LEFT_MARGIN)
|
||||
.attr('x1', leftMargin)
|
||||
.attr('y1', conf.height * 4) // One section head + one task + margins
|
||||
.attr('x2', width - LEFT_MARGIN - 4) // Subtract stroke width so arrow point is retained
|
||||
.attr('x2', width - leftMargin - 4) // Subtract stroke width so arrow point is retained
|
||||
.attr('y2', conf.height * 4)
|
||||
.attr('stroke-width', 4)
|
||||
.attr('stroke', 'black')
|
||||
@@ -239,7 +311,7 @@ export const drawTasks = function (diagram, tasks, verticalPos) {
|
||||
}
|
||||
|
||||
const section = {
|
||||
x: i * conf.taskMargin + i * conf.width + LEFT_MARGIN,
|
||||
x: i * conf.taskMargin + i * conf.width + leftMargin,
|
||||
y: 50,
|
||||
text: task.section,
|
||||
fill,
|
||||
@@ -263,7 +335,7 @@ export const drawTasks = function (diagram, tasks, verticalPos) {
|
||||
}, {});
|
||||
|
||||
// Add some rendering data to the object
|
||||
task.x = i * conf.taskMargin + i * conf.width + LEFT_MARGIN;
|
||||
task.x = i * conf.taskMargin + i * conf.width + leftMargin;
|
||||
task.y = taskPos;
|
||||
task.width = conf.diagramMarginX;
|
||||
task.height = conf.diagramMarginY;
|
||||
|
@@ -262,7 +262,5 @@ Communication tools and platforms
|
||||
- [reveal.js-mermaid-plugin](https://github.com/ludwick/reveal.js-mermaid-plugin)
|
||||
- [Reveal CK](https://github.com/jedcn/reveal-ck)
|
||||
- [reveal-ck-mermaid-plugin](https://github.com/tmtm/reveal-ck-mermaid-plugin)
|
||||
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
|
||||
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
|
||||
|
||||
<!--- cspell:ignore Blazorade HueHive --->
|
||||
|
@@ -407,17 +407,31 @@ erDiagram
|
||||
|
||||
## Configuration
|
||||
|
||||
### Renderer
|
||||
### Layout
|
||||
|
||||
The layout of the diagram is done with the renderer. The default renderer is dagre.
|
||||
The layout of the diagram is handled by [`render()`](../config/setup/mermaid/interfaces/Mermaid.md#render). The default layout is dagre.
|
||||
|
||||
You can opt to use an alternate renderer named elk by editing the configuration. The elk renderer is better for larger and/or more complex diagrams.
|
||||
For larger or more-complex diagrams, you can alternatively apply the ELK (Eclipse Layout Kernel) layout using your YAML frontmatter's `config`. For more information, see [Customizing ELK Layout](../intro/syntax-reference.md#customizing-elk-layout).
|
||||
|
||||
```yaml
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
```
|
||||
|
||||
Your Mermaid code should be similar to the following:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
title: Order example
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
|
||||
```note
|
||||
|
@@ -721,7 +721,7 @@ To give an edge an ID, prepend the edge syntax with the ID followed by an `@` ch
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
A e1@--> B
|
||||
```
|
||||
|
||||
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
|
||||
@@ -746,7 +746,7 @@ In the initial version, two animation speeds are supported: `fast` and `slow`. S
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
A e1@--> B
|
||||
e1@{ animation: fast }
|
||||
```
|
||||
|
||||
@@ -758,7 +758,7 @@ You can also animate edges by assigning a class to them and then defining animat
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A e1@–> B
|
||||
A e1@--> B
|
||||
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
|
||||
class e1 animate
|
||||
```
|
||||
|
@@ -31,6 +31,7 @@ vi.mock('./diagrams/xychart/xychartRenderer.js');
|
||||
vi.mock('./diagrams/requirement/requirementRenderer.js');
|
||||
vi.mock('./diagrams/sequence/sequenceRenderer.js');
|
||||
vi.mock('./diagrams/radar/renderer.js');
|
||||
vi.mock('./diagrams/architecture/architectureRenderer.js');
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
@@ -799,6 +800,7 @@ graph TD;A--x|text including URL space|B;`)
|
||||
{ textDiagramType: 'sequenceDiagram', expectedType: 'sequence' },
|
||||
{ textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' },
|
||||
{ textDiagramType: 'radar-beta', expectedType: 'radar' },
|
||||
{ textDiagramType: 'architecture-beta', expectedType: 'architecture' },
|
||||
];
|
||||
|
||||
describe('accessibility', () => {
|
||||
|
@@ -562,7 +562,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
|
||||
}
|
||||
let svgPath;
|
||||
let linePath = lineFunction(lineData);
|
||||
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
|
||||
const edgeStyles = Array.isArray(edge.style) ? edge.style : edge.style ? [edge.style] : [];
|
||||
let strokeColor = edgeStyles.find((style) => style?.startsWith('stroke:'));
|
||||
|
||||
if (edge.look === 'handDrawn') {
|
||||
|
@@ -1499,6 +1499,10 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
||||
type: integer
|
||||
default: 150
|
||||
minimum: 0
|
||||
maxLabelWidth:
|
||||
description: Maximum width of actor labels
|
||||
type: integer
|
||||
default: 360
|
||||
width:
|
||||
description: Width of actor boxes
|
||||
type: integer
|
||||
|
2
packages/parser/src/language/architecture/arch.langium
Normal file
2
packages/parser/src/language/architecture/arch.langium
Normal file
@@ -0,0 +1,2 @@
|
||||
terminal ARCH_ICON: /\([\w-:]+\)/;
|
||||
terminal ARCH_TITLE: /\[[\w ]+\]/;
|
@@ -1,14 +1,15 @@
|
||||
grammar Architecture
|
||||
import "../common/common";
|
||||
import "arch";
|
||||
|
||||
entry Architecture:
|
||||
NEWLINE*
|
||||
"architecture-beta"
|
||||
(
|
||||
NEWLINE* TitleAndAccessibilities
|
||||
| NEWLINE* Statement*
|
||||
| NEWLINE*
|
||||
)
|
||||
NEWLINE
|
||||
| TitleAndAccessibilities
|
||||
| Statement
|
||||
)*
|
||||
;
|
||||
|
||||
fragment Statement:
|
||||
@@ -31,25 +32,21 @@ fragment Arrow:
|
||||
;
|
||||
|
||||
Group:
|
||||
'group' id=ARCH_ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
|
||||
'group' id=ID icon=ARCH_ICON? title=ARCH_TITLE? ('in' in=ID)? EOL
|
||||
;
|
||||
|
||||
Service:
|
||||
'service' id=ARCH_ID (iconText=ARCH_TEXT_ICON | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ARCH_ID)? EOL
|
||||
'service' id=ID (iconText=STRING | icon=ARCH_ICON)? title=ARCH_TITLE? ('in' in=ID)? EOL
|
||||
;
|
||||
|
||||
Junction:
|
||||
'junction' id=ARCH_ID ('in' in=ARCH_ID)? EOL
|
||||
'junction' id=ID ('in' in=ID)? EOL
|
||||
;
|
||||
|
||||
Edge:
|
||||
lhsId=ARCH_ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ARCH_ID rhsGroup?=ARROW_GROUP? EOL
|
||||
lhsId=ID lhsGroup?=ARROW_GROUP? Arrow rhsId=ID rhsGroup?=ARROW_GROUP? EOL
|
||||
;
|
||||
|
||||
terminal ARROW_DIRECTION: 'L' | 'R' | 'T' | 'B';
|
||||
terminal ARCH_ID: /[\w]+/;
|
||||
terminal ARCH_TEXT_ICON: /\("[^"]+"\)/;
|
||||
terminal ARCH_ICON: /\([\w-:]+\)/;
|
||||
terminal ARCH_TITLE: /\[[\w ]+\]/;
|
||||
terminal ARROW_GROUP: /\{group\}/;
|
||||
terminal ARROW_INTO: /<|>/;
|
||||
|
@@ -1,22 +1,35 @@
|
||||
interface Common {
|
||||
accDescr?: string;
|
||||
accTitle?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
|
||||
;
|
||||
// Base terminals and fragments for common language constructs
|
||||
// Terminal Precedence: Lazy to Greedy
|
||||
// When imported, the terminals are considered after the terminals in the importing grammar
|
||||
// Note: Hence, to add a terminal greedier than the common terminals, import it separately after the common import
|
||||
|
||||
fragment EOL returns string:
|
||||
NEWLINE+ | EOF
|
||||
;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
|
||||
;
|
||||
|
||||
terminal BOOLEAN returns boolean: 'true' | 'false';
|
||||
|
||||
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
|
||||
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
|
||||
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
|
||||
|
||||
terminal FLOAT returns number: /[0-9]+\.[0-9]+(?!\.)/;
|
||||
terminal INT returns number: /0|[1-9][0-9]*(?!\.)/;
|
||||
terminal NUMBER returns number: FLOAT | INT;
|
||||
|
||||
terminal STRING returns string: /"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/;
|
||||
|
||||
// Alphanumerics with underscores and dashes
|
||||
// Must start with an alphanumeric or an underscore
|
||||
// Cant end with a dash
|
||||
terminal ID returns string: /[\w]([-\w]*\w)?/;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
|
||||
hidden terminal WHITESPACE: /[\t ]+/;
|
||||
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
|
||||
|
@@ -1,39 +1,15 @@
|
||||
grammar GitGraph
|
||||
|
||||
interface Common {
|
||||
accDescr?: string;
|
||||
accTitle?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
|
||||
;
|
||||
|
||||
fragment EOL returns string:
|
||||
NEWLINE+ | EOF
|
||||
;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
|
||||
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
|
||||
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
|
||||
|
||||
hidden terminal WHITESPACE: /[\t ]+/;
|
||||
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
|
||||
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
|
||||
import "../common/common";
|
||||
import "reference";
|
||||
|
||||
entry GitGraph:
|
||||
NEWLINE*
|
||||
('gitGraph' | 'gitGraph' ':' | 'gitGraph:' | ('gitGraph' Direction ':'))
|
||||
NEWLINE*
|
||||
(
|
||||
NEWLINE*
|
||||
(TitleAndAccessibilities |
|
||||
statements+=Statement |
|
||||
NEWLINE)*
|
||||
)
|
||||
NEWLINE
|
||||
| TitleAndAccessibilities
|
||||
| statements+=Statement
|
||||
)*
|
||||
;
|
||||
|
||||
Statement
|
||||
@@ -56,12 +32,12 @@ Commit:
|
||||
|'type:' type=('NORMAL' | 'REVERSE' | 'HIGHLIGHT')
|
||||
)* EOL;
|
||||
Branch:
|
||||
'branch' name=(ID|STRING)
|
||||
'branch' name=(REFERENCE|STRING)
|
||||
('order:' order=INT)?
|
||||
EOL;
|
||||
|
||||
Merge:
|
||||
'merge' branch=(ID|STRING)
|
||||
'merge' branch=(REFERENCE|STRING)
|
||||
(
|
||||
'id:' id=STRING
|
||||
|'tag:' tags+=STRING
|
||||
@@ -69,7 +45,7 @@ Merge:
|
||||
)* EOL;
|
||||
|
||||
Checkout:
|
||||
('checkout'|'switch') branch=(ID|STRING) EOL;
|
||||
('checkout'|'switch') branch=(REFERENCE|STRING) EOL;
|
||||
|
||||
CherryPicking:
|
||||
'cherry-pick'
|
||||
@@ -78,10 +54,3 @@ CherryPicking:
|
||||
|'tag:' tags+=STRING
|
||||
|'parent:' parent=STRING
|
||||
)* EOL;
|
||||
|
||||
|
||||
|
||||
terminal INT returns number: /[0-9]+(?=\s)/;
|
||||
terminal ID returns string: /\w([-\./\w]*[-\w])?/;
|
||||
terminal STRING: /"[^"]*"|'[^']*'/;
|
||||
|
||||
|
4
packages/parser/src/language/gitGraph/reference.langium
Normal file
4
packages/parser/src/language/gitGraph/reference.langium
Normal file
@@ -0,0 +1,4 @@
|
||||
// Alphanumerics with underscores, dashes, slashes, and dots
|
||||
// Must start with an alphanumeric or an underscore
|
||||
// Cant end with a dash, slash, or dot
|
||||
terminal REFERENCE returns string: /\w([-\./\w]*[-\w])?/;
|
@@ -12,7 +12,6 @@ export {
|
||||
Commit,
|
||||
Merge,
|
||||
Statement,
|
||||
isCommon,
|
||||
isInfo,
|
||||
isPacket,
|
||||
isPacketBlock,
|
||||
|
@@ -5,15 +5,12 @@ entry Packet:
|
||||
NEWLINE*
|
||||
"packet-beta"
|
||||
(
|
||||
NEWLINE* TitleAndAccessibilities blocks+=PacketBlock*
|
||||
| NEWLINE+ blocks+=PacketBlock+
|
||||
| NEWLINE*
|
||||
)
|
||||
TitleAndAccessibilities
|
||||
| blocks+=PacketBlock
|
||||
| NEWLINE
|
||||
)*
|
||||
;
|
||||
|
||||
PacketBlock:
|
||||
start=INT('-' end=INT)? ':' label=STRING EOL
|
||||
;
|
||||
|
||||
terminal INT returns number: /0|[1-9][0-9]*/;
|
||||
terminal STRING: /"[^"]*"|'[^']*'/;
|
||||
;
|
@@ -5,15 +5,12 @@ entry Pie:
|
||||
NEWLINE*
|
||||
"pie" showData?="showData"?
|
||||
(
|
||||
NEWLINE* TitleAndAccessibilities sections+=PieSection*
|
||||
| NEWLINE+ sections+=PieSection+
|
||||
| NEWLINE*
|
||||
)
|
||||
TitleAndAccessibilities
|
||||
| sections+=PieSection
|
||||
| NEWLINE
|
||||
)*
|
||||
;
|
||||
|
||||
PieSection:
|
||||
label=PIE_SECTION_LABEL ":" value=PIE_SECTION_VALUE EOL
|
||||
label=STRING ":" value=NUMBER EOL
|
||||
;
|
||||
|
||||
terminal PIE_SECTION_LABEL: /"[^"]+"/;
|
||||
terminal PIE_SECTION_VALUE returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;
|
||||
|
@@ -1,31 +1,5 @@
|
||||
grammar Radar
|
||||
// import "../common/common";
|
||||
// Note: The import statement breaks TitleAndAccessibilities probably because of terminal order definition
|
||||
// TODO: May need to change the common.langium to fix this
|
||||
|
||||
interface Common {
|
||||
accDescr?: string;
|
||||
accTitle?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
|
||||
;
|
||||
|
||||
fragment EOL returns string:
|
||||
NEWLINE+ | EOF
|
||||
;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
|
||||
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
|
||||
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
|
||||
|
||||
hidden terminal WHITESPACE: /[\t ]+/;
|
||||
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
|
||||
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
|
||||
import "../common/common";
|
||||
|
||||
entry Radar:
|
||||
NEWLINE*
|
||||
@@ -76,14 +50,6 @@ Option:
|
||||
| name='min' value=NUMBER
|
||||
| name='graticule' value=GRATICULE
|
||||
)
|
||||
;
|
||||
;
|
||||
|
||||
|
||||
terminal NUMBER returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;
|
||||
|
||||
terminal BOOLEAN returns boolean: 'true' | 'false';
|
||||
|
||||
terminal GRATICULE returns string: 'circle' | 'polygon';
|
||||
|
||||
terminal ID returns string: /[a-zA-Z_][a-zA-Z0-9\-_]*/;
|
||||
terminal STRING: /"[^"]*"|'[^']*'/;
|
||||
terminal GRATICULE returns string: 'circle' | 'polygon';
|
88
packages/parser/tests/architecture.test.ts
Normal file
88
packages/parser/tests/architecture.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { Architecture } from '../src/language/index.js';
|
||||
import { expectNoErrorsOrAlternatives, architectureParse as parse } from './test-util.js';
|
||||
|
||||
describe('architecture', () => {
|
||||
describe('should handle architecture definition', () => {
|
||||
it.each([
|
||||
`architecture-beta`,
|
||||
` architecture-beta `,
|
||||
`\tarchitecture-beta\t`,
|
||||
`
|
||||
\tarchitecture-beta
|
||||
`,
|
||||
])('should handle regular architecture', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Architecture);
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle TitleAndAccessibilities', () => {
|
||||
it.each([
|
||||
`architecture-beta title sample title`,
|
||||
` architecture-beta title sample title `,
|
||||
`\tarchitecture-beta\ttitle sample title\t`,
|
||||
`architecture-beta
|
||||
\ttitle sample title
|
||||
`,
|
||||
])('should handle regular architecture + title in same line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Architecture);
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`architecture-beta
|
||||
title sample title`,
|
||||
`architecture-beta
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular architecture + title in next line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Architecture);
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it('should handle regular architecture + title + accTitle + accDescr', () => {
|
||||
const context = `architecture-beta
|
||||
title sample title
|
||||
accTitle: sample accTitle
|
||||
accDescr: sample accDescr
|
||||
`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Architecture);
|
||||
|
||||
const { title, accTitle, accDescr } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
expect(accTitle).toBe('sample accTitle');
|
||||
expect(accDescr).toBe('sample accDescr');
|
||||
});
|
||||
|
||||
it('should handle regular architecture + title + accTitle + multi-line accDescr', () => {
|
||||
const context = `architecture-beta
|
||||
title sample title
|
||||
accTitle: sample accTitle
|
||||
accDescr {
|
||||
sample accDescr
|
||||
}
|
||||
`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Architecture);
|
||||
|
||||
const { title, accTitle, accDescr } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
expect(accTitle).toBe('sample accTitle');
|
||||
expect(accDescr).toBe('sample accDescr');
|
||||
});
|
||||
});
|
||||
});
|
@@ -63,6 +63,12 @@ describe('Parsing Branch Statements', () => {
|
||||
expect(branch.name).toBe('master');
|
||||
});
|
||||
|
||||
it('should parse a branch name starting with numbers', () => {
|
||||
const result = parse(`gitGraph\n commit\n branch 1.0.1\n`);
|
||||
const branch = result.value.statements[1] as Branch;
|
||||
expect(branch.name).toBe('1.0.1');
|
||||
});
|
||||
|
||||
it('should parse a branch with an order property', () => {
|
||||
const result = parse(`gitGraph\n commit\n branch feature order:1\n`);
|
||||
const branch = result.value.statements[1] as Branch;
|
||||
|
@@ -4,226 +4,247 @@ import { Pie } from '../src/language/index.js';
|
||||
import { expectNoErrorsOrAlternatives, pieParse as parse } from './test-util.js';
|
||||
|
||||
describe('pie', () => {
|
||||
it.each([
|
||||
`pie`,
|
||||
` pie `,
|
||||
`\tpie\t`,
|
||||
`
|
||||
describe('should handle pie definition with or without showData', () => {
|
||||
it.each([
|
||||
`pie`,
|
||||
` pie `,
|
||||
`\tpie\t`,
|
||||
`
|
||||
\tpie
|
||||
`,
|
||||
])('should handle regular pie', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
});
|
||||
])('should handle regular pie', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData`,
|
||||
` pie showData `,
|
||||
`\tpie\tshowData\t`,
|
||||
`
|
||||
it.each([
|
||||
`pie showData`,
|
||||
` pie showData `,
|
||||
`\tpie\tshowData\t`,
|
||||
`
|
||||
pie\tshowData
|
||||
`,
|
||||
])('should handle regular showData', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
])('should handle regular showData', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
const { showData } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
});
|
||||
});
|
||||
describe('should handle TitleAndAccessibilities', () => {
|
||||
describe('should handle TitleAndAccessibilities without showData', () => {
|
||||
it.each([
|
||||
`pie title sample title`,
|
||||
` pie title sample title `,
|
||||
`\tpie\ttitle sample title\t`,
|
||||
`pie
|
||||
\ttitle sample title
|
||||
`,
|
||||
])('should handle regular pie + title in same line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
it.each([
|
||||
`pie title sample title`,
|
||||
` pie title sample title `,
|
||||
`\tpie\ttitle sample title\t`,
|
||||
`pie
|
||||
\ttitle sample title
|
||||
`,
|
||||
])('should handle regular pie + title in same line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie
|
||||
title sample title`,
|
||||
`pie
|
||||
title sample title
|
||||
`,
|
||||
`pie
|
||||
title sample title`,
|
||||
`pie
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular pie + title in different line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData title sample title`,
|
||||
`pie showData title sample title
|
||||
`,
|
||||
])('should handle regular pie + showData + title', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, title } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData
|
||||
title sample title`,
|
||||
`pie showData
|
||||
title sample title
|
||||
`,
|
||||
`pie showData
|
||||
title sample title`,
|
||||
`pie showData
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular showData + title in different line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, title } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
describe('sections', () => {
|
||||
describe('normal', () => {
|
||||
it.each([
|
||||
`pie
|
||||
title sample title`,
|
||||
`pie
|
||||
title sample title
|
||||
`,
|
||||
`pie
|
||||
title sample title`,
|
||||
`pie
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular pie + title in different line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle TitleAndAccessibilities with showData', () => {
|
||||
it.each([
|
||||
`pie showData title sample title`,
|
||||
`pie showData title sample title
|
||||
`,
|
||||
])('should handle regular pie + showData + title', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, title } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData
|
||||
title sample title`,
|
||||
`pie showData
|
||||
title sample title
|
||||
`,
|
||||
`pie showData
|
||||
title sample title`,
|
||||
`pie showData
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular showData + title in different line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, title } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should handle sections', () => {
|
||||
it.each([
|
||||
`pie
|
||||
"GitHub":100
|
||||
"GitLab":50`,
|
||||
`pie
|
||||
`pie
|
||||
"GitHub" : 100
|
||||
"GitLab" : 50`,
|
||||
`pie
|
||||
`pie
|
||||
"GitHub"\t:\t100
|
||||
"GitLab"\t:\t50`,
|
||||
`pie
|
||||
`pie
|
||||
\t"GitHub" \t : \t 100
|
||||
\t"GitLab" \t : \t 50
|
||||
`,
|
||||
])('should handle regular sections', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
])('should handle regular sections', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { sections } = result.value;
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
const { sections } = result.value;
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with showData', () => {
|
||||
const context = `pie showData
|
||||
it('should handle sections with showData', () => {
|
||||
const context = `pie showData
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, sections } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
const { showData, sections } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with title', () => {
|
||||
const context = `pie title sample wow
|
||||
it('should handle sections with title', () => {
|
||||
const context = `pie title sample wow
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { title, sections } = result.value;
|
||||
expect(title).toBe('sample wow');
|
||||
const { title, sections } = result.value;
|
||||
expect(title).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with accTitle', () => {
|
||||
const context = `pie accTitle: sample wow
|
||||
it('should handle value with positive decimal', () => {
|
||||
const context = `pie
|
||||
"ash": 60.67
|
||||
"bat": 40`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { sections } = result.value;
|
||||
expect(sections[0].label).toBe('ash');
|
||||
expect(sections[0].value).toBe(60.67);
|
||||
|
||||
expect(sections[1].label).toBe('bat');
|
||||
expect(sections[1].value).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle sections with accTitle', () => {
|
||||
const context = `pie accTitle: sample wow
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { accTitle, sections } = result.value;
|
||||
expect(accTitle).toBe('sample wow');
|
||||
const { accTitle, sections } = result.value;
|
||||
expect(accTitle).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with single line accDescr', () => {
|
||||
const context = `pie accDescr: sample wow
|
||||
it('should handle sections with single line accDescr', () => {
|
||||
const context = `pie accDescr: sample wow
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { accDescr, sections } = result.value;
|
||||
expect(accDescr).toBe('sample wow');
|
||||
const { accDescr, sections } = result.value;
|
||||
expect(accDescr).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with multi line accDescr', () => {
|
||||
const context = `pie accDescr {
|
||||
it('should handle sections with multi line accDescr', () => {
|
||||
const context = `pie accDescr {
|
||||
sample wow
|
||||
}
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { accDescr, sections } = result.value;
|
||||
expect(accDescr).toBe('sample wow');
|
||||
const { accDescr, sections } = result.value;
|
||||
expect(accDescr).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
import { expect, vi } from 'vitest';
|
||||
import type {
|
||||
Architecture,
|
||||
ArchitectureServices,
|
||||
Info,
|
||||
InfoServices,
|
||||
Pie,
|
||||
@@ -13,6 +15,7 @@ import type {
|
||||
GitGraphServices,
|
||||
} from '../src/language/index.js';
|
||||
import {
|
||||
createArchitectureServices,
|
||||
createInfoServices,
|
||||
createPieServices,
|
||||
createRadarServices,
|
||||
@@ -47,6 +50,17 @@ export function createInfoTestServices() {
|
||||
}
|
||||
export const infoParse = createInfoTestServices().parse;
|
||||
|
||||
const architectureServices: ArchitectureServices = createArchitectureServices().Architecture;
|
||||
const architectureParser: LangiumParser = architectureServices.parser.LangiumParser;
|
||||
export function createArchitectureTestServices() {
|
||||
const parse = (input: string) => {
|
||||
return architectureParser.parse<Architecture>(input);
|
||||
};
|
||||
|
||||
return { services: architectureServices, parse };
|
||||
}
|
||||
export const architectureParse = createArchitectureTestServices().parse;
|
||||
|
||||
const pieServices: PieServices = createPieServices().Pie;
|
||||
const pieParser: LangiumParser = pieServices.parser.LangiumParser;
|
||||
export function createPieTestServices() {
|
||||
|
Reference in New Issue
Block a user