mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-11-17 19:24:10 +01:00
added parser unit tests and organized config in gitGraphAst.ts
This commit is contained in:
@@ -1,74 +0,0 @@
|
|||||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
||||||
import { parser } from './gitGraphParser.js';
|
|
||||||
import { db } from './gitGraphAst.js';
|
|
||||||
|
|
||||||
const parseInput = async (input: string) => {
|
|
||||||
await parser.parse(input);
|
|
||||||
};
|
|
||||||
|
|
||||||
const spyOn = vi.spyOn;
|
|
||||||
|
|
||||||
describe('GitGraph Parsing', function () {
|
|
||||||
beforeEach(() => {
|
|
||||||
db.clear();
|
|
||||||
});
|
|
||||||
it('should parse a default commit statement', async () => {
|
|
||||||
const input = `gitGraph:
|
|
||||||
commit
|
|
||||||
`;
|
|
||||||
const commitSpy = spyOn(db, 'commit');
|
|
||||||
await parseInput(input);
|
|
||||||
|
|
||||||
expect(commitSpy).toHaveBeenCalledWith('', undefined, 0, []);
|
|
||||||
commitSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse a basic branch statement with just a name', async () => {
|
|
||||||
const input = `gitGraph:
|
|
||||||
branch newBranch
|
|
||||||
`;
|
|
||||||
const branchSpy = spyOn(db, 'branch');
|
|
||||||
await parseInput(input);
|
|
||||||
expect(branchSpy).toHaveBeenCalledWith('newBranch', 0);
|
|
||||||
branchSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse a basic checkout statement', async () => {
|
|
||||||
const input = `gitGraph:
|
|
||||||
branch newBranch
|
|
||||||
checkout newBranch
|
|
||||||
`;
|
|
||||||
const checkoutSpy = spyOn(db, 'checkout');
|
|
||||||
await parseInput(input);
|
|
||||||
expect(checkoutSpy).toHaveBeenCalledWith('newBranch');
|
|
||||||
checkoutSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse a basic merge statement', async () => {
|
|
||||||
const input = `gitGraph:
|
|
||||||
commit
|
|
||||||
branch newBranch
|
|
||||||
checkout newBranch
|
|
||||||
commit
|
|
||||||
checkout main
|
|
||||||
merge newBranch`;
|
|
||||||
const mergeSpy = spyOn(db, 'merge');
|
|
||||||
await parseInput(input);
|
|
||||||
expect(mergeSpy).toHaveBeenCalledWith('newBranch', '', undefined, []);
|
|
||||||
mergeSpy.mockRestore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse cherry-picking', async () => {
|
|
||||||
const input = `gitGraph
|
|
||||||
commit id: "ZERO"
|
|
||||||
branch develop
|
|
||||||
commit id:"A"
|
|
||||||
checkout main
|
|
||||||
cherry-pick id:"A"
|
|
||||||
`;
|
|
||||||
const cherryPickSpy = spyOn(db, 'cherryPick');
|
|
||||||
await parseInput(input);
|
|
||||||
expect(cherryPickSpy).toHaveBeenCalledWith('A', '', undefined, undefined);
|
|
||||||
cherryPickSpy.mockRestore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -11,9 +11,10 @@ import type {
|
|||||||
MergeAst,
|
MergeAst,
|
||||||
CommitAst,
|
CommitAst,
|
||||||
BranchAst,
|
BranchAst,
|
||||||
|
GitGraphDBProvider,
|
||||||
} from './gitGraphTypes.js';
|
} from './gitGraphTypes.js';
|
||||||
|
|
||||||
const populate = (ast: GitGraph) => {
|
const populate = (ast: GitGraph, db: GitGraphDBProvider) => {
|
||||||
populateCommonDb(ast, db);
|
populateCommonDb(ast, db);
|
||||||
// @ts-ignore: this wont exist if the direction is not specified
|
// @ts-ignore: this wont exist if the direction is not specified
|
||||||
if (ast.dir) {
|
if (ast.dir) {
|
||||||
@@ -21,71 +22,107 @@ const populate = (ast: GitGraph) => {
|
|||||||
db.setDirection(ast.dir);
|
db.setDirection(ast.dir);
|
||||||
}
|
}
|
||||||
for (const statement of ast.statements) {
|
for (const statement of ast.statements) {
|
||||||
parseStatement(statement);
|
parseStatement(statement, db);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseStatement = (statement: any) => {
|
const parseStatement = (statement: any, db: GitGraphDBProvider) => {
|
||||||
switch (statement.$type) {
|
const parsers: Record<string, (stmt: any) => void> = {
|
||||||
case 'Commit':
|
Commit: (stmt) => db.commit(...parseCommit(stmt)),
|
||||||
parseCommit(statement);
|
Branch: (stmt) => db.branch(...parseBranch(stmt)),
|
||||||
break;
|
Merge: (stmt) => db.merge(...parseMerge(stmt)),
|
||||||
case 'Branch':
|
Checkout: (stmt) => db.checkout(parseCheckout(stmt)),
|
||||||
parseBranch(statement);
|
CherryPicking: (stmt) => db.cherryPick(...parseCherryPicking(stmt)),
|
||||||
break;
|
};
|
||||||
case 'Merge':
|
|
||||||
parseMerge(statement);
|
const parser = parsers[statement.$type];
|
||||||
break;
|
if (parser) {
|
||||||
case 'Checkout':
|
parser(statement);
|
||||||
parseCheckout(statement);
|
} else {
|
||||||
break;
|
log.error(`Unknown statement type: ${statement.$type}`);
|
||||||
case 'CherryPicking':
|
|
||||||
parseCherryPicking(statement);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
log.error(`Unknown statement type`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseCommit = (commit: CommitAst) => {
|
const parseCommit = (commit: CommitAst): [string, string, number, string[] | undefined] => {
|
||||||
const id = commit.id;
|
const id = commit.id;
|
||||||
const message = commit.message ?? '';
|
const message = commit.message ?? '';
|
||||||
const type = commit.type !== undefined ? commitType[commit.type] : commitType.NORMAL;
|
const type = commit.type !== undefined ? commitType[commit.type] : commitType.NORMAL;
|
||||||
const tags = commit.tags ?? undefined;
|
const tags = commit.tags ?? undefined;
|
||||||
|
|
||||||
db.commit(message, id, type, tags);
|
return [message, id, type, tags];
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseBranch = (branch: BranchAst) => {
|
const parseBranch = (branch: BranchAst): [string, number] => {
|
||||||
const name = branch.name;
|
const name = branch.name;
|
||||||
const order = branch.order ?? 0;
|
const order = branch.order ?? 0;
|
||||||
db.branch(name, order);
|
return [name, order];
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseMerge = (merge: MergeAst) => {
|
const parseMerge = (
|
||||||
|
merge: MergeAst
|
||||||
|
): [string, string, number | undefined, string[] | undefined] => {
|
||||||
const branch = merge.branch;
|
const branch = merge.branch;
|
||||||
const id = merge.id ?? '';
|
const id = merge.id ?? '';
|
||||||
const type = merge.type !== undefined ? commitType[merge.type] : undefined;
|
const type = merge.type !== undefined ? commitType[merge.type] : undefined;
|
||||||
const tags = merge.tags ?? undefined;
|
const tags = merge.tags ?? undefined;
|
||||||
db.merge(branch, id, type, tags);
|
return [branch, id, type, tags];
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseCheckout = (checkout: CheckoutAst) => {
|
const parseCheckout = (checkout: CheckoutAst): string => {
|
||||||
const branch = checkout.branch;
|
const branch = checkout.branch;
|
||||||
db.checkout(branch);
|
return branch;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseCherryPicking = (cherryPicking: CherryPickingAst) => {
|
const parseCherryPicking = (
|
||||||
|
cherryPicking: CherryPickingAst
|
||||||
|
): [string, string, string[] | undefined, string] => {
|
||||||
const id = cherryPicking.id;
|
const id = cherryPicking.id;
|
||||||
const tags = cherryPicking.tags?.length === 0 ? undefined : cherryPicking.tags;
|
const tags = cherryPicking.tags?.length === 0 ? undefined : cherryPicking.tags;
|
||||||
const parent = cherryPicking.parent;
|
const parent = cherryPicking.parent;
|
||||||
db.cherryPick(id, '', tags, parent);
|
return [id, '', tags, parent];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parser: ParserDefinition = {
|
export const parser: ParserDefinition = {
|
||||||
parse: async (input: string): Promise<void> => {
|
parse: async (input: string): Promise<void> => {
|
||||||
const ast: GitGraph = await parse('gitGraph', input);
|
const ast: GitGraph = await parse('gitGraph', input);
|
||||||
log.debug(ast);
|
log.debug(ast);
|
||||||
populate(ast);
|
populate(ast, db);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (import.meta.vitest) {
|
||||||
|
const { it, expect, describe } = import.meta.vitest;
|
||||||
|
|
||||||
|
const mockDB: GitGraphDBProvider = {
|
||||||
|
commitType: commitType,
|
||||||
|
setDirection: vi.fn(),
|
||||||
|
commit: vi.fn(),
|
||||||
|
branch: vi.fn(),
|
||||||
|
merge: vi.fn(),
|
||||||
|
cherryPick: vi.fn(),
|
||||||
|
checkout: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('GitGraph Parser', () => {
|
||||||
|
it('should parse a commit statement', () => {
|
||||||
|
const commit = {
|
||||||
|
$type: 'Commit',
|
||||||
|
id: '1',
|
||||||
|
message: 'test',
|
||||||
|
tags: ['tag1', 'tag2'],
|
||||||
|
type: 'NORMAL',
|
||||||
|
};
|
||||||
|
parseStatement(commit, mockDB);
|
||||||
|
expect(mockDB.commit).toHaveBeenCalledWith('test', '1', 0, ['tag1', 'tag2']);
|
||||||
|
});
|
||||||
|
it('should parse a branch statement', () => {
|
||||||
|
const branch = {
|
||||||
|
$type: 'Branch',
|
||||||
|
name: 'newBranch',
|
||||||
|
order: 1,
|
||||||
|
};
|
||||||
|
parseStatement(branch, mockDB);
|
||||||
|
expect(mockDB.branch).toHaveBeenCalledWith('newBranch', 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import { log } from '../../logger.js';
|
|||||||
import utils from '../../utils.js';
|
import utils from '../../utils.js';
|
||||||
import type { DrawDefinition } from '../../diagram-api/types.js';
|
import type { DrawDefinition } from '../../diagram-api/types.js';
|
||||||
import type d3 from 'd3';
|
import type d3 from 'd3';
|
||||||
import type { CommitType, Commit, GitGraphDB, DiagramOrientation } from './gitGraphTypes.js';
|
import type {
|
||||||
|
CommitType,
|
||||||
|
Commit,
|
||||||
|
GitGraphDBRenderProvider,
|
||||||
|
DiagramOrientation,
|
||||||
|
} from './gitGraphTypes.js';
|
||||||
import type { GitGraphDiagramConfig } from '../../config.type.js';
|
import type { GitGraphDiagramConfig } from '../../config.type.js';
|
||||||
|
|
||||||
let allCommitsDict = new Map();
|
let allCommitsDict = new Map();
|
||||||
@@ -770,7 +775,9 @@ const drawArrow = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (lineDef === undefined) {
|
||||||
|
throw new Error('Line definition not found');
|
||||||
|
}
|
||||||
svg
|
svg
|
||||||
.append('path')
|
.append('path')
|
||||||
.attr('d', lineDef)
|
.attr('d', lineDef)
|
||||||
@@ -889,7 +896,7 @@ export const draw: DrawDefinition = function (txt, id, ver, diagObj) {
|
|||||||
throw new Error('GitGraph config not found');
|
throw new Error('GitGraph config not found');
|
||||||
}
|
}
|
||||||
const rotateCommitLabel = gitGraphConfig.rotateCommitLabel ?? false;
|
const rotateCommitLabel = gitGraphConfig.rotateCommitLabel ?? false;
|
||||||
const db = diagObj.db as GitGraphDB;
|
const db = diagObj.db as GitGraphDBRenderProvider;
|
||||||
allCommitsDict = db.getCommits();
|
allCommitsDict = db.getCommits();
|
||||||
const branches = db.getBranchesAsObjArray();
|
const branches = db.getBranchesAsObjArray();
|
||||||
dir = db.getDirection();
|
dir = db.getDirection();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
|
||||||
import type { GitGraphDiagramConfig } from '../../config.type.js';
|
import type { GitGraphDiagramConfig } from '../../config.type.js';
|
||||||
|
import type { DiagramDBBase } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
export interface CommitType {
|
export interface CommitType {
|
||||||
NORMAL: number;
|
NORMAL: number;
|
||||||
@@ -66,27 +66,13 @@ export interface CherryPickingAst {
|
|||||||
parent: string;
|
parent: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitGraphDB extends DiagramDB {
|
export interface GitGraphDB extends DiagramDBBase<GitGraphDiagramConfig> {
|
||||||
// config
|
|
||||||
getConfig: () => GitGraphDiagramConfig | undefined;
|
|
||||||
|
|
||||||
// common db
|
|
||||||
clear: () => void;
|
|
||||||
setDiagramTitle: (title: string) => void;
|
|
||||||
getDiagramTitle: () => string;
|
|
||||||
setAccTitle: (title: string) => void;
|
|
||||||
getAccTitle: () => string;
|
|
||||||
setAccDescription: (description: string) => void;
|
|
||||||
getAccDescription: () => string;
|
|
||||||
|
|
||||||
// diagram db
|
|
||||||
commitType: CommitType;
|
commitType: CommitType;
|
||||||
setDirection: (direction: DiagramOrientation) => void;
|
setDirection: (dir: DiagramOrientation) => void;
|
||||||
getDirection: () => DiagramOrientation;
|
setOptions: (rawOptString: string) => void;
|
||||||
setOptions: (options: string) => void;
|
getOptions: () => any;
|
||||||
getOptions: () => string;
|
commit: (msg: string, id: string, type: number, tags?: string[]) => void;
|
||||||
commit: (msg: string, id: string, type: number, tags?: string[] | undefined) => void;
|
branch: (name: string, order?: number) => void;
|
||||||
branch: (name: string, order: number) => void;
|
|
||||||
merge: (
|
merge: (
|
||||||
otherBranch: string,
|
otherBranch: string,
|
||||||
customId?: string,
|
customId?: string,
|
||||||
@@ -101,12 +87,47 @@ export interface GitGraphDB extends DiagramDB {
|
|||||||
) => void;
|
) => void;
|
||||||
checkout: (branch: string) => void;
|
checkout: (branch: string) => void;
|
||||||
prettyPrint: () => void;
|
prettyPrint: () => void;
|
||||||
|
clear: () => void;
|
||||||
getBranchesAsObjArray: () => { name: string }[];
|
getBranchesAsObjArray: () => { name: string }[];
|
||||||
getBranches: () => Map<string, string | null>;
|
getBranches: () => Map<string, string | null>;
|
||||||
getCommits: () => Map<string, Commit>;
|
getCommits: () => Map<string, Commit>;
|
||||||
getCommitsArray: () => Commit[];
|
getCommitsArray: () => Commit[];
|
||||||
getCurrentBranch: () => string;
|
getCurrentBranch: () => string;
|
||||||
|
getDirection: () => DiagramOrientation;
|
||||||
getHead: () => Commit | null;
|
getHead: () => Commit | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GitGraphDBParseProvider extends Partial<GitGraphDB> {
|
||||||
|
commitType: CommitType;
|
||||||
|
setDirection: (dir: DiagramOrientation) => void;
|
||||||
|
commit: (msg: string, id: string, type: number, tags?: string[]) => void;
|
||||||
|
branch: (name: string, order?: number) => void;
|
||||||
|
merge: (
|
||||||
|
otherBranch: string,
|
||||||
|
customId?: string,
|
||||||
|
overrideType?: number,
|
||||||
|
customTags?: string[]
|
||||||
|
) => void;
|
||||||
|
cherryPick: (
|
||||||
|
sourceId: string,
|
||||||
|
targetId: string,
|
||||||
|
tags: string[] | undefined,
|
||||||
|
parentCommitId: string
|
||||||
|
) => void;
|
||||||
|
checkout: (branch: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitGraphDBRenderProvider extends Partial<GitGraphDB> {
|
||||||
|
prettyPrint: () => void;
|
||||||
|
clear: () => void;
|
||||||
|
getBranchesAsObjArray: () => { name: string }[];
|
||||||
|
getBranches: () => Map<string, string | null>;
|
||||||
|
getCommits: () => Map<string, Commit>;
|
||||||
|
getCommitsArray: () => Commit[];
|
||||||
|
getCurrentBranch: () => string;
|
||||||
|
getDirection: () => DiagramOrientation;
|
||||||
|
getHead: () => Commit | null;
|
||||||
|
getDiagramTitle: () => string;
|
||||||
|
}
|
||||||
|
|
||||||
export type DiagramOrientation = 'LR' | 'TB' | 'BT';
|
export type DiagramOrientation = 'LR' | 'TB' | 'BT';
|
||||||
|
|||||||
Reference in New Issue
Block a user