diff --git a/packages/mermaid/src/diagrams/git/gitGraph.parser.spec.ts b/packages/mermaid/src/diagrams/git/gitGraph.parser.spec.ts deleted file mode 100644 index 1a38a8217..000000000 --- a/packages/mermaid/src/diagrams/git/gitGraph.parser.spec.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/packages/mermaid/src/diagrams/git/gitGraphParser.ts b/packages/mermaid/src/diagrams/git/gitGraphParser.ts index b014d7e57..1ca863329 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphParser.ts +++ b/packages/mermaid/src/diagrams/git/gitGraphParser.ts @@ -11,9 +11,10 @@ import type { MergeAst, CommitAst, BranchAst, + GitGraphDBProvider, } from './gitGraphTypes.js'; -const populate = (ast: GitGraph) => { +const populate = (ast: GitGraph, db: GitGraphDBProvider) => { populateCommonDb(ast, db); // @ts-ignore: this wont exist if the direction is not specified if (ast.dir) { @@ -21,71 +22,107 @@ const populate = (ast: GitGraph) => { db.setDirection(ast.dir); } for (const statement of ast.statements) { - parseStatement(statement); + parseStatement(statement, db); } }; -const parseStatement = (statement: any) => { - switch (statement.$type) { - case 'Commit': - parseCommit(statement); - break; - case 'Branch': - parseBranch(statement); - break; - case 'Merge': - parseMerge(statement); - break; - case 'Checkout': - parseCheckout(statement); - break; - case 'CherryPicking': - parseCherryPicking(statement); - break; - default: - log.error(`Unknown statement type`); +const parseStatement = (statement: any, db: GitGraphDBProvider) => { + const parsers: Record void> = { + Commit: (stmt) => db.commit(...parseCommit(stmt)), + Branch: (stmt) => db.branch(...parseBranch(stmt)), + Merge: (stmt) => db.merge(...parseMerge(stmt)), + Checkout: (stmt) => db.checkout(parseCheckout(stmt)), + CherryPicking: (stmt) => db.cherryPick(...parseCherryPicking(stmt)), + }; + + const parser = parsers[statement.$type]; + if (parser) { + parser(statement); + } else { + log.error(`Unknown statement type: ${statement.$type}`); } }; -const parseCommit = (commit: CommitAst) => { +const parseCommit = (commit: CommitAst): [string, string, number, string[] | undefined] => { const id = commit.id; const message = commit.message ?? ''; const type = commit.type !== undefined ? commitType[commit.type] : commitType.NORMAL; 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 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 id = merge.id ?? ''; const type = merge.type !== undefined ? commitType[merge.type] : 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; - db.checkout(branch); + return branch; }; -const parseCherryPicking = (cherryPicking: CherryPickingAst) => { +const parseCherryPicking = ( + cherryPicking: CherryPickingAst +): [string, string, string[] | undefined, string] => { const id = cherryPicking.id; const tags = cherryPicking.tags?.length === 0 ? undefined : cherryPicking.tags; const parent = cherryPicking.parent; - db.cherryPick(id, '', tags, parent); + return [id, '', tags, parent]; }; export const parser: ParserDefinition = { parse: async (input: string): Promise => { const ast: GitGraph = await parse('gitGraph', input); 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); + }); + }); +} diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.ts b/packages/mermaid/src/diagrams/git/gitGraphRenderer.ts index 0408baa23..c3862e581 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.ts +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.ts @@ -4,7 +4,12 @@ import { log } from '../../logger.js'; import utils from '../../utils.js'; import type { DrawDefinition } from '../../diagram-api/types.js'; 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'; let allCommitsDict = new Map(); @@ -770,7 +775,9 @@ const drawArrow = ( } } } - + if (lineDef === undefined) { + throw new Error('Line definition not found'); + } svg .append('path') .attr('d', lineDef) @@ -889,7 +896,7 @@ export const draw: DrawDefinition = function (txt, id, ver, diagObj) { throw new Error('GitGraph config not found'); } const rotateCommitLabel = gitGraphConfig.rotateCommitLabel ?? false; - const db = diagObj.db as GitGraphDB; + const db = diagObj.db as GitGraphDBRenderProvider; allCommitsDict = db.getCommits(); const branches = db.getBranchesAsObjArray(); dir = db.getDirection(); diff --git a/packages/mermaid/src/diagrams/git/gitGraphTypes.ts b/packages/mermaid/src/diagrams/git/gitGraphTypes.ts index b473dd874..b888cc297 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphTypes.ts +++ b/packages/mermaid/src/diagrams/git/gitGraphTypes.ts @@ -1,5 +1,5 @@ -import type { DiagramDB } from '../../diagram-api/types.js'; import type { GitGraphDiagramConfig } from '../../config.type.js'; +import type { DiagramDBBase } from '../../diagram-api/types.js'; export interface CommitType { NORMAL: number; @@ -66,27 +66,13 @@ export interface CherryPickingAst { parent: string; } -export interface GitGraphDB extends DiagramDB { - // 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 +export interface GitGraphDB extends DiagramDBBase { commitType: CommitType; - setDirection: (direction: DiagramOrientation) => void; - getDirection: () => DiagramOrientation; - setOptions: (options: string) => void; - getOptions: () => string; - commit: (msg: string, id: string, type: number, tags?: string[] | undefined) => void; - branch: (name: string, order: number) => void; + setDirection: (dir: DiagramOrientation) => void; + setOptions: (rawOptString: string) => void; + getOptions: () => any; + commit: (msg: string, id: string, type: number, tags?: string[]) => void; + branch: (name: string, order?: number) => void; merge: ( otherBranch: string, customId?: string, @@ -101,12 +87,47 @@ export interface GitGraphDB extends DiagramDB { ) => void; checkout: (branch: string) => void; prettyPrint: () => void; + clear: () => void; getBranchesAsObjArray: () => { name: string }[]; getBranches: () => Map; getCommits: () => Map; getCommitsArray: () => Commit[]; getCurrentBranch: () => string; + getDirection: () => DiagramOrientation; getHead: () => Commit | null; } +export interface GitGraphDBParseProvider extends Partial { + 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 { + prettyPrint: () => void; + clear: () => void; + getBranchesAsObjArray: () => { name: string }[]; + getBranches: () => Map; + getCommits: () => Map; + getCommitsArray: () => Commit[]; + getCurrentBranch: () => string; + getDirection: () => DiagramOrientation; + getHead: () => Commit | null; + getDiagramTitle: () => string; +} + export type DiagramOrientation = 'LR' | 'TB' | 'BT';