boardgame-core/tests/utils/command-schema.test.ts

254 lines
9.9 KiB
TypeScript
Raw Permalink Normal View History

2026-04-01 18:54:02 +08:00
import { describe, it, expect } from 'vitest';
2026-04-02 15:59:27 +08:00
import { parseCommand, parseCommandSchema, validateCommand } from '@/utils/command';
2026-04-01 18:54:02 +08:00
describe('parseCommandSchema', () => {
it('should parse empty schema', () => {
const schema = parseCommandSchema('');
expect(schema).toEqual({
name: '',
params: [],
options: {},
flags: {},
2026-04-01 18:54:02 +08:00
});
});
it('should parse required params', () => {
const schema = parseCommandSchema('move <from> <to>');
expect(schema.params).toEqual([
{ name: 'from', required: true, variadic: false },
{ name: 'to', required: true, variadic: false },
]);
});
it('should parse optional params', () => {
const schema = parseCommandSchema('move <from> [to]');
expect(schema.params).toEqual([
{ name: 'from', required: true, variadic: false },
{ name: 'to', required: false, variadic: false },
]);
});
it('should parse variadic params', () => {
const schema = parseCommandSchema('move <from> [targets...]');
expect(schema.params).toEqual([
{ name: 'from', required: true, variadic: false },
{ name: 'targets', required: false, variadic: true },
]);
});
it('should parse required variadic params', () => {
const schema = parseCommandSchema('move <targets...>');
expect(schema.params).toEqual([
{ name: 'targets', required: true, variadic: true },
]);
});
it('should parse long flags', () => {
const schema = parseCommandSchema('move [--force] [--quiet]');
expect(schema.flags).toEqual({
force: { name: 'force', short: undefined },
quiet: { name: 'quiet', short: undefined },
});
2026-04-01 18:54:02 +08:00
});
it('should parse short flags', () => {
const schema = parseCommandSchema('move [-f] [-q]');
expect(schema.flags).toEqual({
f: { name: 'f', short: 'f' },
q: { name: 'q', short: 'q' },
});
2026-04-01 18:54:02 +08:00
});
it('should parse long options', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move --x: string [--y: string]');
expect(Object.keys(schema.options)).toEqual(['x', 'y']);
expect(schema.options.x).toMatchObject({ name: 'x', required: true });
expect(schema.options.x.schema).toBeDefined();
expect(schema.options.y).toMatchObject({ name: 'y', required: false });
expect(schema.options.y.schema).toBeDefined();
2026-04-01 18:54:02 +08:00
});
it('should parse short options', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move -x: string [-y: string]');
expect(Object.keys(schema.options)).toEqual(['x', 'y']);
expect(schema.options.x).toMatchObject({ name: 'x', short: 'x', required: true });
expect(schema.options.x.schema).toBeDefined();
expect(schema.options.y).toMatchObject({ name: 'y', short: 'y', required: false });
expect(schema.options.y.schema).toBeDefined();
2026-04-01 18:54:02 +08:00
});
it('should parse mixed schema', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move <from> <to> [--force] [-f] [--speed: string -s]');
2026-04-01 18:54:02 +08:00
expect(schema).toEqual({
name: 'move',
params: [
2026-04-01 21:18:58 +08:00
{ name: 'from', required: true, variadic: false, schema: undefined },
{ name: 'to', required: true, variadic: false, schema: undefined },
2026-04-01 18:54:02 +08:00
],
flags: {
force: { name: 'force', short: undefined },
f: { name: 'f', short: 'f' },
},
options: {
speed: { name: 'speed', short: 's', required: false, schema: expect.any(Object), defaultValue: undefined },
},
2026-04-01 18:54:02 +08:00
});
});
it('should handle complex schema', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('place <piece> <region> [x...] [--rotate: number] [--force] [-f]');
2026-04-01 18:54:02 +08:00
expect(schema.name).toBe('place');
expect(schema.params).toHaveLength(3);
expect(Object.keys(schema.flags)).toHaveLength(2);
expect(Object.keys(schema.options)).toHaveLength(1);
2026-04-01 18:54:02 +08:00
});
});
describe('validateCommand', () => {
it('should validate correct command', () => {
const schema = parseCommandSchema('move <from> <to>');
const command = parseCommand('move meeple1 region1');
const result = validateCommand(command, schema);
expect(result).toEqual({ valid: true });
});
it('should reject wrong command name', () => {
const schema = parseCommandSchema('move <from>');
const command = parseCommand('place meeple1');
const result = validateCommand(command, schema);
expect(result).toEqual({
valid: false,
errors: expect.arrayContaining([
expect.stringContaining('命令名称不匹配'),
]),
});
});
it('should reject missing required params', () => {
const schema = parseCommandSchema('move <from> <to>');
const command = parseCommand('move meeple1');
const result = validateCommand(command, schema);
expect(result).toEqual({
valid: false,
errors: expect.arrayContaining([
expect.stringContaining('参数不足'),
]),
});
});
it('should accept optional params missing', () => {
const schema = parseCommandSchema('move <from> [to]');
const command = parseCommand('move meeple1');
const result = validateCommand(command, schema);
expect(result).toEqual({ valid: true });
});
it('should reject extra params without variadic', () => {
const schema = parseCommandSchema('move <from> <to>');
const command = parseCommand('move meeple1 region1 extra');
const result = validateCommand(command, schema);
expect(result).toEqual({
valid: false,
errors: expect.arrayContaining([
expect.stringContaining('参数过多'),
]),
});
});
it('should accept extra params with variadic', () => {
const schema = parseCommandSchema('move <from> [targets...]');
const command = parseCommand('move meeple1 region1 region2 region3');
const result = validateCommand(command, schema);
expect(result).toEqual({ valid: true });
});
it('should reject missing required option', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move <from> --speed: string');
2026-04-01 18:54:02 +08:00
const command = parseCommand('move meeple1');
const result = validateCommand(command, schema);
expect(result).toEqual({
valid: false,
errors: expect.arrayContaining([
expect.stringContaining('缺少必需选项'),
]),
});
});
it('should accept present required option', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move <from> --speed: string');
2026-04-01 18:54:02 +08:00
const command = parseCommand('move meeple1 --speed 10');
const result = validateCommand(command, schema);
expect(result).toEqual({ valid: true });
});
it('should accept optional option missing', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move <from> [--speed: string]');
2026-04-01 18:54:02 +08:00
const command = parseCommand('move meeple1');
const result = validateCommand(command, schema);
expect(result).toEqual({ valid: true });
});
it('should accept flags present or not', () => {
const schema = parseCommandSchema('move <from> [--force]');
const cmd1 = parseCommand('move meeple1');
const cmd2 = parseCommand('move meeple1 --force');
expect(validateCommand(cmd1, schema)).toEqual({ valid: true });
expect(validateCommand(cmd2, schema)).toEqual({ valid: true });
});
it('should validate short form option', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('move <from> -s: string');
2026-04-01 18:54:02 +08:00
const command = parseCommand('move meeple1 -s 10');
const result = validateCommand(command, schema);
expect(result).toEqual({ valid: true });
});
it('should provide detailed error messages', () => {
2026-04-01 21:18:58 +08:00
const schema = parseCommandSchema('place <piece> <region> --rotate: string');
2026-04-01 18:54:02 +08:00
const command = parseCommand('place meeple1');
const result = validateCommand(command, schema);
expect(result.valid).toBe(false);
if (!result.valid) {
expect(result.errors.length).toBeGreaterThanOrEqual(1);
}
});
});
describe('integration', () => {
it('should work together parse and validate', () => {
2026-04-01 21:18:58 +08:00
const schemaStr = 'place <piece> <region> [--x: string] [--y: string] [--force] [-f]';
2026-04-01 18:54:02 +08:00
const schema = parseCommandSchema(schemaStr);
const validCmd = parseCommand('place meeple1 board --x 5 --force');
expect(validateCommand(validCmd, schema)).toEqual({ valid: true });
const invalidCmd = parseCommand('place meeple1');
const result = validateCommand(invalidCmd, schema);
expect(result.valid).toBe(false);
});
2026-04-01 21:18:58 +08:00
it('should parse short alias syntax', () => {
const schema = parseCommandSchema('move <from> [--verbose: boolean -v]');
expect(Object.keys(schema.flags)).toHaveLength(1);
expect(schema.flags.verbose).toEqual({ name: 'verbose', short: 'v' });
2026-04-01 21:18:58 +08:00
});
it('should parse command with short alias', () => {
const schema = parseCommandSchema('move <from> [--verbose -v]');
const command = parseCommand('move meeple1 -v');
const result = validateCommand(command, schema);
expect(result.valid).toBe(true);
expect(command.flags.v).toBe(true);
});
it('should parse command with short alias option', () => {
const schema = parseCommandSchema('move <from> [--speed: number -s]');
const command = parseCommand('move meeple1 -s 100');
const result = validateCommand(command, schema);
expect(result.valid).toBe(true);
expect(command.options.s).toBe('100');
});
2026-04-01 18:54:02 +08:00
});