feat(config-editor): add validation rules (dup/required/enum/ref/overrides/herolist) with tests
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { validate } from '../src/shared/validation';
|
||||
import { RecordValue } from '../src/io/recordValue';
|
||||
|
||||
const ctx = {
|
||||
hasSkill: (u: number) => [6001, 6301, 6501].includes(u),
|
||||
hasField: (u: number) => [7015].includes(u),
|
||||
hasHero: (u: number) => [5011, 6001].includes(u),
|
||||
heroListKeys: new Set<string>(['5011']),
|
||||
};
|
||||
|
||||
function heroObj(extra: Record<string, RecordValue> = {}): RecordValue {
|
||||
return { kind: 'obj', props: {
|
||||
uuid: { kind: 'num', value: 5011 }, name: { kind: 'str', value: 'x' }, path: { kind: 'str', value: 'p' },
|
||||
fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'HERO' }, lv: { kind: 'num', value: 1 },
|
||||
type: { kind: 'enumRef', qualifier: 'HType', member: 'Melee' }, hp: { kind: 'num', value: 400 },
|
||||
ap: { kind: 'num', value: 20 },
|
||||
skills: { kind: 'obj', props: { 6001: { kind: 'obj', props: { uuid: { kind: 'num', value: 6001 }, lv: { kind: 'num', value: 1 }, cd: { kind: 'speed', level: 'Slow3' }, ccd: { kind: 'num', value: 0 } } } } },
|
||||
info: { kind: 'str', value: 'ok' },
|
||||
...extra,
|
||||
} };
|
||||
}
|
||||
|
||||
test('valid hero → no errors', () => {
|
||||
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx);
|
||||
assert.equal(issues.filter(i => i.severity === 'error').length, 0);
|
||||
});
|
||||
test('duplicate uuid → error', () => {
|
||||
// 两条记录携带相同的 uuid 字段值(Map 键不同,uuid 字段相同)
|
||||
const dup = heroObj(); // uuid=5011
|
||||
const issues = validate('hero', new Map([['5011', heroObj()], ['5012', dup]]), ctx);
|
||||
assert.ok(issues.some(i => i.severity === 'error' && i.code === 'dup-uuid'));
|
||||
});
|
||||
test('missing required field → error', () => {
|
||||
const h = heroObj(); delete h.props['name'];
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'missing-required' && i.fieldPath === 'name'));
|
||||
});
|
||||
test('trigger slot references missing skill → error', () => {
|
||||
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
|
||||
s_uuid: { kind: 'num', value: 9999 }, t_num: { kind: 'num', value: 3 } } }] } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'dangling-ref'));
|
||||
});
|
||||
test('field list references missing field skill → error', () => {
|
||||
const h = heroObj({ field: { kind: 'arr', items: [{ kind: 'num', value: 8888 }] } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'dangling-ref'));
|
||||
});
|
||||
test('HeroList/hero consistency: fac=HERO entry missing from HeroList → error', () => {
|
||||
const ctx2 = { ...ctx, heroListKeys: new Set<string>([]) };
|
||||
const issues = validate('hero', new Map([['5011', heroObj()]]), ctx2);
|
||||
assert.ok(issues.some(i => i.code === 'herolist-inconsistent'));
|
||||
});
|
||||
test('invalid enum value → error', () => {
|
||||
const h = heroObj({ fac: { kind: 'enumRef', qualifier: 'FacSet', member: 'NOPE' } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'bad-enum'));
|
||||
});
|
||||
test('unknown overrides key → error', () => {
|
||||
const h = heroObj({ atked: { kind: 'arr', items: [{ kind: 'obj', props: {
|
||||
s_uuid: { kind: 'num', value: 6301 }, t_num: { kind: 'num', value: 3 },
|
||||
overrides: { kind: 'obj', props: { bogus: { kind: 'num', value: 1 } } } } }] } });
|
||||
const issues = validate('hero', new Map([['5011', h]]), ctx);
|
||||
assert.ok(issues.some(i => i.code === 'bad-override-key'));
|
||||
});
|
||||
Reference in New Issue
Block a user