mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
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>
456 lines
14 KiB
TypeScript
456 lines
14 KiB
TypeScript
/**
|
|
* A2UI Surface Generator Unit Tests
|
|
* Split from renderer.test.ts
|
|
*
|
|
* Tests for the SurfaceGenerator including lifecycle, components,
|
|
* hierarchy, data model, messages, user actions, builder integration,
|
|
* and event emission.
|
|
*
|
|
* @module tests/unit/adapters/a2ui/renderer-surface-generator
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
|
|
import {
|
|
literal,
|
|
path,
|
|
type ComponentNode,
|
|
type UserActionMessage,
|
|
} from '../../../../src/adapters/a2ui/renderer/message-types.js';
|
|
|
|
import {
|
|
createComponentBuilder,
|
|
} from '../../../../src/adapters/a2ui/renderer/component-builder.js';
|
|
|
|
import {
|
|
SurfaceGenerator,
|
|
createSurfaceGenerator,
|
|
type SurfaceState,
|
|
} from '../../../../src/adapters/a2ui/renderer/surface-generator.js';
|
|
|
|
describe('SurfaceGenerator', () => {
|
|
let generator: SurfaceGenerator;
|
|
|
|
beforeEach(() => {
|
|
generator = createSurfaceGenerator({
|
|
idGenerator: () => 'generated-id',
|
|
timestampGenerator: () => '2026-01-30T12:00:00Z',
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('Surface Lifecycle', () => {
|
|
it('should create new surface', () => {
|
|
const surface = generator.createSurface('test-surface', {
|
|
title: 'Test',
|
|
catalogId: 'qe-v1',
|
|
});
|
|
|
|
expect(surface.id).toBe('test-surface');
|
|
expect(surface.title).toBe('Test');
|
|
expect(surface.catalogId).toBe('qe-v1');
|
|
expect(surface.version).toBe(1);
|
|
expect(surface.isRendered).toBe(false);
|
|
});
|
|
|
|
it('should auto-generate surface ID', () => {
|
|
const surface = generator.createSurface();
|
|
expect(surface.id).toBe('generated-id');
|
|
});
|
|
|
|
it('should get surface by ID', () => {
|
|
generator.createSurface('test');
|
|
const surface = generator.getSurface('test');
|
|
expect(surface).toBeDefined();
|
|
expect(surface?.id).toBe('test');
|
|
});
|
|
|
|
it('should check surface existence', () => {
|
|
generator.createSurface('exists');
|
|
expect(generator.hasSurface('exists')).toBe(true);
|
|
expect(generator.hasSurface('missing')).toBe(false);
|
|
});
|
|
|
|
it('should get surface IDs and count', () => {
|
|
generator.createSurface('a');
|
|
generator.createSurface('b');
|
|
expect(generator.getSurfaceIds()).toEqual(['a', 'b']);
|
|
expect(generator.getSurfaceCount()).toBe(2);
|
|
});
|
|
|
|
it('should delete surface', () => {
|
|
generator.createSurface('test');
|
|
const msg = generator.deleteSurface('test');
|
|
|
|
expect(msg?.type).toBe('deleteSurface');
|
|
expect(msg?.surfaceId).toBe('test');
|
|
expect(generator.hasSurface('test')).toBe(false);
|
|
});
|
|
|
|
it('should return null when deleting non-existent surface', () => {
|
|
const msg = generator.deleteSurface('missing');
|
|
expect(msg).toBeNull();
|
|
});
|
|
|
|
it('should enforce max surfaces limit', () => {
|
|
const limitedGenerator = createSurfaceGenerator({ maxSurfaces: 2 });
|
|
limitedGenerator.createSurface('a');
|
|
limitedGenerator.createSurface('b');
|
|
|
|
expect(() => limitedGenerator.createSurface('c')).toThrow(
|
|
'Maximum surfaces limit'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Component Management', () => {
|
|
beforeEach(() => {
|
|
generator.createSurface('test');
|
|
});
|
|
|
|
it('should add component to surface', () => {
|
|
const component: ComponentNode = {
|
|
id: 'comp-1',
|
|
type: 'Text',
|
|
properties: { text: { literalString: 'Hello' } },
|
|
};
|
|
|
|
const msg = generator.addComponent('test', component);
|
|
expect(msg?.components).toHaveLength(1);
|
|
expect(msg?.components[0].id).toBe('comp-1');
|
|
});
|
|
|
|
it('should add multiple components', () => {
|
|
const components: ComponentNode[] = [
|
|
{ id: 'a', type: 'Text', properties: {} },
|
|
{ id: 'b', type: 'Text', properties: {} },
|
|
];
|
|
|
|
const msg = generator.addComponents('test', components);
|
|
expect(msg?.components).toHaveLength(2);
|
|
});
|
|
|
|
it('should update component', () => {
|
|
generator.addComponent('test', {
|
|
id: 'comp',
|
|
type: 'Text',
|
|
properties: { text: { literalString: 'Old' } },
|
|
});
|
|
|
|
const msg = generator.updateComponent('test', 'comp', {
|
|
properties: { text: { literalString: 'New' } },
|
|
});
|
|
|
|
expect(msg?.components[0].properties.text).toEqual({ literalString: 'New' });
|
|
});
|
|
|
|
it('should remove component', () => {
|
|
generator.addComponents('test', [
|
|
{ id: 'a', type: 'Text', properties: {} },
|
|
{ id: 'b', type: 'Text', properties: {} },
|
|
]);
|
|
|
|
const msg = generator.removeComponent('test', 'a');
|
|
expect(msg?.components).toHaveLength(1);
|
|
expect(msg?.components[0].id).toBe('b');
|
|
});
|
|
|
|
it('should get component from surface', () => {
|
|
generator.addComponent('test', {
|
|
id: 'comp',
|
|
type: 'Card',
|
|
properties: {},
|
|
});
|
|
|
|
const comp = generator.getComponent('test', 'comp');
|
|
expect(comp?.type).toBe('Card');
|
|
});
|
|
|
|
it('should get all components from surface', () => {
|
|
generator.addComponents('test', [
|
|
{ id: 'a', type: 'Text', properties: {} },
|
|
{ id: 'b', type: 'Text', properties: {} },
|
|
]);
|
|
|
|
const comps = generator.getComponents('test');
|
|
expect(comps).toHaveLength(2);
|
|
});
|
|
|
|
it('should return null for non-existent surface', () => {
|
|
expect(generator.addComponent('missing', { id: 'x', type: 'T', properties: {} })).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('Hierarchy Management', () => {
|
|
beforeEach(() => {
|
|
generator.createSurface('test');
|
|
generator.addComponents('test', [
|
|
{ id: 'parent', type: 'Column', properties: {} },
|
|
{ id: 'child', type: 'Text', properties: {} },
|
|
]);
|
|
});
|
|
|
|
it('should add child to parent', () => {
|
|
generator.addChild('test', 'parent', 'child');
|
|
const parent = generator.getComponent('test', 'parent');
|
|
expect(parent?.children).toContain('child');
|
|
});
|
|
|
|
it('should remove child from parent', () => {
|
|
generator.addChild('test', 'parent', 'child');
|
|
generator.removeChild('test', 'parent', 'child');
|
|
const parent = generator.getComponent('test', 'parent');
|
|
expect(parent?.children).not.toContain('child');
|
|
});
|
|
|
|
it('should set children for parent', () => {
|
|
generator.addComponent('test', { id: 'child2', type: 'Text', properties: {} });
|
|
generator.setChildren('test', 'parent', ['child', 'child2']);
|
|
const parent = generator.getComponent('test', 'parent');
|
|
expect(parent?.children).toEqual(['child', 'child2']);
|
|
});
|
|
});
|
|
|
|
describe('Data Model Management', () => {
|
|
beforeEach(() => {
|
|
generator.createSurface('test');
|
|
});
|
|
|
|
it('should update surface data', () => {
|
|
const msg = generator.updateData('test', { key: 'value' });
|
|
expect(msg?.type).toBe('dataModelUpdate');
|
|
expect(msg?.data).toEqual({ key: 'value' });
|
|
});
|
|
|
|
it('should merge data updates', () => {
|
|
generator.updateData('test', { a: 1 });
|
|
generator.updateData('test', { b: 2 });
|
|
const data = generator.getData('test');
|
|
expect(data).toEqual({ a: 1, b: 2 });
|
|
});
|
|
|
|
it('should set data (replace)', () => {
|
|
generator.updateData('test', { a: 1, b: 2 });
|
|
generator.setData('test', { c: 3 });
|
|
const data = generator.getData('test');
|
|
expect(data).toEqual({ c: 3 });
|
|
});
|
|
});
|
|
|
|
describe('Message Generation', () => {
|
|
beforeEach(() => {
|
|
generator.createSurface('test', { title: 'Test Surface' });
|
|
generator.addComponent('test', { id: 'root', type: 'Column', properties: {} });
|
|
});
|
|
|
|
it('should generate surfaceUpdate message', () => {
|
|
const msg = generator.generateSurfaceUpdate('test');
|
|
expect(msg?.type).toBe('surfaceUpdate');
|
|
expect(msg?.surfaceId).toBe('test');
|
|
expect(msg?.components).toHaveLength(1);
|
|
});
|
|
|
|
it('should generate incremental update', () => {
|
|
generator.addComponent('test', { id: 'text', type: 'Text', properties: {} });
|
|
const msg = generator.generateIncrementalUpdate('test', ['text']);
|
|
expect(msg?.components).toHaveLength(1);
|
|
expect(msg?.components[0].id).toBe('text');
|
|
});
|
|
|
|
it('should generate dataModelUpdate message', () => {
|
|
generator.setData('test', { value: 42 });
|
|
const msg = generator.generateDataModelUpdate('test');
|
|
expect(msg?.type).toBe('dataModelUpdate');
|
|
expect(msg?.data).toEqual({ value: 42 });
|
|
});
|
|
|
|
it('should generate beginRendering message', () => {
|
|
const msg = generator.generateBeginRendering('test');
|
|
expect(msg?.type).toBe('beginRendering');
|
|
expect(msg?.surfaceId).toBe('test');
|
|
expect(msg?.title).toBe('Test Surface');
|
|
expect(msg?.rootComponentId).toBe('root');
|
|
});
|
|
|
|
it('should mark surface as rendered', () => {
|
|
generator.generateBeginRendering('test');
|
|
const surface = generator.getSurface('test');
|
|
expect(surface?.isRendered).toBe(true);
|
|
});
|
|
|
|
it('should generate deleteSurface message', () => {
|
|
const msg = generator.generateDeleteSurface('test');
|
|
expect(msg?.type).toBe('deleteSurface');
|
|
});
|
|
|
|
it('should get all messages for surface', () => {
|
|
generator.setData('test', { value: 1 });
|
|
const msgs = generator.getAllMessages('test');
|
|
|
|
expect(msgs?.beginRendering.type).toBe('beginRendering');
|
|
expect(msgs?.surfaceUpdate.type).toBe('surfaceUpdate');
|
|
expect(msgs?.dataModelUpdate.type).toBe('dataModelUpdate');
|
|
});
|
|
});
|
|
|
|
describe('User Action Handling', () => {
|
|
it('should create userAction message', () => {
|
|
const action = generator.createUserAction('test', 'btn', 'click', { x: 1 });
|
|
|
|
expect(action.type).toBe('userAction');
|
|
expect(action.surfaceId).toBe('test');
|
|
expect(action.componentId).toBe('btn');
|
|
expect(action.actionId).toBe('click');
|
|
expect(action.payload).toEqual({ x: 1 });
|
|
expect(action.timestamp).toBe('2026-01-30T12:00:00Z');
|
|
});
|
|
|
|
it('should handle user action', () => {
|
|
generator.createSurface('test');
|
|
generator.addComponent('test', { id: 'btn', type: 'Button', properties: {} });
|
|
|
|
const handler = vi.fn();
|
|
const action: UserActionMessage = {
|
|
type: 'userAction',
|
|
surfaceId: 'test',
|
|
componentId: 'btn',
|
|
actionId: 'click',
|
|
};
|
|
|
|
generator.handleUserAction(action, handler);
|
|
expect(handler).toHaveBeenCalledWith(action);
|
|
});
|
|
|
|
it('should throw for missing surface', () => {
|
|
const action: UserActionMessage = {
|
|
type: 'userAction',
|
|
surfaceId: 'missing',
|
|
componentId: 'btn',
|
|
actionId: 'click',
|
|
};
|
|
|
|
expect(() => generator.handleUserAction(action, vi.fn())).toThrow(
|
|
'Surface missing not found'
|
|
);
|
|
});
|
|
|
|
it('should throw for missing component', () => {
|
|
generator.createSurface('test');
|
|
const action: UserActionMessage = {
|
|
type: 'userAction',
|
|
surfaceId: 'test',
|
|
componentId: 'missing',
|
|
actionId: 'click',
|
|
};
|
|
|
|
expect(() => generator.handleUserAction(action, vi.fn())).toThrow(
|
|
'Component missing not found'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('Builder Integration', () => {
|
|
it('should apply builder surface', () => {
|
|
const builder = createComponentBuilder();
|
|
builder
|
|
.beginSurface('builder-surface')
|
|
.addComponent('root', { type: 'Column' })
|
|
.addChild('root', 'text', { type: 'Text' });
|
|
|
|
const msg = generator.applyBuilder(builder);
|
|
expect(msg?.surfaceId).toBe('builder-surface');
|
|
expect(generator.hasSurface('builder-surface')).toBe(true);
|
|
});
|
|
|
|
it('should create builder for surface', () => {
|
|
generator.createSurface('test', { title: 'Test', catalogId: 'qe-v1' });
|
|
generator.addComponent('test', { id: 'root', type: 'Column', properties: {} });
|
|
|
|
const builder = generator.createBuilder('test');
|
|
expect(builder.hasComponent('root')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Utility Methods', () => {
|
|
it('should clear all surfaces', () => {
|
|
generator.createSurface('a');
|
|
generator.createSurface('b');
|
|
generator.clear();
|
|
|
|
expect(generator.getSurfaceCount()).toBe(0);
|
|
});
|
|
|
|
it('should get generator statistics', () => {
|
|
generator.createSurface('a');
|
|
generator.createSurface('b');
|
|
generator.addComponents('a', [
|
|
{ id: 'c1', type: 'Text', properties: {} },
|
|
{ id: 'c2', type: 'Text', properties: {} },
|
|
]);
|
|
generator.addComponent('b', { id: 'c3', type: 'Text', properties: {} });
|
|
generator.generateBeginRendering('a');
|
|
|
|
const stats = generator.getStats();
|
|
expect(stats.surfaceCount).toBe(2);
|
|
expect(stats.totalComponents).toBe(3);
|
|
expect(stats.averageComponentsPerSurface).toBe(1.5);
|
|
expect(stats.renderedSurfaces).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Event Emission', () => {
|
|
it('should emit surfaceCreated event', () => {
|
|
const handler = vi.fn();
|
|
generator.on('surfaceCreated', handler);
|
|
generator.createSurface('test');
|
|
|
|
expect(handler).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'created',
|
|
surfaceId: 'test',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should emit surfaceUpdated event', () => {
|
|
generator.createSurface('test');
|
|
const handler = vi.fn();
|
|
generator.on('surfaceUpdated', handler);
|
|
|
|
generator.addComponent('test', { id: 'comp', type: 'Text', properties: {} });
|
|
expect(handler).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should emit surfaceDeleted event', () => {
|
|
generator.createSurface('test');
|
|
const handler = vi.fn();
|
|
generator.on('surfaceDeleted', handler);
|
|
|
|
generator.deleteSurface('test');
|
|
expect(handler).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'deleted',
|
|
surfaceId: 'test',
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should emit surfaceRendered event', () => {
|
|
generator.createSurface('test');
|
|
const handler = vi.fn();
|
|
generator.on('surfaceRendered', handler);
|
|
|
|
generator.generateBeginRendering('test');
|
|
expect(handler).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
type: 'rendered',
|
|
surfaceId: 'test',
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|