| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import type { Branch, Merge } from '../src/language/index.js'; |
| 3 | import { gitGraphParse as parse } from './test-util.js'; |
| 4 | import type { Commit } from '../src/language/index.js'; |
| 5 | import type { Checkout, CherryPicking } from '../src/language/generated/ast.js'; |
| 6 | |
| 7 | describe('Parsing Commit Statements', () => { |
| 8 | it('should parse a simple commit', () => { |
| 9 | const result = parse(`gitGraph\n commit\n`); |
| 10 | expect(result.value.statements[0].$type).toBe('Commit'); |
| 11 | }); |
| 12 | |
| 13 | it('should parse multiple commits', () => { |
| 14 | const result = parse(`gitGraph\n commit\n commit\n commit\n`); |
| 15 | expect(result.value.statements).toHaveLength(3); |
| 16 | }); |
| 17 | |
| 18 | it('should parse commits with all properties', () => { |
| 19 | const result = parse(`gitGraph\n commit id:"1" msg:"Fix bug" tag:"v1.2" type:NORMAL\n`); |
| 20 | const commit = result.value.statements[0] as Commit; |
| 21 | expect(commit.$type).toBe('Commit'); |
| 22 | expect(commit.id).toBe('1'); |
| 23 | expect(commit.message).toBe('Fix bug'); |
| 24 | expect(commit.tags).toEqual(['v1.2']); |
| 25 | expect(commit.type).toBe('NORMAL'); |
| 26 | }); |
| 27 | |
| 28 | it('should handle commit messages with special characters', () => { |
| 29 | const result = parse(`gitGraph\n commit msg:"Fix issue #123: Handle errors"\n`); |
| 30 | const commit = result.value.statements[0] as Commit; |
| 31 | expect(commit.message).toBe('Fix issue #123: Handle errors'); |
| 32 | }); |
| 33 | |
| 34 | it('should parse commits with only a message and no other properties', () => { |
| 35 | const result = parse(`gitGraph\n commit msg:"Initial release"\n`); |
| 36 | const commit = result.value.statements[0] as Commit; |
| 37 | expect(commit.message).toBe('Initial release'); |
| 38 | expect(commit.id).toBeUndefined(); |
| 39 | expect(commit.type).toBeUndefined(); |
| 40 | }); |
| 41 | |
| 42 | it('should ignore malformed properties and not break parsing', () => { |
| 43 | const result = parse(`gitGraph\n commit id:"2" msg:"Malformed commit" oops:"ignored"\n`); |
| 44 | const commit = result.value.statements[0] as Commit; |
| 45 | expect(commit.id).toBe('2'); |
| 46 | expect(commit.message).toBe('Malformed commit'); |
| 47 | expect(commit.hasOwnProperty('oops')).toBe(false); |
| 48 | }); |
| 49 | |
| 50 | it('should parse multiple commits with different types', () => { |
| 51 | const result = parse(`gitGraph\n commit type:NORMAL\n commit type:REVERSE\n`); |
| 52 | const commit1 = result.value.statements[0] as Commit; |
| 53 | const commit2 = result.value.statements[1] as Commit; |
| 54 | expect(commit1.type).toBe('NORMAL'); |
| 55 | expect(commit2.type).toBe('REVERSE'); |
| 56 | }); |
| 57 | }); |
| 58 | |
| 59 | describe('Parsing Branch Statements', () => { |
| 60 | it('should parse a branch with a simple name', () => { |
| 61 | const result = parse(`gitGraph\n commit\n commit\n branch master\n`); |
| 62 | const branch = result.value.statements[2] as Branch; |
| 63 | expect(branch.name).toBe('master'); |
| 64 | }); |
| 65 | |
| 66 | it('should parse a branch name starting with numbers', () => { |
| 67 | const result = parse(`gitGraph\n commit\n branch 1.0.1\n`); |
| 68 | const branch = result.value.statements[1] as Branch; |
| 69 | expect(branch.name).toBe('1.0.1'); |
| 70 | }); |
| 71 | |
| 72 | it('should parse a branch with an order property', () => { |
| 73 | const result = parse(`gitGraph\n commit\n branch feature order:1\n`); |
| 74 | const branch = result.value.statements[1] as Branch; |
| 75 | expect(branch.name).toBe('feature'); |
| 76 | expect(branch.order).toBe(1); |
| 77 | }); |
| 78 | |
| 79 | it('should handle branch names with special characters', () => { |
| 80 | const result = parse(`gitGraph\n branch feature/test-branch\n`); |
| 81 | const branch = result.value.statements[0] as Branch; |
| 82 | expect(branch.name).toBe('feature/test-branch'); |
| 83 | }); |
| 84 | |
| 85 | it('should parse branches with hyphens and underscores', () => { |
| 86 | const result = parse(`gitGraph\n branch my-feature_branch\n`); |
| 87 | const branch = result.value.statements[0] as Branch; |
| 88 | expect(branch.name).toBe('my-feature_branch'); |
| 89 | }); |
| 90 | |
| 91 | it('should correctly handle branch without order property', () => { |
| 92 | const result = parse(`gitGraph\n branch feature\n`); |
| 93 | const branch = result.value.statements[0] as Branch; |
| 94 | expect(branch.name).toBe('feature'); |
| 95 | expect(branch.order).toBeUndefined(); |
| 96 | }); |
| 97 | }); |
| 98 | |
| 99 | describe('Parsing Merge Statements', () => { |
| 100 | it('should parse a merge with a branch name', () => { |
| 101 | const result = parse(`gitGraph\n merge master\n`); |
| 102 | const merge = result.value.statements[0] as Merge; |
| 103 | expect(merge.branch).toBe('master'); |
| 104 | }); |
| 105 | |
| 106 | it('should handle merges with additional properties', () => { |
| 107 | const result = parse(`gitGraph\n merge feature id:"m1" tag:"release" type:HIGHLIGHT\n`); |
| 108 | const merge = result.value.statements[0] as Merge; |
| 109 | expect(merge.branch).toBe('feature'); |
| 110 | expect(merge.id).toBe('m1'); |
| 111 | expect(merge.tags).toEqual(['release']); |
| 112 | expect(merge.type).toBe('HIGHLIGHT'); |
| 113 | }); |
| 114 | |
| 115 | it('should parse merge without any properties', () => { |
| 116 | const result = parse(`gitGraph\n merge feature\n`); |
| 117 | const merge = result.value.statements[0] as Merge; |
| 118 | expect(merge.branch).toBe('feature'); |
| 119 | }); |
| 120 | |
| 121 | it('should ignore malformed properties in merge statements', () => { |
| 122 | const result = parse(`gitGraph\n merge feature random:"ignored"\n`); |
| 123 | const merge = result.value.statements[0] as Merge; |
| 124 | expect(merge.branch).toBe('feature'); |
| 125 | expect(merge.hasOwnProperty('random')).toBe(false); |
| 126 | }); |
| 127 | }); |
| 128 | |
| 129 | describe('Parsing Checkout Statements', () => { |
| 130 | it('should parse a checkout to a named branch', () => { |
| 131 | const result = parse( |
| 132 | `gitGraph\n commit id:"1"\n branch develop\n branch fun\n checkout develop\n` |
| 133 | ); |
| 134 | const checkout = result.value.statements[3] as Checkout; |
| 135 | expect(checkout.branch).toBe('develop'); |
| 136 | }); |
| 137 | |
| 138 | it('should parse checkout to branches with complex names', () => { |
| 139 | const result = parse(`gitGraph\n checkout hotfix-123\n`); |
| 140 | const checkout = result.value.statements[0] as Checkout; |
| 141 | expect(checkout.branch).toBe('hotfix-123'); |
| 142 | }); |
| 143 | |
| 144 | it('should parse checkouts with hyphens and numbers', () => { |
| 145 | const result = parse(`gitGraph\n checkout release-2021\n`); |
| 146 | const checkout = result.value.statements[0] as Checkout; |
| 147 | expect(checkout.branch).toBe('release-2021'); |
| 148 | }); |
| 149 | }); |
| 150 | |
| 151 | describe('Parsing CherryPicking Statements', () => { |
| 152 | it('should parse cherry-picking with a commit id', () => { |
| 153 | const result = parse(`gitGraph\n commit id:"123" commit id:"321" cherry-pick id:"123"\n`); |
| 154 | const cherryPick = result.value.statements[2] as CherryPicking; |
| 155 | expect(cherryPick.id).toBe('123'); |
| 156 | }); |
| 157 | |
| 158 | it('should parse cherry-picking with multiple properties', () => { |
| 159 | const result = parse(`gitGraph\n cherry-pick id:"123" tag:"urgent" parent:"100"\n`); |
| 160 | const cherryPick = result.value.statements[0] as CherryPicking; |
| 161 | expect(cherryPick.id).toBe('123'); |
| 162 | expect(cherryPick.tags).toEqual(['urgent']); |
| 163 | expect(cherryPick.parent).toBe('100'); |
| 164 | }); |
| 165 | |
| 166 | describe('Parsing with Accessibility Titles and Descriptions', () => { |
| 167 | it('should parse accessibility titles', () => { |
| 168 | const result = parse(`gitGraph\n accTitle: Accessible Graph\n commit\n`); |
| 169 | expect(result.value.accTitle).toBe('Accessible Graph'); |
| 170 | }); |
| 171 | |
| 172 | it('should parse multiline accessibility descriptions', () => { |
| 173 | const result = parse( |
| 174 | `gitGraph\n accDescr {\n Detailed description\n across multiple lines\n }\n commit\n` |
| 175 | ); |
| 176 | expect(result.value.accDescr).toBe('Detailed description\nacross multiple lines'); |
| 177 | }); |
| 178 | }); |
| 179 | |
| 180 | describe('Integration Tests', () => { |
| 181 | it('should correctly parse a complex graph with various elements', () => { |
| 182 | const result = parse(` |
| 183 | gitGraph TB: |
| 184 | accTitle: Complex Example |
| 185 | commit id:"init" type:NORMAL |
| 186 | branch feature |
| 187 | commit id:"feat1" msg:"Add feature" |
| 188 | checkout main |
| 189 | merge feature tag:"v1.0" |
| 190 | cherry-pick id:"feat1" tag:"critical fix" |
| 191 | `); |
| 192 | expect(result.value.accTitle).toBe('Complex Example'); |
| 193 | expect(result.value.statements[0].$type).toBe('Commit'); |
| 194 | expect(result.value.statements[1].$type).toBe('Branch'); |
| 195 | expect(result.value.statements[2].$type).toBe('Commit'); |
| 196 | expect(result.value.statements[3].$type).toBe('Checkout'); |
| 197 | expect(result.value.statements[4].$type).toBe('Merge'); |
| 198 | expect(result.value.statements[5].$type).toBe('CherryPicking'); |
| 199 | }); |
| 200 | }); |
| 201 | |
| 202 | describe('Error Handling for Invalid Syntax', () => { |
| 203 | it('should report errors for unknown properties in commit', () => { |
| 204 | const result = parse(`gitGraph\n commit unknown:"oops"\n`); |
| 205 | expect(result.parserErrors).not.toHaveLength(0); |
| 206 | }); |
| 207 | |
| 208 | it('should report errors for invalid branch order', () => { |
| 209 | const result = parse(`gitGraph\n branch feature order:xyz\n`); |
| 210 | expect(result.parserErrors).not.toHaveLength(0); |
| 211 | }); |
| 212 | }); |
| 213 | }); |
| 214 | |