2026-04-01 19:04:09 +08:00
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
|
import {
|
|
|
|
|
parseCommandSchema,
|
|
|
|
|
parseCommandWithSchema,
|
|
|
|
|
validateCommand,
|
|
|
|
|
parseCommand,
|
|
|
|
|
type CommandSchema,
|
2026-04-02 15:59:27 +08:00
|
|
|
} from '@/utils/command';
|
2026-04-01 19:04:09 +08:00
|
|
|
|
|
|
|
|
describe('parseCommandSchema with inline-schema', () => {
|
|
|
|
|
it('should parse schema with typed params', () => {
|
|
|
|
|
const schema = parseCommandSchema('move <from: [x: string; y: string]> <to: string>');
|
|
|
|
|
expect(schema.name).toBe('move');
|
|
|
|
|
expect(schema.params).toHaveLength(2);
|
|
|
|
|
expect(schema.params[0].name).toBe('from');
|
|
|
|
|
expect(schema.params[0].schema).toBeDefined();
|
|
|
|
|
expect(schema.params[1].name).toBe('to');
|
|
|
|
|
expect(schema.params[1].schema).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with typed options', () => {
|
|
|
|
|
const schema = parseCommandSchema('move <from> <to> [--all: boolean] [--count: number]');
|
|
|
|
|
expect(schema.name).toBe('move');
|
2026-04-02 08:29:40 +08:00
|
|
|
expect(Object.keys(schema.flags)).toHaveLength(1);
|
|
|
|
|
expect(Object.keys(schema.options)).toHaveLength(1);
|
|
|
|
|
expect(schema.flags.all.name).toBe('all');
|
|
|
|
|
expect(schema.options.count.name).toBe('count');
|
|
|
|
|
expect(schema.options.count.schema).toBeDefined();
|
2026-04-01 19:04:09 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with tuple type', () => {
|
|
|
|
|
const schema = parseCommandSchema('place <coords: [number; number]>');
|
|
|
|
|
expect(schema.name).toBe('place');
|
|
|
|
|
expect(schema.params).toHaveLength(1);
|
|
|
|
|
expect(schema.params[0].name).toBe('coords');
|
|
|
|
|
expect(schema.params[0].schema).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with array type', () => {
|
|
|
|
|
const schema = parseCommandSchema('select <ids: string[]>');
|
|
|
|
|
expect(schema.name).toBe('select');
|
|
|
|
|
expect(schema.params).toHaveLength(1);
|
|
|
|
|
expect(schema.params[0].name).toBe('ids');
|
|
|
|
|
expect(schema.params[0].schema).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with tuple array type', () => {
|
|
|
|
|
const schema = parseCommandSchema('place <coords: [number; number][]>');
|
|
|
|
|
expect(schema.name).toBe('place');
|
|
|
|
|
expect(schema.params).toHaveLength(1);
|
|
|
|
|
expect(schema.params[0].name).toBe('coords');
|
|
|
|
|
expect(schema.params[0].schema).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with mixed types', () => {
|
|
|
|
|
const schema = parseCommandSchema(
|
2026-04-01 21:18:58 +08:00
|
|
|
'move <from: [x: string; y: string]> <to: string> [--count: number]'
|
2026-04-01 19:04:09 +08:00
|
|
|
);
|
|
|
|
|
expect(schema.name).toBe('move');
|
|
|
|
|
expect(schema.params).toHaveLength(2);
|
2026-04-02 08:29:40 +08:00
|
|
|
expect(Object.keys(schema.options)).toHaveLength(1);
|
2026-04-01 19:04:09 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with optional typed param', () => {
|
|
|
|
|
const schema = parseCommandSchema('move <from> [to: string]');
|
|
|
|
|
expect(schema.name).toBe('move');
|
|
|
|
|
expect(schema.params).toHaveLength(2);
|
|
|
|
|
expect(schema.params[0].required).toBe(true);
|
|
|
|
|
expect(schema.params[1].required).toBe(false);
|
|
|
|
|
expect(schema.params[1].schema).toBeDefined();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse schema with optional typed option', () => {
|
|
|
|
|
const schema = parseCommandSchema('move <from> [--speed: number]');
|
|
|
|
|
expect(schema.name).toBe('move');
|
2026-04-02 08:29:40 +08:00
|
|
|
expect(Object.keys(schema.options)).toHaveLength(1);
|
|
|
|
|
expect(schema.options.speed.required).toBe(false);
|
|
|
|
|
expect(schema.options.speed.schema).toBeDefined();
|
2026-04-01 19:04:09 +08:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('parseCommandWithSchema', () => {
|
|
|
|
|
it('should parse and validate command with tuple param', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'move [1; 2] region1',
|
|
|
|
|
'move <from: [x: string; y: string]> <to: string>'
|
|
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
|
|
|
|
expect(result.command.params).toHaveLength(2);
|
|
|
|
|
expect(result.command.params[0]).toEqual(['1', '2']);
|
|
|
|
|
expect(result.command.params[1]).toBe('region1');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse and validate command with boolean option', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
2026-04-01 21:18:58 +08:00
|
|
|
'move meeple1 region1 --count 5',
|
|
|
|
|
'move <from> <to> [--count: number]'
|
2026-04-01 19:04:09 +08:00
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
2026-04-01 21:18:58 +08:00
|
|
|
expect(result.command.options.count).toBe(5);
|
2026-04-01 19:04:09 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse and validate command with number option', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
2026-04-01 21:18:58 +08:00
|
|
|
'move meeple1 region1 --speed 100',
|
|
|
|
|
'move <from> <to> [--speed: number]'
|
2026-04-01 19:04:09 +08:00
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
2026-04-01 21:18:58 +08:00
|
|
|
expect(result.command.options.speed).toBe(100);
|
2026-04-01 19:04:09 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should fail validation with wrong command name', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'jump meeple1 region1',
|
|
|
|
|
'move <from> <to>'
|
|
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(false);
|
|
|
|
|
if (!result.valid) {
|
|
|
|
|
expect(result.errors).toContainEqual(
|
|
|
|
|
expect.stringContaining('命令名称不匹配')
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should fail validation with missing required param', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'move meeple1',
|
|
|
|
|
'move <from> <to>'
|
|
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(false);
|
|
|
|
|
if (!result.valid) {
|
|
|
|
|
expect(result.errors).toContainEqual(
|
|
|
|
|
expect.stringContaining('参数不足')
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should fail validation with missing required option', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'move meeple1 region1',
|
2026-04-01 21:18:58 +08:00
|
|
|
'move <from> <to> [--force]'
|
2026-04-01 19:04:09 +08:00
|
|
|
);
|
2026-04-01 21:18:58 +08:00
|
|
|
// 可选标志,应该通过验证
|
2026-04-01 19:04:09 +08:00
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse complex command with typed params and options', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
2026-04-01 21:18:58 +08:00
|
|
|
'move [1; 2] region1 --count 3',
|
|
|
|
|
'move <from: [x: string; y: string]> <to: string> [--count: number]'
|
2026-04-01 19:04:09 +08:00
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
|
|
|
|
expect(result.command.params[0]).toEqual(['1', '2']);
|
|
|
|
|
expect(result.command.params[1]).toBe('region1');
|
|
|
|
|
expect(result.command.options.count).toBe(3);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle number parsing in tuple', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'place [10; 20]',
|
|
|
|
|
'place <coords: [number; number]>'
|
|
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
|
|
|
|
expect(result.command.params[0]).toEqual([10, 20]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle string array parsing', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'select [alice; bob; charlie]',
|
|
|
|
|
'select <ids: string[]>'
|
|
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
|
|
|
|
expect(result.command.params[0]).toEqual(['alice', 'bob', 'charlie']);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle tuple array parsing', () => {
|
|
|
|
|
const result = parseCommandWithSchema(
|
|
|
|
|
'place [[1; 2]; [3; 4]; [5; 6]]',
|
|
|
|
|
'place <coords: [number; number][]>'
|
|
|
|
|
);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
if (result.valid) {
|
|
|
|
|
expect(result.command.params[0]).toEqual([[1, 2], [3, 4], [5, 6]]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('validateCommand with schema types', () => {
|
|
|
|
|
it('should validate command with typed params', () => {
|
|
|
|
|
const schema = parseCommandSchema('move <from: [x: string; y: string]> <to: string>');
|
|
|
|
|
const command = parseCommand('move [1; 2] region1');
|
|
|
|
|
const result = validateCommand(command, schema);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should validate command with typed options', () => {
|
2026-04-01 21:18:58 +08:00
|
|
|
const schema = parseCommandSchema('move <from> <to> [--count: number]');
|
|
|
|
|
const command = parseCommand('move meeple1 region1 --count 5');
|
2026-04-01 19:04:09 +08:00
|
|
|
const result = validateCommand(command, schema);
|
|
|
|
|
expect(result.valid).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should fail validation with insufficient params', () => {
|
|
|
|
|
const schema = parseCommandSchema('move <from: [x: string; y: string]> <to: string>');
|
|
|
|
|
const command = parseCommand('move [1; 2]');
|
|
|
|
|
const result = validateCommand(command, schema);
|
|
|
|
|
expect(result.valid).toBe(false);
|
|
|
|
|
if (!result.valid) {
|
|
|
|
|
expect(result.errors).toContainEqual(
|
|
|
|
|
expect.stringContaining('参数不足')
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|