mirror of
https://github.com/motajs/template.git
synced 2026-09-15 19:48:56 +08:00
test(03-15): cover independent replay command ownership
- Verify distinct command classes, fresh instances, stable order, and extension registration - Add structural ownership regressions without changing replay or user-owned decorator boundaries
This commit is contained in:
parent
13162b2406
commit
f5c6b06c59
@ -1,10 +1,10 @@
|
||||
---
|
||||
schema_version: 1
|
||||
open_count: 9
|
||||
open_count: 10
|
||||
waived_count: 0
|
||||
fixed_count: 8
|
||||
total_count: 17
|
||||
last_updated: 2026-09-11T06:11:49.370Z
|
||||
total_count: 18
|
||||
last_updated: 2026-09-11T06:27:22.412Z
|
||||
---
|
||||
|
||||
# Broken Windows Ledger
|
||||
@ -32,6 +32,7 @@ last_updated: 2026-09-11T06:11:49.370Z
|
||||
| 15 | 03 | deviation | packages-user/data-common/src/replay/array.ts | | Repaired replay parameter encoding so diagnostic params remain original and deterministic. | open | | 2026-09-10T09:18:58.279Z | |
|
||||
| 16 | 03 | deviation | packages-user/data-state/test/replayVerifier.ts | | Added a package-local verifier harness shared by Vitest and the Node runner to avoid composite-script import resolution. | open | | 2026-09-10T09:18:58.946Z | |
|
||||
| 17 | 03 | deviation | packages-user/data-state/test/fixtures/closed-loop.ts | | Applied repository Prettier/CRLF formatting to the fixed replay fixture and runner files. | open | | 2026-09-10T09:18:59.589Z | |
|
||||
| 18 | 03 | deviation | script/check-data-circular.ts | | Plan 03-15 scoped circular gate reports seven pre-existing legacy/render boundary cycles through data-state/src/legacy/move.ts; replay command class changes do not touch those imports. | open | | 2026-09-11T06:27:22.412Z | |
|
||||
|
||||
````json
|
||||
[
|
||||
@ -238,6 +239,18 @@ last_updated: 2026-09-11T06:11:49.370Z
|
||||
"reason": "",
|
||||
"recorded_at": "2026-09-10T09:18:59.589Z",
|
||||
"resolved_at": null
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"kind": "deviation",
|
||||
"phase": "03",
|
||||
"file": "script/check-data-circular.ts",
|
||||
"line": null,
|
||||
"description": "Plan 03-15 scoped circular gate reports seven pre-existing legacy/render boundary cycles through data-state/src/legacy/move.ts; replay command class changes do not touch those imports.",
|
||||
"status": "open",
|
||||
"reason": "",
|
||||
"recorded_at": "2026-09-11T06:27:22.412Z",
|
||||
"resolved_at": null
|
||||
}
|
||||
]
|
||||
````
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# Deferred Items — Phase 03
|
||||
|
||||
- `pnpm check:type` remains blocked by pre-existing render/legacy diagnostics outside Plan 03-06. The Tile events contract diagnostics from the earlier baseline were resolved by Plan 03-06.
|
||||
- Plan 03-15's scoped circular gate reports seven pre-existing legacy/render boundary cycles through `data-state/src/legacy/move.ts`; the independent replay command class changes do not touch those imports.
|
||||
|
||||
@ -48,6 +48,16 @@ describe('replay commands', () => {
|
||||
expect(items.map(item => item.code)).toEqual(REPLAY_COMMAND_ORDER);
|
||||
expect(items).toHaveLength(8);
|
||||
expect(items.map(item => item.command.execute)).toHaveLength(8);
|
||||
expect(items.map(item => item.command.constructor.name)).toEqual([
|
||||
'ReplayUpCommand',
|
||||
'ReplayRightCommand',
|
||||
'ReplayDownCommand',
|
||||
'ReplayLeftCommand',
|
||||
'ReplayAutoPathfindCommand',
|
||||
'ReplayUseItemCommand',
|
||||
'ReplayEquipCommand',
|
||||
'ReplayUnequipCommand'
|
||||
]);
|
||||
});
|
||||
|
||||
// 验证顶层注册器按稳定顺序注册并拒绝重复 code
|
||||
@ -72,18 +82,18 @@ describe('replay commands', () => {
|
||||
it('assembles an independent top-level registry for every CoreState', () => {
|
||||
const first = createCoreState();
|
||||
const second = createCoreState();
|
||||
const firstCommands = REPLAY_COMMAND_ORDER.map(
|
||||
code => first.replaySystem.getCommand(code)!
|
||||
);
|
||||
const secondCommands = REPLAY_COMMAND_ORDER.map(
|
||||
code => second.replaySystem.getCommand(code)!
|
||||
);
|
||||
expect(first.replaySystem).not.toBe(second.replaySystem);
|
||||
expect(first.pathfinding).not.toBe(second.pathfinding);
|
||||
expect(
|
||||
REPLAY_COMMAND_ORDER.map(code =>
|
||||
first.replaySystem.getCommand(code)
|
||||
).length
|
||||
).toBe(8);
|
||||
expect(
|
||||
REPLAY_COMMAND_ORDER.map(code =>
|
||||
second.replaySystem.getCommand(code)
|
||||
).length
|
||||
).toBe(8);
|
||||
expect(firstCommands).toHaveLength(8);
|
||||
expect(secondCommands).toHaveLength(8);
|
||||
expect(new Set(firstCommands).size).toBe(8);
|
||||
expect(new Set(secondCommands).size).toBe(8);
|
||||
expect(
|
||||
REPLAY_COMMAND_ORDER.every(
|
||||
code => first.replaySystem.getCommand(code) !== null
|
||||
@ -95,6 +105,32 @@ describe('replay commands', () => {
|
||||
)
|
||||
).toBe(true);
|
||||
expect(first.replaySystem.route).not.toBe(second.replaySystem.route);
|
||||
for (let index = 0; index < firstCommands.length; index++) {
|
||||
expect(firstCommands[index]).not.toBe(secondCommands[index]);
|
||||
}
|
||||
});
|
||||
|
||||
// 验证现有 command item 扩展边界可以替换单个实现而不改变注册器
|
||||
it('registers an existing custom command item through the current interface', () => {
|
||||
const state = createCoreState();
|
||||
const replay = new ReplaySystem();
|
||||
const defaultItems = createReplayCommandItems(state);
|
||||
const customItem: IReplayCommandItem = {
|
||||
code: ReplayCommandCode.Up,
|
||||
command: {
|
||||
execute: () => Promise.resolve(true)
|
||||
}
|
||||
};
|
||||
registerReplayCommandItems(replay, [
|
||||
customItem,
|
||||
...defaultItems.slice(1)
|
||||
]);
|
||||
expect(replay.getCommand(ReplayCommandCode.Up)).toBe(
|
||||
customItem.command
|
||||
);
|
||||
expect(replay.getCommand(ReplayCommandCode.Right)).toBe(
|
||||
defaultItems[1].command
|
||||
);
|
||||
});
|
||||
|
||||
// 验证四向移动不等待 controller.onEnd 即完成 command
|
||||
@ -390,4 +426,38 @@ describe('replay safety decorators', () => {
|
||||
);
|
||||
expect(source).not.toContain('shouldReplay');
|
||||
});
|
||||
|
||||
// 验证八个 command 类各自拥有 execute 且不存在共享入口或跨类调用
|
||||
it('keeps replay command ownership isolated in the command module', () => {
|
||||
const source = readFileSync(
|
||||
new URL('./commands.ts', import.meta.url),
|
||||
'utf8'
|
||||
);
|
||||
const classes = [
|
||||
'ReplayUpCommand',
|
||||
'ReplayRightCommand',
|
||||
'ReplayDownCommand',
|
||||
'ReplayLeftCommand',
|
||||
'ReplayAutoPathfindCommand',
|
||||
'ReplayUseItemCommand',
|
||||
'ReplayEquipCommand',
|
||||
'ReplayUnequipCommand'
|
||||
];
|
||||
expect(source).not.toContain('ReplayCommandEntrances');
|
||||
expect(source).not.toContain('createMoveCommand');
|
||||
expect(source).not.toMatch(/\bentries\./);
|
||||
for (const className of classes) {
|
||||
const body = source.match(
|
||||
new RegExp(
|
||||
`class\\s+${className}\\b[\\s\\S]*?(?=\\r?\\nclass\\s|\\r?\\nfunction\\s|\\r?\\nexport function\\s|\\r?\\n/\\*\\*/)`
|
||||
)
|
||||
)?.[0];
|
||||
expect(body).toBeDefined();
|
||||
expect(body).toMatch(/execute\s*\(/);
|
||||
for (const otherClass of classes) {
|
||||
if (otherClass === className) continue;
|
||||
expect(body).not.toContain(otherClass);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user