UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

407 lines (339 loc) 13.4 kB
import { describe, it, expect } from 'vitest'; import { getNygardTemplate, getMADRTemplate, getYStatementTemplate, renderADRTemplate, getTemplateFields, validateTemplateData } from '../templates.js'; import type { ADR, ADRTemplate } from '../types.js'; describe('ADR Templates', () => { const baseADR: ADR = { id: 'ADR-0001', title: 'Use React for Frontend Framework', status: 'accepted', date: new Date('2024-01-15'), deciders: ['John Doe', 'Jane Smith'], template: 'nygard', context: 'We need to choose a frontend framework for our new web application.', decision: 'We will use React as our frontend framework.', consequences: 'The team will need to learn React. We will have access to a large ecosystem.', createdAt: new Date('2024-01-15'), updatedAt: new Date('2024-01-15'), statusHistory: [] }; describe('getNygardTemplate', () => { it('should return Nygard template structure', () => { const template = getNygardTemplate(); expect(template.name).toBe('nygard'); expect(template.description).toContain('Michael Nygard'); expect(template.fields).toContain('title'); expect(template.fields).toContain('status'); expect(template.fields).toContain('context'); expect(template.fields).toContain('decision'); expect(template.fields).toContain('consequences'); }); it('should include required and optional fields', () => { const template = getNygardTemplate(); expect(template.requiredFields).toContain('title'); expect(template.requiredFields).toContain('context'); expect(template.requiredFields).toContain('decision'); expect(template.optionalFields).toContain('tags'); expect(template.optionalFields).toContain('relatedTo'); }); }); describe('getMADRTemplate', () => { it('should return MADR template structure', () => { const template = getMADRTemplate(); expect(template.name).toBe('madr'); expect(template.description).toContain('Markdown Architectural Decision Records'); expect(template.fields).toContain('decisionDrivers'); expect(template.fields).toContain('consideredOptions'); }); it('should include MADR-specific fields', () => { const template = getMADRTemplate(); expect(template.requiredFields).toContain('decisionDrivers'); expect(template.fields).toContain('prosAndCons'); expect(template.fields).toContain('links'); }); }); describe('getYStatementTemplate', () => { it('should return Y-statement template structure', () => { const template = getYStatementTemplate(); expect(template.name).toBe('y-statement'); expect(template.description).toContain('simple format'); expect(template.fields).toHaveLength(8); // Fewer fields than other templates }); it('should have minimal required fields', () => { const template = getYStatementTemplate(); expect(template.requiredFields).toContain('title'); expect(template.requiredFields).toContain('context'); expect(template.requiredFields).toContain('decision'); expect(template.requiredFields).toContain('consequences'); expect(template.requiredFields).not.toContain('decisionDrivers'); }); }); describe('renderADRTemplate', () => { it('should render Nygard template correctly', () => { const rendered = renderADRTemplate(baseADR); expect(rendered).toContain('# ADR-0001: Use React for Frontend Framework'); expect(rendered).toContain('## Status'); expect(rendered).toContain('Accepted'); expect(rendered).toContain('## Context'); expect(rendered).toContain(baseADR.context); expect(rendered).toContain('## Decision'); expect(rendered).toContain(baseADR.decision); expect(rendered).toContain('## Consequences'); expect(rendered).toContain(baseADR.consequences); }); it('should render MADR template with additional fields', () => { const madrADR: ADR = { ...baseADR, template: 'madr', decisionDrivers: ['Performance', 'Developer Experience', 'Community Support'], consideredOptions: [ { title: 'React', description: 'A JavaScript library for building user interfaces', pros: ['Large ecosystem', 'Good performance', 'Wide adoption'], cons: ['Learning curve', 'Requires build tooling'] }, { title: 'Vue', description: 'The Progressive JavaScript Framework', pros: ['Easy to learn', 'Good documentation', 'Flexible'], cons: ['Smaller ecosystem', 'Less job market demand'] } ] }; const rendered = renderADRTemplate(madrADR); expect(rendered).toContain('## Decision Drivers'); expect(rendered).toContain('* Performance'); expect(rendered).toContain('* Developer Experience'); expect(rendered).toContain('## Considered Options'); expect(rendered).toContain('* React - A JavaScript library'); expect(rendered).toContain('* Vue - The Progressive JavaScript Framework'); }); it('should render Y-statement template simply', () => { const yStatementADR: ADR = { ...baseADR, template: 'y-statement' }; const rendered = renderADRTemplate(yStatementADR); expect(rendered).toContain('# ADR-0001: Use React for Frontend Framework'); expect(rendered).toContain('## Status: Accepted'); expect(rendered).toContain(baseADR.context); expect(rendered).toContain(baseADR.decision); expect(rendered).toContain(baseADR.consequences); expect(rendered).not.toContain('## Decision Drivers'); // Y-statement doesn't have this }); it('should include metadata section', () => { const rendered = renderADRTemplate(baseADR); expect(rendered).toContain('**Date**: 2024-01-15'); expect(rendered).toContain('**Deciders**: John Doe, Jane Smith'); }); it('should include status history if present', () => { const adrWithHistory: ADR = { ...baseADR, statusHistory: [ { from: 'proposed', to: 'accepted', date: new Date('2024-01-20'), changedBy: 'John Doe', reason: 'Approved by architecture board' } ] }; const rendered = renderADRTemplate(adrWithHistory); expect(rendered).toContain('## Status History'); expect(rendered).toContain('proposed → accepted'); expect(rendered).toContain('2024-01-20'); expect(rendered).toContain('John Doe'); expect(rendered).toContain('Approved by architecture board'); }); it('should include tags if present', () => { const adrWithTags: ADR = { ...baseADR, tags: ['frontend', 'react', 'framework'] }; const rendered = renderADRTemplate(adrWithTags); expect(rendered).toContain('**Tags**: frontend, react, framework'); }); it('should include relationships if present', () => { const adrWithRelations: ADR = { ...baseADR, supersedes: ['ADR-0000'], relatedTo: ['ADR-0002', 'ADR-0003'] }; const rendered = renderADRTemplate(adrWithRelations); expect(rendered).toContain('## Links'); expect(rendered).toContain('* Supersedes: ADR-0000'); expect(rendered).toContain('* Related to: ADR-0002, ADR-0003'); }); it('should render options with pros and cons for MADR', () => { const madrADR: ADR = { ...baseADR, template: 'madr', consideredOptions: [ { title: 'React', description: 'Facebook\'s library', pros: ['Popular', 'Fast'], cons: ['Complex'] } ], prosAndCons: [ { option: 'React', pros: ['Great ecosystem'], cons: ['Steep learning curve'] } ] }; const rendered = renderADRTemplate(madrADR); expect(rendered).toContain('### React'); expect(rendered).toContain('Facebook\'s library'); expect(rendered).toContain('* Good, because Popular'); expect(rendered).toContain('* Bad, because Complex'); }); it('should format dates consistently', () => { const rendered = renderADRTemplate(baseADR); // Should use ISO date format expect(rendered).toMatch(/\*\*Date\*\*: \d{4}-\d{2}-\d{2}/); }); }); describe('getTemplateFields', () => { it('should return fields for Nygard template', () => { const fields = getTemplateFields('nygard'); expect(fields.required).toContain('title'); expect(fields.required).toContain('context'); expect(fields.required).toContain('decision'); expect(fields.required).toContain('consequences'); expect(fields.optional).toContain('tags'); }); it('should return fields for MADR template', () => { const fields = getTemplateFields('madr'); expect(fields.required).toContain('decisionDrivers'); expect(fields.optional).toContain('consideredOptions'); expect(fields.optional).toContain('prosAndCons'); }); it('should return fields for Y-statement template', () => { const fields = getTemplateFields('y-statement'); expect(fields.required).toContain('title'); expect(fields.required).toContain('context'); expect(fields.required).toContain('decision'); expect(fields.optional).toContain('tags'); }); it('should throw error for invalid template', () => { expect(() => getTemplateFields('invalid' as ADRTemplate)) .toThrow('Unknown template: invalid'); }); }); describe('validateTemplateData', () => { it('should validate Nygard template data', () => { const data = { title: 'Test ADR', context: 'Test context', decision: 'Test decision', consequences: 'Test consequences', deciders: ['John'] }; const result = validateTemplateData('nygard', data); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should catch missing required fields', () => { const data = { title: 'Test ADR', // Missing context decision: 'Test decision', consequences: 'Test consequences' }; const result = validateTemplateData('nygard', data); expect(result.valid).toBe(false); expect(result.errors).toContain('Missing required field: context'); }); it('should validate MADR specific fields', () => { const data = { title: 'Test ADR', context: 'Test context', decision: 'Test decision', consequences: 'Test consequences', deciders: ['John'], decisionDrivers: ['Performance', 'Cost'] }; const result = validateTemplateData('madr', data); expect(result.valid).toBe(true); }); it('should validate array fields', () => { const data = { title: 'Test ADR', context: 'Test context', decision: 'Test decision', consequences: 'Test consequences', deciders: 'John' // Should be array }; const result = validateTemplateData('nygard', data); expect(result.valid).toBe(false); expect(result.errors).toContain('Field "deciders" must be an array'); }); it('should validate consideredOptions structure', () => { const data = { title: 'Test ADR', context: 'Test context', decision: 'Test decision', consequences: 'Test consequences', deciders: ['John'], decisionDrivers: ['Performance'], consideredOptions: [ { title: 'Option 1', // Missing description pros: ['Good'], cons: ['Bad'] } ] }; const result = validateTemplateData('madr', data); expect(result.valid).toBe(false); expect(result.errors).toContain('Option must have title, description, pros, and cons'); }); it('should allow optional fields to be missing', () => { const data = { title: 'Test ADR', context: 'Test context', decision: 'Test decision', consequences: 'Test consequences', deciders: ['John'] // No tags, relatedTo, etc. }; const result = validateTemplateData('nygard', data); expect(result.valid).toBe(true); }); it('should validate Y-statement minimal requirements', () => { const data = { title: 'Simple Decision', context: 'We need to decide', decision: 'We choose this', consequences: 'This will happen', deciders: ['Team'] }; const result = validateTemplateData('y-statement', data); expect(result.valid).toBe(true); }); it('should validate empty arrays', () => { const data = { title: 'Test ADR', context: 'Test context', decision: 'Test decision', consequences: 'Test consequences', deciders: [] // Empty array }; const result = validateTemplateData('nygard', data); expect(result.valid).toBe(false); expect(result.errors).toContain('Field "deciders" cannot be empty'); }); }); });