Files
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

493 lines
16 KiB
TypeScript

/**
* A2UI QE Surface Templates and Integration Tests
* Split from renderer.test.ts
*
* Tests for QE surface templates (coverage, test results, security,
* accessibility) and renderer integration scenarios.
*
* @module tests/unit/adapters/a2ui/renderer-templates
*/
import { describe, it, expect } from 'vitest';
import {
literal,
path,
} from '../../../../src/adapters/a2ui/renderer/message-types.js';
import {
createComponentBuilder,
} from '../../../../src/adapters/a2ui/renderer/component-builder.js';
import {
createSurfaceGenerator,
} from '../../../../src/adapters/a2ui/renderer/surface-generator.js';
import {
createCoverageSurface,
createCoverageDataUpdate,
createCoverageSummarySurface,
createTestResultsSurface,
createTestResultsDataUpdate,
createSecuritySurface,
createSecurityDataUpdate,
createAccessibilitySurface,
createAccessibilityDataUpdate,
type CoverageData,
type TestResults,
type SecurityFindings,
type A11yAudit,
} from '../../../../src/adapters/a2ui/renderer/templates/index.js';
describe('QE Surface Templates', () => {
describe('Coverage Surface Template', () => {
const mockCoverageData: CoverageData = {
total: 85.5,
target: 80,
lineCoverage: 88.0,
branchCoverage: 75.0,
functionCoverage: 92.0,
modules: [
{ name: 'src/core', percentage: 90, fileCount: 10 },
{ name: 'src/utils', percentage: 78, fileCount: 5 },
],
files: [
{
path: 'src/index.ts',
lineCoverage: 95,
branchCoverage: 80,
functionCoverage: 100,
coveredLines: 95,
totalLines: 100,
},
],
gaps: [
{
id: 'gap-1',
file: 'src/utils.ts',
startLine: 10,
endLine: 15,
type: 'uncovered',
description: 'Error handling not covered',
suggestion: 'Add test for error case',
},
],
timestamp: '2026-01-30T12:00:00Z',
summary: '85.5% coverage - meets target',
};
it('should create coverage surface', () => {
const surface = createCoverageSurface(mockCoverageData);
expect(surface.type).toBe('surfaceUpdate');
expect(surface.surfaceId).toBe('coverage-dashboard');
expect(surface.components.length).toBeGreaterThan(0);
});
it('should create coverage surface with custom ID', () => {
const surface = createCoverageSurface(mockCoverageData, 'custom-coverage');
expect(surface.surfaceId).toBe('custom-coverage');
});
it('should create coverage data update', () => {
const update = createCoverageDataUpdate(mockCoverageData);
expect(update.type).toBe('dataModelUpdate');
expect(update.data.metrics).toBeDefined();
expect((update.data.metrics as Record<string, unknown>).total).toBe(85.5);
expect((update.data.metrics as Record<string, unknown>).status).toBe('passed');
});
it('should set status to failed when below target', () => {
const lowCoverage: CoverageData = { ...mockCoverageData, total: 75 };
const update = createCoverageDataUpdate(lowCoverage);
expect((update.data.metrics as Record<string, unknown>).status).toBe('failed');
});
it('should create coverage summary surface', () => {
const surface = createCoverageSummarySurface({
total: 85.5,
target: 80,
summary: 'Good coverage',
timestamp: '2026-01-30T12:00:00Z',
});
expect(surface.surfaceId).toBe('coverage-summary');
expect(surface.components.length).toBeGreaterThan(0);
});
it('should include QE-specific components', () => {
const surface = createCoverageSurface(mockCoverageData);
const types = surface.components.map((c) => c.type);
expect(types).toContain('qe:coverageGauge');
expect(types).toContain('qe:qualityGateIndicator');
});
});
describe('Test Results Surface Template', () => {
const mockTestResults: TestResults = {
total: 100,
passed: 95,
failed: 3,
skipped: 2,
duration: 45000,
passDuration: 40000,
failDuration: 5000,
startTime: '2026-01-30T12:00:00Z',
endTime: '2026-01-30T12:00:45Z',
suites: [
{
name: 'Unit Tests',
file: 'tests/unit.test.ts',
total: 50,
passed: 48,
failed: 2,
skipped: 0,
duration: 20000,
},
],
tests: [
{
id: 'test-1',
name: 'should work correctly',
suite: 'Unit Tests',
status: 'passed',
duration: 100,
startTime: '2026-01-30T12:00:00Z',
endTime: '2026-01-30T12:00:00.1Z',
},
{
id: 'test-2',
name: 'should fail on error',
suite: 'Unit Tests',
status: 'failed',
duration: 500,
error: 'Expected true but got false',
startTime: '2026-01-30T12:00:01Z',
},
],
summary: '95 of 100 tests passed',
passRate: 95,
};
it('should create test results surface', () => {
const surface = createTestResultsSurface(mockTestResults);
expect(surface.type).toBe('surfaceUpdate');
expect(surface.surfaceId).toBe('test-results');
expect(surface.components.length).toBeGreaterThan(0);
});
it('should create test results data update', () => {
const update = createTestResultsDataUpdate(mockTestResults);
expect(update.type).toBe('dataModelUpdate');
const results = update.data.results as Record<string, unknown>;
expect(results.total).toBe(100);
expect(results.passed).toBe(95);
expect(results.passRateFormatted).toBe('95.0%');
});
it('should format duration correctly', () => {
const update = createTestResultsDataUpdate(mockTestResults);
const results = update.data.results as Record<string, unknown>;
expect(results.durationFormatted).toBe('45.0s');
});
it('should compute pass rate color', () => {
const update = createTestResultsDataUpdate(mockTestResults);
const results = update.data.results as Record<string, unknown>;
expect(results.passRateColor).toBe('#4CAF50');
});
it('should include QE-specific components', () => {
const surface = createTestResultsSurface(mockTestResults);
const types = surface.components.map((c) => c.type);
expect(types).toContain('qe:testStatusBadge');
expect(types).toContain('qe:testTimeline');
});
});
describe('Security Surface Template', () => {
const mockSecurityFindings: SecurityFindings = {
total: 15,
bySeverity: [
{ severity: 'critical', count: 2, color: '#9C27B0' },
{ severity: 'high', count: 5, color: '#F44336' },
{ severity: 'medium', count: 6, color: '#FF9800' },
{ severity: 'low', count: 2, color: '#FFC107' },
],
findings: [
{
id: 'vuln-1',
title: 'SQL Injection',
severity: 'critical',
cve: 'CVE-2026-1234',
cwe: 'CWE-89',
owasp: 'A03:2021-Injection',
description: 'SQL injection vulnerability in login form',
remediation: 'Use parameterized queries',
file: 'src/auth.ts',
line: 42,
cvssScore: 9.8,
confidence: 'high',
detectedAt: '2026-01-30T12:00:00Z',
status: 'open',
},
],
dependencies: [
{
package: 'lodash',
currentVersion: '4.17.20',
fixedVersion: '4.17.21',
cve: 'CVE-2021-23337',
severity: 'high',
description: 'Prototype pollution',
},
],
timestamp: '2026-01-30T12:00:00Z',
duration: 30000,
scannerVersion: '2.5.0',
summary: '15 vulnerabilities found',
riskScore: 75,
riskLevel: 'high',
};
it('should create security surface', () => {
const surface = createSecuritySurface(mockSecurityFindings);
expect(surface.type).toBe('surfaceUpdate');
expect(surface.surfaceId).toBe('security-report');
expect(surface.components.length).toBeGreaterThan(0);
});
it('should create security data update', () => {
const update = createSecurityDataUpdate(mockSecurityFindings);
expect(update.type).toBe('dataModelUpdate');
const security = update.data.security as Record<string, unknown>;
expect(security.total).toBe(15);
expect(security.riskScore).toBe(75);
});
it('should compute severity counts', () => {
const update = createSecurityDataUpdate(mockSecurityFindings);
const counts = (update.data.security as Record<string, unknown>).counts as Record<string, number>;
expect(counts.critical).toBe(1);
expect(counts.high).toBe(0);
});
it('should set alert for critical findings', () => {
const update = createSecurityDataUpdate(mockSecurityFindings);
const security = update.data.security as Record<string, unknown>;
expect(security.hasCritical).toBe(true);
expect(security.alertVariant).toBe('error');
});
it('should include QE-specific components', () => {
const surface = createSecuritySurface(mockSecurityFindings);
const types = surface.components.map((c) => c.type);
expect(types).toContain('qe:vulnerabilityCard');
expect(types).toContain('qe:severityBadge');
expect(types).toContain('qe:riskGauge');
});
});
describe('Accessibility Surface Template', () => {
const mockA11yAudit: A11yAudit = {
total: 25,
passed: 175,
score: 87,
targetLevel: 'AA',
isCompliant: false,
byImpact: [
{ impact: 'critical', count: 3, color: '#9C27B0' },
{ impact: 'serious', count: 8, color: '#F44336' },
{ impact: 'moderate', count: 10, color: '#FF9800' },
{ impact: 'minor', count: 4, color: '#FFC107' },
],
byLevel: [
{ level: 'A', count: 10 },
{ level: 'AA', count: 15 },
{ level: 'AAA', count: 0 },
],
byPrinciple: [
{ principle: 'perceivable', name: 'Perceivable', count: 12, color: '#2196F3' },
{ principle: 'operable', name: 'Operable', count: 8, color: '#4CAF50' },
{ principle: 'understandable', name: 'Understandable', count: 3, color: '#FF9800' },
{ principle: 'robust', name: 'Robust', count: 2, color: '#9C27B0' },
],
findings: [
{
id: 'a11y-1',
ruleId: 'image-alt',
rule: 'Images must have alternate text',
criterion: '1.1.1',
wcagLevel: 'A',
principle: 'perceivable',
impact: 'critical',
element: 'img.hero-image',
html: '<img src="hero.jpg">',
description: 'Image missing alt text',
suggestion: 'Add alt attribute describing the image',
helpUrl: 'https://www.w3.org/WAI/WCAG21/Understanding/non-text-content',
pageUrl: 'https://example.com/',
instanceCount: 3,
},
],
pages: [
{
url: 'https://example.com/',
title: 'Home',
findingsCount: 15,
passedCount: 85,
score: 85,
},
],
timestamp: '2026-01-30T12:00:00Z',
duration: 60000,
toolVersion: '4.8.0',
summary: '25 accessibility issues found',
};
it('should create accessibility surface', () => {
const surface = createAccessibilitySurface(mockA11yAudit);
expect(surface.type).toBe('surfaceUpdate');
expect(surface.surfaceId).toBe('a11y-report');
expect(surface.components.length).toBeGreaterThan(0);
});
it('should create accessibility data update', () => {
const update = createAccessibilityDataUpdate(mockA11yAudit);
expect(update.type).toBe('dataModelUpdate');
const a11y = update.data.a11y as Record<string, unknown>;
expect(a11y.total).toBe(25);
expect(a11y.score).toBe(87);
expect(a11y.isCompliant).toBe(false);
});
it('should compute impact counts', () => {
const update = createAccessibilityDataUpdate(mockA11yAudit);
const impacts = (update.data.a11y as Record<string, unknown>).impacts as Record<string, number>;
expect(impacts.critical).toBe(1);
expect(impacts.serious).toBe(0);
});
it('should compute level counts', () => {
const update = createAccessibilityDataUpdate(mockA11yAudit);
const levels = (update.data.a11y as Record<string, unknown>).levels as Record<string, number>;
expect(levels.A).toBe(1);
expect(levels.AA).toBe(0);
});
it('should generate compliance text', () => {
const update = createAccessibilityDataUpdate(mockA11yAudit);
const a11y = update.data.a11y as Record<string, unknown>;
expect(a11y.complianceText).toContain('does not meet WCAG AA');
});
it('should include QE-specific components', () => {
const surface = createAccessibilitySurface(mockA11yAudit);
const types = surface.components.map((c) => c.type);
expect(types).toContain('qe:a11yScoreGauge');
expect(types).toContain('qe:complianceBadge');
expect(types).toContain('qe:a11yFindingCard');
expect(types).toContain('qe:a11yImpactBadge');
});
});
});
describe('A2UI Renderer Integration', () => {
it('should create complete surface workflow', () => {
const generator = createSurfaceGenerator();
const builder = createComponentBuilder();
builder
.beginSurface('dashboard')
.setTitle('QE Dashboard')
.addComponent('root', { type: 'Column' })
.addChild('root', 'coverage', { type: 'qe:coverageGauge', value: path('/coverage') })
.addChild('root', 'tests', { type: 'qe:testStatusBadge', count: path('/tests/passed') });
generator.applyBuilder(builder);
generator.setData('dashboard', {
coverage: 85.5,
tests: { passed: 100, failed: 5 },
});
const messages = generator.getAllMessages('dashboard');
expect(messages).not.toBeNull();
expect(messages?.beginRendering.title).toBe('QE Dashboard');
expect(messages?.surfaceUpdate.components).toHaveLength(3);
expect(messages?.dataModelUpdate.data.coverage).toBe(85.5);
});
it('should handle incremental updates', () => {
const generator = createSurfaceGenerator();
generator.createSurface('test');
generator.addComponents('test', [
{ id: 'gauge', type: 'qe:coverageGauge', properties: { value: 80 } },
{ id: 'badge', type: 'qe:testStatusBadge', properties: { count: 50 } },
]);
generator.updateComponent('test', 'gauge', { properties: { value: 85 } });
const update = generator.generateIncrementalUpdate('test', ['gauge']);
expect(update?.components).toHaveLength(1);
expect(update?.components[0].properties.value).toBe(85);
});
it('should emit proper version tracking', () => {
const generator = createSurfaceGenerator();
generator.createSurface('test');
const v1 = generator.generateSurfaceUpdate('test')?.version;
generator.addComponent('test', { id: 'a', type: 'Text', properties: {} });
const v2 = generator.generateSurfaceUpdate('test')?.version;
expect(v2).toBeGreaterThan(v1!);
});
it('should work with QE templates end-to-end', () => {
const generator = createSurfaceGenerator();
const coverageSurface = createCoverageSurface({
total: 85,
target: 80,
lineCoverage: 88,
branchCoverage: 75,
functionCoverage: 92,
modules: [],
files: [],
gaps: [],
timestamp: '2026-01-30T12:00:00Z',
summary: 'Coverage is good',
});
generator.createSurface(coverageSurface.surfaceId);
generator.addComponents(coverageSurface.surfaceId, coverageSurface.components);
const components = generator.getComponents(coverageSurface.surfaceId);
expect(components.length).toBeGreaterThan(0);
const beginMsg = generator.generateBeginRendering(coverageSurface.surfaceId);
expect(beginMsg).not.toBeNull();
});
});