fix: replace hardcoded localhost:3001 with relative API URL + add canvas mock and integration test
- Fix frontend Network error: API_BASE now defaults to empty string (same-origin) - Fix 3 files: api.ts, AuthContext.tsx, Dashboard.tsx - Add Canvas 2D context mock in tests/setup.ts for jsdom - Fix -0 vs +0 in ZoomPanController.getViewport() - Add IntegrationWorkflow.test.ts (35 tests, full CAD workflow)
This commit is contained in:
@@ -0,0 +1,259 @@
|
||||
/**
|
||||
* commandRegistry Tests – Command lookup, autocomplete, categories
|
||||
*/
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { CommandRegistry } from '../src/services/commandRegistry';
|
||||
import type { CommandDefinition } from '../src/services/commandRegistry';
|
||||
|
||||
describe('CommandRegistry', () => {
|
||||
let registry: CommandRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new CommandRegistry();
|
||||
});
|
||||
|
||||
describe('lookup by name', () => {
|
||||
it('should find LINE by name', () => {
|
||||
const cmd = registry.lookup('LINE');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('LINE');
|
||||
expect(cmd!.toolId).toBe('line');
|
||||
});
|
||||
|
||||
it('should find CIRCLE by name', () => {
|
||||
const cmd = registry.lookup('CIRCLE');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('CIRCLE');
|
||||
});
|
||||
|
||||
it('should find RECT by name', () => {
|
||||
const cmd = registry.lookup('RECT');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('RECT');
|
||||
});
|
||||
|
||||
it('should find UNDO by name', () => {
|
||||
const cmd = registry.lookup('UNDO');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.category).toBe('meta');
|
||||
});
|
||||
|
||||
it('should be case insensitive', () => {
|
||||
const cmd = registry.lookup('line');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('LINE');
|
||||
});
|
||||
|
||||
it('should trim whitespace', () => {
|
||||
const cmd = registry.lookup(' LINE ');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('LINE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lookup by alias', () => {
|
||||
it('should find LINE by alias L', () => {
|
||||
const cmd = registry.lookup('L');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('LINE');
|
||||
});
|
||||
|
||||
it('should find CIRCLE by alias C', () => {
|
||||
const cmd = registry.lookup('C');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('CIRCLE');
|
||||
});
|
||||
|
||||
it('should find RECT by alias R', () => {
|
||||
const cmd = registry.lookup('R');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('RECT');
|
||||
});
|
||||
|
||||
it('should find MOVE by alias M', () => {
|
||||
const cmd = registry.lookup('M');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('MOVE');
|
||||
});
|
||||
|
||||
it('should find ERASE by alias E and DEL', () => {
|
||||
expect(registry.lookup('E')!.name).toBe('ERASE');
|
||||
expect(registry.lookup('DEL')!.name).toBe('ERASE');
|
||||
});
|
||||
|
||||
it('should find POLYLINE by alias PL', () => {
|
||||
const cmd = registry.lookup('PL');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('POLYLINE');
|
||||
});
|
||||
|
||||
it('should find German alias LINIE for LINE', () => {
|
||||
const cmd = registry.lookup('LINIE');
|
||||
expect(cmd).not.toBeNull();
|
||||
expect(cmd!.name).toBe('LINE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('lookup unknown command', () => {
|
||||
it('should return null for unknown command', () => {
|
||||
expect(registry.lookup('UNKNOWN')).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for empty string', () => {
|
||||
expect(registry.lookup('')).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for random text', () => {
|
||||
expect(registry.lookup('XYZABC')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToolId', () => {
|
||||
it('should return toolId for LINE', () => {
|
||||
expect(registry.getToolId('LINE')).toBe('line');
|
||||
});
|
||||
|
||||
it('should return toolId for CIRCLE alias C', () => {
|
||||
expect(registry.getToolId('C')).toBe('circle');
|
||||
});
|
||||
|
||||
it('should return null for meta commands like UNDO', () => {
|
||||
expect(registry.getToolId('UNDO')).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for unknown command', () => {
|
||||
expect(registry.getToolId('UNKNOWN')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLabel', () => {
|
||||
it('should return label for LINE', () => {
|
||||
const label = registry.getLabel('LINE');
|
||||
expect(label).not.toBeNull();
|
||||
expect(label).toContain('Linie');
|
||||
});
|
||||
|
||||
it('should return label for alias L', () => {
|
||||
const label = registry.getLabel('L');
|
||||
expect(label).not.toBeNull();
|
||||
expect(label).toContain('Linie');
|
||||
});
|
||||
|
||||
it('should return null for unknown command', () => {
|
||||
expect(registry.getLabel('UNKNOWN')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllCommands', () => {
|
||||
it('should return all command definitions', () => {
|
||||
const all = registry.getAllCommands();
|
||||
expect(all.length).toBeGreaterThan(10);
|
||||
});
|
||||
|
||||
it('should include LINE, CIRCLE, RECT, MOVE, UNDO', () => {
|
||||
const all = registry.getAllCommands();
|
||||
const names = all.map(c => c.name);
|
||||
expect(names).toContain('LINE');
|
||||
expect(names).toContain('CIRCLE');
|
||||
expect(names).toContain('RECT');
|
||||
expect(names).toContain('MOVE');
|
||||
expect(names).toContain('UNDO');
|
||||
});
|
||||
});
|
||||
|
||||
describe('autocomplete', () => {
|
||||
it('should return exact match first', () => {
|
||||
const results = registry.autocomplete('L');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
// Exact alias match 'L' should be LINE (priority 1)
|
||||
expect(results[0].name).toBe('LINE');
|
||||
});
|
||||
|
||||
it('should return commands starting with input', () => {
|
||||
const results = registry.autocomplete('LI');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
const names = results.map(c => c.name);
|
||||
expect(names).toContain('LINE');
|
||||
});
|
||||
|
||||
it('should return empty for empty input', () => {
|
||||
expect(registry.autocomplete('')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return max 10 results', () => {
|
||||
const results = registry.autocomplete('A');
|
||||
expect(results.length).toBeLessThanOrEqual(10);
|
||||
});
|
||||
|
||||
it('should match aliases', () => {
|
||||
const results = registry.autocomplete('PL');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
const names = results.map(c => c.name);
|
||||
expect(names).toContain('POLYLINE');
|
||||
});
|
||||
|
||||
it('should match case-insensitively', () => {
|
||||
const results = registry.autocomplete('line');
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
expect(results[0].name).toBe('LINE');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllNames', () => {
|
||||
it('should return all names and aliases in uppercase', () => {
|
||||
const names = registry.getAllNames();
|
||||
expect(names.length).toBeGreaterThan(10);
|
||||
expect(names.every(n => n === n.toUpperCase())).toBe(true);
|
||||
});
|
||||
|
||||
it('should include LINE and alias L', () => {
|
||||
const names = registry.getAllNames();
|
||||
expect(names).toContain('LINE');
|
||||
expect(names).toContain('L');
|
||||
});
|
||||
});
|
||||
|
||||
describe('categories', () => {
|
||||
it('should have draw category commands', () => {
|
||||
const all = registry.getAllCommands();
|
||||
const draw = all.filter(c => c.category === 'draw');
|
||||
expect(draw.length).toBeGreaterThan(5);
|
||||
const names = draw.map(c => c.name);
|
||||
expect(names).toContain('LINE');
|
||||
expect(names).toContain('CIRCLE');
|
||||
});
|
||||
|
||||
it('should have modify category commands', () => {
|
||||
const all = registry.getAllCommands();
|
||||
const modify = all.filter(c => c.category === 'modify');
|
||||
expect(modify.length).toBeGreaterThan(5);
|
||||
const names = modify.map(c => c.name);
|
||||
expect(names).toContain('MOVE');
|
||||
expect(names).toContain('ROTATE');
|
||||
});
|
||||
|
||||
it('should have view category commands', () => {
|
||||
const all = registry.getAllCommands();
|
||||
const view = all.filter(c => c.category === 'view');
|
||||
expect(view.length).toBeGreaterThan(0);
|
||||
const names = view.map(c => c.name);
|
||||
expect(names).toContain('PAN');
|
||||
expect(names).toContain('ZOOM');
|
||||
});
|
||||
|
||||
it('should have meta category commands', () => {
|
||||
const all = registry.getAllCommands();
|
||||
const meta = all.filter(c => c.category === 'meta');
|
||||
expect(meta.length).toBeGreaterThan(0);
|
||||
const names = meta.map(c => c.name);
|
||||
expect(names).toContain('UNDO');
|
||||
expect(names).toContain('REDO');
|
||||
});
|
||||
|
||||
it('should have special category commands', () => {
|
||||
const all = registry.getAllCommands();
|
||||
const special = all.filter(c => c.category === 'special');
|
||||
expect(special.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user