import { describe, expect, it } from 'vitest'; import { parseUsecaseWithAntlr, UsecaseParseError } from '../src/language/usecase/index.js'; import { ARROW_TYPE } from '../src/language/usecase/types.js'; import type { UsecaseParseResult } from '../src/language/usecase/types.js'; describe('usecase ANTLR parser', () => { const parse = (input: string): UsecaseParseResult => { return parseUsecaseWithAntlr(input); }; it('should parse basic usecase diagram with actors', () => { const input = `usecase actor Developer1 actor Developer2 actor Developer3`; const result = parse(input); expect(result.actors).toHaveLength(3); expect(result.actors[0]).toEqual({ id: 'Developer1', name: 'Developer1', }); expect(result.actors[1]).toEqual({ id: 'Developer2', name: 'Developer2', }); expect(result.actors[2]).toEqual({ id: 'Developer3', name: 'Developer3', }); expect(result.useCases).toHaveLength(0); expect(result.relationships).toHaveLength(0); }); it('should parse actors with quoted names', () => { const input = `usecase actor "User Admin" actor 'System User'`; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.actors[0]).toEqual({ id: 'User Admin', name: 'User Admin', }); expect(result.actors[1]).toEqual({ id: 'System User', name: 'System User', }); }); it('should create use cases implicitly from relationships', () => { const input = `usecase actor User User --> Login User --> "Manage Users" User --> 'View Reports'`; const result = parse(input); expect(result.useCases).toHaveLength(3); expect(result.useCases[0]).toEqual({ id: 'Login', name: 'Login', }); expect(result.useCases[1]).toEqual({ id: 'Manage Users', name: 'Manage Users', }); expect(result.useCases[2]).toEqual({ id: 'View Reports', name: 'View Reports', }); expect(result.actors).toHaveLength(1); expect(result.relationships).toHaveLength(3); }); it('should parse relationships between actors and implicit use cases', () => { const input = `usecase actor User actor Admin User --> Login Admin --> "Manage System"`; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(2); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'User', to: 'Login', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, }); expect(result.relationships[1]).toEqual({ id: 'rel_1', from: 'Admin', to: 'Manage System', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, }); }); it('should parse actor to actor relationships', () => { const input = `usecase actor User actor Admin User --> Admin`; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.relationships).toHaveLength(1); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'User', to: 'Admin', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, }); }); it('should handle empty usecase diagram', () => { const input = `usecase`; const result = parse(input); expect(result.actors).toHaveLength(0); expect(result.useCases).toHaveLength(0); expect(result.relationships).toHaveLength(0); }); it('should handle usecase diagram with newlines and whitespace', () => { const input = `usecase actor Developer1 actor Developer2 Developer1 --> Login `; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(1); expect(result.relationships).toHaveLength(1); }); it('should handle complex usecase diagram with implicit use cases', () => { const input = `usecase actor "System Admin" actor User actor Guest User --> "Login System" "System Admin" --> "User Management" Guest --> "View Content" User --> "View Content"`; const result = parse(input); expect(result.actors).toHaveLength(3); expect(result.useCases).toHaveLength(3); expect(result.relationships).toHaveLength(4); // Verify specific relationships const loginRel = result.relationships.find((r) => r.from === 'User' && r.to === 'Login System'); expect(loginRel).toBeDefined(); expect(loginRel?.type).toBe('association'); }); }); describe('Enhanced ANTLR usecase parser features', () => { const parse = (input: string): UsecaseParseResult => { return parseUsecaseWithAntlr(input); }; test('should handle different arrow types with implicit use cases', () => { const input = `usecase actor User actor Admin User --> Login Admin <-- Manage User -- Login `; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(3); // Check relationships with different arrow types expect(result.relationships).toEqual( expect.arrayContaining([ expect.objectContaining({ from: 'User', to: 'Login', type: 'association' }), expect.objectContaining({ from: 'Admin', to: 'Manage', type: 'association' }), expect.objectContaining({ from: 'User', to: 'Login', type: 'association' }), ]) ); }); test('should handle mixed entity types in relationships with implicit use cases', () => { const input = `usecase actor Manager actor Employee Manager --> Employee Employee --> "Submit Report" Manager --> "Submit Report" `; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(1); expect(result.relationships).toHaveLength(3); // Check mixed relationships (actor-to-actor and actor-to-usecase) expect(result.relationships).toEqual( expect.arrayContaining([ expect.objectContaining({ from: 'Manager', to: 'Employee', type: 'association' }), expect.objectContaining({ from: 'Employee', to: 'Submit Report', type: 'association' }), expect.objectContaining({ from: 'Manager', to: 'Submit Report', type: 'association' }), ]) ); }); test('should handle comprehensive usecase diagram with implicit use cases', () => { const input = `usecase actor Customer actor "Bank Employee" actor "System Admin" Customer --> "Withdraw Money" Customer --> "Check Balance" Customer --> "Transfer Funds" "Bank Employee" --> "Check Balance" "Bank Employee" --> "Transfer Funds" "Bank Employee" --> "Manage Accounts" "System Admin" --> "Manage Accounts" `; const result = parse(input); expect(result.actors).toHaveLength(3); expect(result.useCases).toHaveLength(4); expect(result.relationships).toHaveLength(7); // Check actors expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Customer', name: 'Customer' }, { id: 'Bank Employee', name: 'Bank Employee' }, { id: 'System Admin', name: 'System Admin' }, ]) ); // Check use cases expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Withdraw Money', name: 'Withdraw Money' }, { id: 'Check Balance', name: 'Check Balance' }, { id: 'Transfer Funds', name: 'Transfer Funds' }, { id: 'Manage Accounts', name: 'Manage Accounts' }, ]) ); // Check relationships expect(result.relationships).toEqual( expect.arrayContaining([ expect.objectContaining({ from: 'Customer', to: 'Withdraw Money', type: 'association' }), expect.objectContaining({ from: 'Customer', to: 'Check Balance', type: 'association' }), expect.objectContaining({ from: 'Customer', to: 'Transfer Funds', type: 'association' }), expect.objectContaining({ from: 'Bank Employee', to: 'Check Balance', type: 'association', }), expect.objectContaining({ from: 'Bank Employee', to: 'Transfer Funds', type: 'association', }), expect.objectContaining({ from: 'Bank Employee', to: 'Manage Accounts', type: 'association', }), expect.objectContaining({ from: 'System Admin', to: 'Manage Accounts', type: 'association', }), ]) ); }); }); describe('Comma-separated actor syntax', () => { it('should parse comma-separated actors', () => { const input = `usecase actor Developer1, Developer2, Developer3`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.actors).toEqual([ { id: 'Developer1', name: 'Developer1' }, { id: 'Developer2', name: 'Developer2' }, { id: 'Developer3', name: 'Developer3' }, ]); }); it('should parse quoted names with commas', () => { const input = `usecase actor "User Admin", "System Admin", "Database Admin"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.actors).toEqual([ { id: 'User Admin', name: 'User Admin' }, { id: 'System Admin', name: 'System Admin' }, { id: 'Database Admin', name: 'Database Admin' }, ]); }); it('should handle mixed single and comma-separated actors', () => { const input = `usecase actor SingleActor actor Group1, Group2, Group3 actor AnotherSingle`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(5); expect(result.actors).toEqual([ { id: 'SingleActor', name: 'SingleActor' }, { id: 'Group1', name: 'Group1' }, { id: 'Group2', name: 'Group2' }, { id: 'Group3', name: 'Group3' }, { id: 'AnotherSingle', name: 'AnotherSingle' }, ]); }); it('should handle comma-separated actors with implicit use cases from relationships', () => { const input = `usecase actor User, Admin, Guest User --> Login Admin --> Login Guest --> Login User --> Logout Admin --> Logout`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(5); expect(result.actors).toEqual([ { id: 'User', name: 'User' }, { id: 'Admin', name: 'Admin' }, { id: 'Guest', name: 'Guest' }, ]); expect(result.useCases).toEqual([ { id: 'Login', name: 'Login' }, { id: 'Logout', name: 'Logout' }, ]); }); it('should maintain backward compatibility with original syntax', () => { const input = `usecase actor Developer1 actor Developer2 actor Developer3`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.actors).toEqual([ { id: 'Developer1', name: 'Developer1' }, { id: 'Developer2', name: 'Developer2' }, { id: 'Developer3', name: 'Developer3' }, ]); }); it('should handle single actor in comma syntax', () => { const input = `usecase actor SingleActor`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.actors).toEqual([{ id: 'SingleActor', name: 'SingleActor' }]); }); it('should handle complex comma-separated scenario with implicit use cases', () => { const input = `usecase actor "Customer Service", "Technical Support", "Sales Team" actor SystemAdmin "Customer Service" --> "Handle Tickets" "Technical Support" --> "Handle Tickets" "Sales Team" --> "Process Orders" SystemAdmin --> "Handle Tickets" SystemAdmin --> "Process Orders" `; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(4); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(5); expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Customer Service', name: 'Customer Service' }, { id: 'Technical Support', name: 'Technical Support' }, { id: 'Sales Team', name: 'Sales Team' }, { id: 'SystemAdmin', name: 'SystemAdmin' }, ]) ); }); }); describe('Actor metadata syntax', () => { it('should parse actor with metadata', () => { const input = `usecase actor Developer1@{ "icon" : "icon_name", "type" : "hollow", "name": "Sample Name" }`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.actors[0]).toEqual({ id: 'Developer1', name: 'Developer1', metadata: { icon: 'icon_name', type: 'hollow', name: 'Sample Name', }, }); }); it('should parse simple metadata', () => { const input = `usecase actor User@{ "role" : "admin" }`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.actors[0]).toEqual({ id: 'User', name: 'User', metadata: { role: 'admin', }, }); }); it('should parse comma-separated actors with metadata', () => { const input = `usecase actor Admin@{ "role" : "admin" }, User@{ "role" : "user" }`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.actors).toEqual([ { id: 'Admin', name: 'Admin', metadata: { role: 'admin' }, }, { id: 'User', name: 'User', metadata: { role: 'user' }, }, ]); }); it('should handle mixed actors with and without metadata', () => { const input = `usecase actor SimpleActor, MetaActor@{ "type" : "special" }`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.actors).toEqual([ { id: 'SimpleActor', name: 'SimpleActor', metadata: undefined, }, { id: 'MetaActor', name: 'MetaActor', metadata: { type: 'special' }, }, ]); }); it('should handle quoted actor names with metadata', () => { const input = `usecase actor "System Admin"@{ "level" : "high", "department" : "IT" }`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.actors[0]).toEqual({ id: 'System Admin', name: 'System Admin', metadata: { level: 'high', department: 'IT', }, }); }); it('should handle metadata with relationships and implicit use cases', () => { const input = `usecase actor Admin@{ "role" : "admin" }, User@{ "role" : "user" } Admin --> Login User --> Login`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(1); expect(result.relationships).toHaveLength(2); expect(result.actors).toEqual([ { id: 'Admin', name: 'Admin', metadata: { role: 'admin' }, }, { id: 'User', name: 'User', metadata: { role: 'user' }, }, ]); expect(result.useCases).toEqual([{ id: 'Login', name: 'Login' }]); }); it('should maintain backward compatibility without metadata', () => { const input = `usecase actor Developer1, Developer2, Developer3`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.actors).toEqual([ { id: 'Developer1', name: 'Developer1', metadata: undefined }, { id: 'Developer2', name: 'Developer2', metadata: undefined }, { id: 'Developer3', name: 'Developer3', metadata: undefined }, ]); }); it('should handle complex metadata scenario with implicit use cases', () => { const input = `usecase actor "Customer Service"@{ "icon" : "user", "type" : "primary" }, "Technical Support"@{ "icon" : "wrench", "type" : "secondary" } actor SystemAdmin@{ "role" : "admin", "level" : "high" } "Customer Service" --> "Handle Tickets" "Technical Support" --> "Handle Tickets" SystemAdmin --> "Handle Tickets" SystemAdmin --> "Process Orders"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(4); expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Customer Service', name: 'Customer Service', metadata: { icon: 'user', type: 'primary' }, }, { id: 'Technical Support', name: 'Technical Support', metadata: { icon: 'wrench', type: 'secondary' }, }, { id: 'SystemAdmin', name: 'SystemAdmin', metadata: { role: 'admin', level: 'high' }, }, ]) ); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Handle Tickets', name: 'Handle Tickets' }, { id: 'Process Orders', name: 'Process Orders' }, ]) ); }); }); describe('Implicit use case creation', () => { it('should create use cases implicitly from relationships', () => { const input = `usecase actor developer1 actor developer2 developer1 --> Login developer2 --> "Handle Tickets" developer1 --> "System Maintenance"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(3); expect(result.relationships).toHaveLength(3); expect(result.actors).toEqual([ { id: 'developer1', name: 'developer1', metadata: undefined }, { id: 'developer2', name: 'developer2', metadata: undefined }, ]); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Login', name: 'Login' }, { id: 'Handle Tickets', name: 'Handle Tickets' }, { id: 'System Maintenance', name: 'System Maintenance' }, ]) ); }); it('should not create use cases for actor-to-actor relationships', () => { const input = `usecase actor Manager, Developer Manager --> Developer`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(0); expect(result.relationships).toHaveLength(1); expect(result.actors).toEqual([ { id: 'Manager', name: 'Manager', metadata: undefined }, { id: 'Developer', name: 'Developer', metadata: undefined }, ]); }); it('should handle mixed actor-to-usecase and actor-to-actor relationships', () => { const input = `usecase actor Manager, Developer, Tester Manager --> Developer Developer --> "Code Review" Tester --> "Testing" Manager --> "Project Planning"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.useCases).toHaveLength(3); expect(result.relationships).toHaveLength(4); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Code Review', name: 'Code Review' }, { id: 'Testing', name: 'Testing' }, { id: 'Project Planning', name: 'Project Planning' }, ]) ); }); }); describe('System Boundary functionality', () => { it('should parse basic system boundary syntax', () => { const input = `usecase actor Developer actor Tester systemBoundary Tasks coding testing end Developer --> coding Tester --> testing`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(2); expect(result.actors).toEqual([ { id: 'Developer', name: 'Developer', metadata: undefined }, { id: 'Tester', name: 'Tester', metadata: undefined }, ]); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'coding', name: 'coding', systemBoundary: 'Tasks' }, { id: 'testing', name: 'testing', systemBoundary: 'Tasks' }, ]) ); expect(result.systemBoundaries).toEqual([ { id: 'Tasks', name: 'Tasks', useCases: ['coding', 'testing'], type: 'rect' }, ]); }); it('should handle system boundary with quoted names', () => { const input = `usecase actor User systemBoundary "User Management" "Create User" "Delete User" "Update Profile" end User --> "Create User" User --> "Update Profile"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.useCases).toHaveLength(3); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(2); expect(result.systemBoundaries).toEqual([ { id: 'User Management', name: 'User Management', useCases: ['Create User', 'Delete User', 'Update Profile'], type: 'rect', }, ]); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Create User', name: 'Create User', systemBoundary: 'User Management' }, { id: 'Delete User', name: 'Delete User', systemBoundary: 'User Management' }, { id: 'Update Profile', name: 'Update Profile', systemBoundary: 'User Management' }, ]) ); }); it('should handle multiple system boundaries', () => { const input = `usecase actor Admin, User systemBoundary Authentication Login Logout end systemBoundary "User Management" "Manage Users" "View Reports" end Admin --> Login User --> Login Admin --> "Manage Users" User --> "View Reports"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(4); expect(result.systemBoundaries).toHaveLength(2); expect(result.relationships).toHaveLength(4); expect(result.systemBoundaries).toEqual( expect.arrayContaining([ { id: 'Authentication', name: 'Authentication', useCases: ['Login', 'Logout'], type: 'rect', }, { id: 'User Management', name: 'User Management', useCases: ['Manage Users', 'View Reports'], type: 'rect', }, ]) ); }); it('should handle system boundary with actors having metadata', () => { const input = `usecase actor Admin@{ "icon" : "admin" }, User@{ "icon" : "user" } systemBoundary "Core Features" Login Dashboard end Admin --> Login User --> Dashboard`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(2); expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Admin', name: 'Admin', metadata: { icon: 'admin' } }, { id: 'User', name: 'User', metadata: { icon: 'user' } }, ]) ); expect(result.systemBoundaries).toEqual([ { id: 'Core Features', name: 'Core Features', useCases: ['Login', 'Dashboard'], type: 'rect', }, ]); }); it('should handle mixed use cases (some in boundaries, some not)', () => { const input = `usecase actor Developer, Manager systemBoundary "Development Tasks" coding testing end Developer --> coding Developer --> testing Manager --> "Project Planning" Developer --> "Code Review"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(4); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(4); // Use cases in boundary should have systemBoundary property const codingUseCase = result.useCases.find((uc) => uc.id === 'coding'); const testingUseCase = result.useCases.find((uc) => uc.id === 'testing'); expect(codingUseCase?.systemBoundary).toBe('Development Tasks'); expect(testingUseCase?.systemBoundary).toBe('Development Tasks'); // Use cases not in boundary should not have systemBoundary property const planningUseCase = result.useCases.find((uc) => uc.id === 'Project Planning'); const reviewUseCase = result.useCases.find((uc) => uc.id === 'Code Review'); expect(planningUseCase?.systemBoundary).toBeUndefined(); expect(reviewUseCase?.systemBoundary).toBeUndefined(); }); it('should handle empty system boundary', () => { const input = `usecase actor Developer systemBoundary EmptyBoundary end Developer --> "Some Task"`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.useCases).toHaveLength(1); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(1); expect(result.systemBoundaries).toEqual([ { id: 'EmptyBoundary', name: 'EmptyBoundary', useCases: [], type: 'rect' }, ]); // Use case created from relationship should not be in boundary const someTaskUseCase = result.useCases.find((uc) => uc.id === 'Some Task'); expect(someTaskUseCase?.systemBoundary).toBeUndefined(); }); }); describe('System Boundary Type Configuration', () => { it('should parse system boundary with package type', () => { const input = `usecase actor Developer1 systemBoundary Tasks coding end Tasks@{ type: package } Developer1 --> coding`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.useCases).toHaveLength(1); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(1); expect(result.systemBoundaries[0]).toEqual({ id: 'Tasks', name: 'Tasks', useCases: ['coding'], type: 'package', }); }); it('should parse system boundary with rect type', () => { const input = `usecase actor Developer1 systemBoundary Tasks coding end Tasks@{ type: rect } Developer1 --> coding`; const result = parseUsecaseWithAntlr(input); expect(result.systemBoundaries[0]).toEqual({ id: 'Tasks', name: 'Tasks', useCases: ['coding'], type: 'rect', }); }); it('should default to rect type when no type specified', () => { const input = `usecase actor Developer1 systemBoundary Tasks coding end Developer1 --> coding`; const result = parseUsecaseWithAntlr(input); expect(result.systemBoundaries[0]).toEqual({ id: 'Tasks', name: 'Tasks', useCases: ['coding'], type: 'rect', // Should default to rect }); }); it('should handle multiple boundaries with different types', () => { const input = `usecase actor Admin, User systemBoundary Authentication Login Logout end systemBoundary "User Management" "Manage Users" "View Reports" end Authentication@{ type: package } "User Management"@{ type: rect } Admin --> Login User --> "Manage Users"`; const result = parseUsecaseWithAntlr(input); expect(result.systemBoundaries).toHaveLength(2); const authBoundary = result.systemBoundaries.find((b) => b.id === 'Authentication'); const userManagementBoundary = result.systemBoundaries.find((b) => b.id === 'User Management'); expect(authBoundary).toEqual({ id: 'Authentication', name: 'Authentication', useCases: ['Login', 'Logout'], type: 'package', }); expect(userManagementBoundary).toEqual({ id: 'User Management', name: 'User Management', useCases: ['Manage Users', 'View Reports'], type: 'rect', }); }); it('should handle quoted boundary names with type configuration', () => { const input = `usecase actor User systemBoundary "Core Features" Login Dashboard end "Core Features"@{ type: package } User --> Login`; const result = parseUsecaseWithAntlr(input); expect(result.systemBoundaries[0]).toEqual({ id: 'Core Features', name: 'Core Features', useCases: ['Login', 'Dashboard'], type: 'package', }); }); it('should work with actor metadata and system boundary types', () => { const input = `usecase actor Admin@{ "icon" : "admin" }, User@{ "icon" : "user" } systemBoundary "Core System" Login Dashboard end "Core System"@{ type: package } Admin --> Login User --> Dashboard`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.systemBoundaries).toHaveLength(1); expect(result.relationships).toHaveLength(2); expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Admin', name: 'Admin', metadata: { icon: 'admin' } }, { id: 'User', name: 'User', metadata: { icon: 'user' } }, ]) ); expect(result.systemBoundaries[0]).toEqual({ id: 'Core System', name: 'Core System', useCases: ['Login', 'Dashboard'], type: 'package', }); }); it('should maintain backward compatibility with existing system boundaries', () => { const input = `usecase actor Developer, Tester systemBoundary Tasks coding testing end Developer --> coding Tester --> testing`; const result = parseUsecaseWithAntlr(input); expect(result.systemBoundaries[0]).toEqual({ id: 'Tasks', name: 'Tasks', useCases: ['coding', 'testing'], type: 'rect', // Should default to rect for backward compatibility }); }); }); describe('Node ID with Label Syntax', () => { it('should parse basic node ID with label syntax', () => { const input = `usecase actor Developer1 Developer1 --> a(Go through code)`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.useCases).toHaveLength(1); expect(result.relationships).toHaveLength(1); expect(result.actors[0]).toEqual({ id: 'Developer1', name: 'Developer1', metadata: undefined, }); expect(result.useCases[0]).toEqual({ id: 'Go through code', name: 'Go through code', nodeId: 'a', }); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'Developer1', to: 'Go through code', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, }); }); it('should parse your exact requested syntax', () => { const input = `usecase actor Developer1 actor Developer2 Developer1 --> a(Go through code) Developer2 --> b(Go through implementation) actor tester --> c(Go through testing)`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(3); expect(result.useCases).toHaveLength(3); expect(result.relationships).toHaveLength(3); // Check actors expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Developer1', name: 'Developer1', metadata: undefined }, { id: 'Developer2', name: 'Developer2', metadata: undefined }, { id: 'tester', name: 'tester', metadata: undefined }, ]) ); // Check use cases with node IDs expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Go through code', name: 'Go through code', nodeId: 'a' }, { id: 'Go through implementation', name: 'Go through implementation', nodeId: 'b' }, { id: 'Go through testing', name: 'Go through testing', nodeId: 'c' }, ]) ); // Check relationships expect(result.relationships).toHaveLength(3); expect(result.relationships).toEqual( expect.arrayContaining([ expect.objectContaining({ from: 'Developer1', to: 'Go through code' }), expect.objectContaining({ from: 'Developer2', to: 'Go through implementation' }), expect.objectContaining({ from: 'tester', to: 'Go through testing' }), ]) ); }); it('should handle quoted labels in node ID syntax', () => { const input = `usecase actor Admin Admin --> x("Create User") Admin --> y("Delete User")`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(2); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Create User', name: 'Create User', nodeId: 'x' }, { id: 'Delete User', name: 'Delete User', nodeId: 'y' }, ]) ); }); it('should handle multi-word labels in node ID syntax', () => { const input = `usecase actor Developer Developer --> task1(Review code changes) Developer --> task2(Run unit tests)`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(1); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(2); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Review code changes', name: 'Review code changes', nodeId: 'task1' }, { id: 'Run unit tests', name: 'Run unit tests', nodeId: 'task2' }, ]) ); }); it('should handle inline actor declarations with node ID syntax', () => { const input = `usecase actor Developer1 actor tester --> c(Go through testing) Developer1 --> a(Go through code)`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(2); // Both actors should be created (one explicit, one inline) expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Developer1', name: 'Developer1', metadata: undefined }, { id: 'tester', name: 'tester', metadata: undefined }, ]) ); // Use cases should have node IDs expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Go through testing', name: 'Go through testing', nodeId: 'c' }, { id: 'Go through code', name: 'Go through code', nodeId: 'a' }, ]) ); }); it('should maintain backward compatibility with regular syntax', () => { const input = `usecase actor Developer1 actor Developer2 Developer1 --> "Regular Use Case" Developer2 --> a(Node ID Use Case)`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(2); // Regular use case without node ID const regularUseCase = result.useCases.find((uc) => uc.id === 'Regular Use Case'); expect(regularUseCase).toEqual({ id: 'Regular Use Case', name: 'Regular Use Case', nodeId: undefined, }); // Use case with node ID const nodeIdUseCase = result.useCases.find((uc) => uc.id === 'Node ID Use Case'); expect(nodeIdUseCase).toEqual({ id: 'Node ID Use Case', name: 'Node ID Use Case', nodeId: 'a', }); }); it('should work with actor metadata and node ID syntax', () => { const input = `usecase actor Admin@{ "icon" : "admin" } actor User@{ "icon" : "user" } Admin --> x(Create User) User --> y(View Profile)`; const result = parseUsecaseWithAntlr(input); expect(result.actors).toHaveLength(2); expect(result.useCases).toHaveLength(2); expect(result.relationships).toHaveLength(2); expect(result.actors).toEqual( expect.arrayContaining([ { id: 'Admin', name: 'Admin', metadata: { icon: 'admin' } }, { id: 'User', name: 'User', metadata: { icon: 'user' } }, ]) ); expect(result.useCases).toEqual( expect.arrayContaining([ { id: 'Create User', name: 'Create User', nodeId: 'x' }, { id: 'View Profile', name: 'View Profile', nodeId: 'y' }, ]) ); }); }); describe('Edge Label Syntax', () => { const parse = (input: string): UsecaseParseResult => { return parseUsecaseWithAntlr(input); }; it('should parse basic edge label syntax', () => { const input = `usecase actor Developer1 Developer1 --important--> a(coding)`; const result = parse(input); expect(result.relationships).toHaveLength(1); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'Developer1', to: 'coding', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'important', }); }); it('should parse your exact requested syntax', () => { const input = `usecase actor Developer1 Developer1 --important--> a(coding)`; const result = parse(input); expect(result.actors).toHaveLength(1); expect(result.actors[0]).toEqual({ id: 'Developer1', name: 'Developer1', }); expect(result.useCases).toHaveLength(1); expect(result.useCases[0]).toEqual({ id: 'coding', name: 'coding', nodeId: 'a', }); expect(result.relationships).toHaveLength(1); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'Developer1', to: 'coding', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'important', }); }); it('should parse edge labels with string values', () => { const input = `usecase actor User User --"very important"--> Login`; const result = parse(input); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'User', to: 'Login', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'very important', }); }); it('should parse multiple edge labels', () => { const input = `usecase actor Developer actor Tester Developer --primary--> "Code Review" Tester --secondary--> "Bug Testing"`; const result = parse(input); expect(result.relationships).toHaveLength(2); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'Developer', to: 'Code Review', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'primary', }); expect(result.relationships[1]).toEqual({ id: 'rel_1', from: 'Tester', to: 'Bug Testing', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'secondary', }); }); it('should parse edge labels with different arrow types', () => { const input = `usecase actor User actor Admin User --important--> Login Admin <--critical-- Manage User --optional-- Dashboard`; const result = parse(input); expect(result.relationships).toHaveLength(3); expect(result.relationships[0].label).toBe('important'); expect(result.relationships[1].label).toBe('critical'); expect(result.relationships[2].label).toBe('optional'); }); it('should maintain backward compatibility with unlabeled arrows', () => { const input = `usecase actor User User --> Login User --important--> Manage`; const result = parse(input); expect(result.relationships).toHaveLength(2); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'User', to: 'Login', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, }); expect(result.relationships[1]).toEqual({ id: 'rel_1', from: 'User', to: 'Manage', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'important', }); }); it('should work with node ID syntax and edge labels', () => { const input = `usecase actor Developer Developer --critical--> a(Code Review) Developer --optional--> b(Documentation)`; const result = parse(input); expect(result.relationships).toHaveLength(2); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'Developer', to: 'Code Review', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'critical', }); expect(result.relationships[1]).toEqual({ id: 'rel_1', from: 'Developer', to: 'Documentation', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'optional', }); expect(result.useCases[0].nodeId).toBe('a'); expect(result.useCases[1].nodeId).toBe('b'); }); it('should work with inline actor declarations and edge labels', () => { const input = `usecase actor Developer --important--> a(coding) actor Tester --critical--> b(testing)`; const result = parse(input); expect(result.actors).toHaveLength(2); expect(result.relationships).toHaveLength(2); expect(result.relationships[0]).toEqual({ id: 'rel_0', from: 'Developer', to: 'coding', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'important', }); expect(result.relationships[1]).toEqual({ id: 'rel_1', from: 'Tester', to: 'testing', type: 'association', arrowType: ARROW_TYPE.SOLID_ARROW, label: 'critical', }); }); }); describe('Error Handling', () => { describe('Syntax Error Handling', () => { it('should throw UsecaseParseError for invalid syntax', () => { const invalidSyntax = `usecase invalid syntax here actor User `; expect(() => parseUsecaseWithAntlr(invalidSyntax)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(invalidSyntax)).toThrow(/Syntax error in usecase diagram/); }); it('should throw UsecaseParseError for incomplete relationships', () => { const incompleteSyntax = `usecase actor User User --> `; expect(() => parseUsecaseWithAntlr(incompleteSyntax)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(incompleteSyntax)).toThrow(/mismatched input/); }); it('should throw UsecaseParseError for malformed actor declarations', () => { const malformedSyntax = `usecase actor actor User `; expect(() => parseUsecaseWithAntlr(malformedSyntax)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(malformedSyntax)).toThrow(/no viable alternative/); }); it('should throw UsecaseParseError for invalid arrow syntax', () => { const invalidArrowSyntax = `usecase actor User User -invalid-> Login `; expect(() => parseUsecaseWithAntlr(invalidArrowSyntax)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(invalidArrowSyntax)).toThrow(/token recognition error/); }); it('should throw UsecaseParseError for empty input', () => { const emptyInput = ''; expect(() => parseUsecaseWithAntlr(emptyInput)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(emptyInput)).toThrow( /mismatched input '' expecting {'usecase', NEWLINE}/ ); }); it('should throw UsecaseParseError for only whitespace input', () => { const whitespaceInput = ' \n \t \n '; expect(() => parseUsecaseWithAntlr(whitespaceInput)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(whitespaceInput)).toThrow( /extraneous input '' expecting {'usecase', NEWLINE}/ ); }); it('should throw UsecaseParseError for missing usecase keyword', () => { const missingKeyword = ` actor User User --> Login `; expect(() => parseUsecaseWithAntlr(missingKeyword)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(missingKeyword)).toThrow( /extraneous input 'actor' expecting {'usecase', NEWLINE}/ ); }); }); describe('Validation Error Handling', () => { it('should handle duplicate actor IDs by keeping both', () => { const duplicateActors = `usecase actor User actor User User --> Login `; const result = parseUsecaseWithAntlr(duplicateActors); expect(result).toBeDefined(); expect(result.actors).toHaveLength(2); expect(result.actors[0].id).toBe('User'); expect(result.actors[1].id).toBe('User'); }); it('should handle self-referencing relationships', () => { const selfReference = `usecase actor User User --> User `; const result = parseUsecaseWithAntlr(selfReference); expect(result).toBeDefined(); expect(result.actors).toHaveLength(1); expect(result.relationships).toHaveLength(1); expect(result.relationships[0].from).toBe('User'); expect(result.relationships[0].to).toBe('User'); }); it('should handle very long entity names', () => { const longName = 'A'.repeat(1000); const longNameSyntax = `usecase actor "${longName}" "${longName}" --> Login `; const result = parseUsecaseWithAntlr(longNameSyntax); expect(result).toBeDefined(); expect(result.actors).toHaveLength(1); expect(result.actors[0].id).toBe(longName); }); it('should handle special characters in names', () => { const specialCharsSyntax = `usecase actor "User@Domain.com" "User@Domain.com" --> "Login/Logout" `; const result = parseUsecaseWithAntlr(specialCharsSyntax); expect(result).toBeDefined(); expect(result.actors).toHaveLength(1); expect(result.actors[0].id).toBe('User@Domain.com'); expect(result.useCases).toHaveLength(1); expect(result.useCases[0].id).toBe('Login/Logout'); }); }); describe('Edge Cases', () => { it('should throw UsecaseParseError for mixed valid and invalid syntax', () => { const mixedSyntax = `usecase actor User invalid line here User --> Login another invalid line actor Admin `; expect(() => parseUsecaseWithAntlr(mixedSyntax)).toThrow(UsecaseParseError); expect(() => parseUsecaseWithAntlr(mixedSyntax)).toThrow(/no viable alternative/); }); it('should handle Unicode characters', () => { const unicodeSyntax = `usecase actor "用户" "用户" --> "登录" `; const result = parseUsecaseWithAntlr(unicodeSyntax); expect(result).toBeDefined(); expect(result.actors).toHaveLength(1); expect(result.actors[0].id).toBe('用户'); expect(result.useCases).toHaveLength(1); expect(result.useCases[0].id).toBe('登录'); }); it('should handle very large diagrams', () => { let largeDiagram = 'usecase\n'; for (let i = 0; i < 100; i++) { largeDiagram += ` actor User${i}\n`; largeDiagram += ` User${i} --> UseCase${i}\n`; } const result = parseUsecaseWithAntlr(largeDiagram); expect(result).toBeDefined(); expect(result.actors).toHaveLength(100); expect(result.useCases).toHaveLength(100); expect(result.relationships).toHaveLength(100); }); }); });