mirror of
https://github.com/motajs/template.git
synced 2026-10-19 05:10:16 +08:00
test(08-02): align data-common tests to shipped replay/face interfaces
Remove addHook().load() chains; align ReplaySandbox command stubs to ReplayCommandType.Active + ReplayCommandResult; retarget failure assertions to error 72; fix short-string param token base to 32; expect FaceDirection.Unknown for unknown blocks; onRecordCommand index 0; retire utils.test.ts. Confirmed production fix: restore first-step pre-read in ReplaySandbox play()/step() (75a39ad omission). Focused: 147/147 green.
This commit is contained in:
parent
b053c509c7
commit
70d530d71c
@ -78,7 +78,7 @@ describe('RoleFaceBinder malloc and bind', () => {
|
||||
|
||||
expect(result.info.map(info => info.code)).toContain(43);
|
||||
expect(binder.getMainFace(2)).toBeNull();
|
||||
expect(binder.getFaceDirection(2)).toBeUndefined();
|
||||
expect(binder.getFaceDirection(2)).toBe(FaceDirection.Unknown);
|
||||
});
|
||||
|
||||
// 验证绑定与主朝向相同朝向时经 logger 观测错误码 44 且不建立绑定
|
||||
@ -92,7 +92,7 @@ describe('RoleFaceBinder malloc and bind', () => {
|
||||
|
||||
expect(result.info.map(info => info.code)).toContain(44);
|
||||
expect(binder.getMainFace(2)).toBeNull();
|
||||
expect(binder.getFaceDirection(2)).toBeUndefined();
|
||||
expect(binder.getFaceDirection(2)).toBe(FaceDirection.Unknown);
|
||||
});
|
||||
});
|
||||
|
||||
@ -106,11 +106,11 @@ describe('RoleFaceBinder queries', () => {
|
||||
expect(binder.getFaceOf(99, FaceDirection.Down)).toBeNull();
|
||||
});
|
||||
|
||||
// 验证 getFaceDirection 对未注册图块返回 undefined
|
||||
it('returns undefined direction for an unknown block', () => {
|
||||
// 验证 getFaceDirection 对未注册图块返回 FaceDirection.Unknown
|
||||
it('returns Unknown direction for an unknown block', () => {
|
||||
const binder = createBinder();
|
||||
|
||||
expect(binder.getFaceDirection(42)).toBeUndefined();
|
||||
expect(binder.getFaceDirection(42)).toBe(FaceDirection.Unknown);
|
||||
});
|
||||
|
||||
// 验证 getMainFace 对未注册图块返回 null
|
||||
|
||||
@ -1,203 +0,0 @@
|
||||
// 测试 L0 common 朝向工具纯函数:坐标偏移、降级、旋转与字符串解析
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { FaceDirection } from '../types';
|
||||
import {
|
||||
degradeFace,
|
||||
fromDirectionString,
|
||||
getFaceMovement,
|
||||
nextFaceDirection
|
||||
} from '../utils';
|
||||
|
||||
/** 全部朝向枚举值,用于校验方向表的完整覆盖 */
|
||||
const ALL_DIRECTIONS: readonly FaceDirection[] = [
|
||||
FaceDirection.Unknown,
|
||||
FaceDirection.Left,
|
||||
FaceDirection.Up,
|
||||
FaceDirection.Right,
|
||||
FaceDirection.Down,
|
||||
FaceDirection.LeftUp,
|
||||
FaceDirection.RightUp,
|
||||
FaceDirection.LeftDown,
|
||||
FaceDirection.RightDown
|
||||
];
|
||||
|
||||
/** 每个朝向对应的单步坐标偏移量 */
|
||||
const FACE_MOVEMENTS: readonly [FaceDirection, Loc][] = [
|
||||
[FaceDirection.Unknown, { x: 0, y: 0 }],
|
||||
[FaceDirection.Left, { x: -1, y: 0 }],
|
||||
[FaceDirection.Up, { x: 0, y: -1 }],
|
||||
[FaceDirection.Right, { x: 1, y: 0 }],
|
||||
[FaceDirection.Down, { x: 0, y: 1 }],
|
||||
[FaceDirection.LeftUp, { x: -1, y: -1 }],
|
||||
[FaceDirection.RightUp, { x: 1, y: -1 }],
|
||||
[FaceDirection.LeftDown, { x: -1, y: 1 }],
|
||||
[FaceDirection.RightDown, { x: 1, y: 1 }]
|
||||
];
|
||||
|
||||
describe('getFaceMovement', () => {
|
||||
// 验证全部朝向(含未知)都返回唯一且正确的坐标偏移量
|
||||
it('returns the coordinate offset for every face direction', () => {
|
||||
for (const [dir, movement] of FACE_MOVEMENTS) {
|
||||
expect(getFaceMovement(dir)).toEqual(movement);
|
||||
}
|
||||
expect(FACE_MOVEMENTS.map(item => item[0]).sort()).toEqual(
|
||||
[...ALL_DIRECTIONS].sort()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('degradeFace', () => {
|
||||
// 验证对角朝向降级为对应的左/右,正交朝向原样返回
|
||||
it('degrades diagonals into their orthogonal direction', () => {
|
||||
expect(degradeFace(FaceDirection.LeftUp)).toBe(FaceDirection.Left);
|
||||
expect(degradeFace(FaceDirection.LeftDown)).toBe(FaceDirection.Left);
|
||||
expect(degradeFace(FaceDirection.RightUp)).toBe(FaceDirection.Right);
|
||||
expect(degradeFace(FaceDirection.RightDown)).toBe(FaceDirection.Right);
|
||||
expect(degradeFace(FaceDirection.Left)).toBe(FaceDirection.Left);
|
||||
expect(degradeFace(FaceDirection.Right)).toBe(FaceDirection.Right);
|
||||
expect(degradeFace(FaceDirection.Up)).toBe(FaceDirection.Up);
|
||||
expect(degradeFace(FaceDirection.Down)).toBe(FaceDirection.Down);
|
||||
});
|
||||
|
||||
// 验证未知朝向默认返回未知,且可经 unknown 参数自定义返回值
|
||||
it('maps unknown to the configurable fallback', () => {
|
||||
expect(degradeFace(FaceDirection.Unknown)).toBe(FaceDirection.Unknown);
|
||||
expect(degradeFace(FaceDirection.Unknown, FaceDirection.Down)).toBe(
|
||||
FaceDirection.Down
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nextFaceDirection', () => {
|
||||
// 验证四方向顺时针旋转顺序为上→右→下→左
|
||||
it('rotates four directions clockwise', () => {
|
||||
expect(nextFaceDirection(FaceDirection.Up)).toBe(FaceDirection.Right);
|
||||
expect(nextFaceDirection(FaceDirection.Right)).toBe(FaceDirection.Down);
|
||||
expect(nextFaceDirection(FaceDirection.Down)).toBe(FaceDirection.Left);
|
||||
expect(nextFaceDirection(FaceDirection.Left)).toBe(FaceDirection.Up);
|
||||
});
|
||||
|
||||
// 验证四方向逆时针旋转顺序为上→左→下→右
|
||||
it('rotates four directions anticlockwise', () => {
|
||||
expect(nextFaceDirection(FaceDirection.Up, true)).toBe(
|
||||
FaceDirection.Left
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Left, true)).toBe(
|
||||
FaceDirection.Down
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Down, true)).toBe(
|
||||
FaceDirection.Right
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Right, true)).toBe(
|
||||
FaceDirection.Up
|
||||
);
|
||||
});
|
||||
|
||||
// 验证四方向模式下对角朝向同样按九十度旋转
|
||||
it('rotates diagonal directions in four-way mode', () => {
|
||||
expect(nextFaceDirection(FaceDirection.LeftUp)).toBe(
|
||||
FaceDirection.RightUp
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.RightUp)).toBe(
|
||||
FaceDirection.RightDown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.RightDown)).toBe(
|
||||
FaceDirection.LeftDown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.LeftDown)).toBe(
|
||||
FaceDirection.LeftUp
|
||||
);
|
||||
});
|
||||
|
||||
// 验证八方向顺时针按四十五度依次推进一整圈
|
||||
it('rotates eight directions clockwise', () => {
|
||||
expect(nextFaceDirection(FaceDirection.Up, false, true)).toBe(
|
||||
FaceDirection.RightUp
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.RightUp, false, true)).toBe(
|
||||
FaceDirection.Right
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Right, false, true)).toBe(
|
||||
FaceDirection.RightDown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.RightDown, false, true)).toBe(
|
||||
FaceDirection.Down
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Down, false, true)).toBe(
|
||||
FaceDirection.LeftDown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.LeftDown, false, true)).toBe(
|
||||
FaceDirection.Left
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Left, false, true)).toBe(
|
||||
FaceDirection.LeftUp
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.LeftUp, false, true)).toBe(
|
||||
FaceDirection.Up
|
||||
);
|
||||
});
|
||||
|
||||
// 验证八方向逆时针按四十五度依次回退一整圈
|
||||
it('rotates eight directions anticlockwise', () => {
|
||||
expect(nextFaceDirection(FaceDirection.Up, true, true)).toBe(
|
||||
FaceDirection.LeftUp
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.LeftUp, true, true)).toBe(
|
||||
FaceDirection.Left
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Left, true, true)).toBe(
|
||||
FaceDirection.LeftDown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.LeftDown, true, true)).toBe(
|
||||
FaceDirection.Down
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Down, true, true)).toBe(
|
||||
FaceDirection.RightDown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.RightDown, true, true)).toBe(
|
||||
FaceDirection.Right
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Right, true, true)).toBe(
|
||||
FaceDirection.RightUp
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.RightUp, true, true)).toBe(
|
||||
FaceDirection.Up
|
||||
);
|
||||
});
|
||||
|
||||
// 验证未知朝向在所有旋转模式下都原样透传
|
||||
it('passes unknown through every rotation mode', () => {
|
||||
expect(nextFaceDirection(FaceDirection.Unknown)).toBe(
|
||||
FaceDirection.Unknown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Unknown, true)).toBe(
|
||||
FaceDirection.Unknown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Unknown, false, true)).toBe(
|
||||
FaceDirection.Unknown
|
||||
);
|
||||
expect(nextFaceDirection(FaceDirection.Unknown, true, true)).toBe(
|
||||
FaceDirection.Unknown
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromDirectionString', () => {
|
||||
// 验证每个合法朝向字符串都解析为对应枚举值
|
||||
it('parses every direction string', () => {
|
||||
expect(fromDirectionString('left')).toBe(FaceDirection.Left);
|
||||
expect(fromDirectionString('right')).toBe(FaceDirection.Right);
|
||||
expect(fromDirectionString('up')).toBe(FaceDirection.Up);
|
||||
expect(fromDirectionString('down')).toBe(FaceDirection.Down);
|
||||
expect(fromDirectionString('leftup')).toBe(FaceDirection.LeftUp);
|
||||
expect(fromDirectionString('rightup')).toBe(FaceDirection.RightUp);
|
||||
expect(fromDirectionString('leftdown')).toBe(FaceDirection.LeftDown);
|
||||
expect(fromDirectionString('rightdown')).toBe(FaceDirection.RightDown);
|
||||
});
|
||||
|
||||
// 验证无法识别的字符串回退为未知朝向
|
||||
it('falls back to unknown for an unrecognized string', () => {
|
||||
const invalid = 'sideways' as Dir2;
|
||||
expect(fromDirectionString(invalid)).toBe(FaceDirection.Unknown);
|
||||
});
|
||||
});
|
||||
@ -572,13 +572,13 @@ describe('ReplayArray param codec', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
// 验证短字符串参数使用内联类型 token(长度 + 9)并读回一致
|
||||
// 验证短字符串参数使用内联类型 token(长度 + 32)并读回一致
|
||||
it('round-trips a short string with an inline type token', () => {
|
||||
const array = createArray();
|
||||
array.add(0, ['hi']);
|
||||
|
||||
expect(array.get(0).params).toEqual(['hi']);
|
||||
expect(firstParamToken(array)).toBe(11);
|
||||
expect(firstParamToken(array)).toBe(34);
|
||||
});
|
||||
|
||||
// 验证超过内联长度的字符串参数使用 type 9 并读回一致
|
||||
@ -591,13 +591,13 @@ describe('ReplayArray param codec', () => {
|
||||
expect(firstParamToken(array)).toBe(9);
|
||||
});
|
||||
|
||||
// 验证空字符串参数回退到 type 9 并读回为空串
|
||||
// 验证空字符串参数按内联类型码 32(长度 0)并读回为空串
|
||||
it('round-trips an empty string', () => {
|
||||
const array = createArray();
|
||||
array.add(0, ['']);
|
||||
|
||||
expect(array.get(0).params).toEqual(['']);
|
||||
expect(firstParamToken(array)).toBe(9);
|
||||
expect(firstParamToken(array)).toBe(32);
|
||||
});
|
||||
|
||||
// 验证单条录像步的多个不同类型参数按顺序完整读回
|
||||
@ -791,7 +791,7 @@ describe('ReplayArray param codec', () => {
|
||||
});
|
||||
|
||||
describe('ReplayArray param type table', () => {
|
||||
// 参数类型码表只增不改:锚定 float=6、非负 bigint=7、负 bigint=8、短字符串基址 9、负 int64=5
|
||||
// 参数类型码表只增不改:锚定 float=6、非负 bigint=7、负 bigint=8、短字符串基址 32、负 int64=5
|
||||
it('keeps the param type code mapping stable', () => {
|
||||
const floatArray = createArray();
|
||||
floatArray.add(0, [1.5]);
|
||||
@ -811,7 +811,7 @@ describe('ReplayArray param type table', () => {
|
||||
|
||||
const shortStringArray = createArray();
|
||||
shortStringArray.add(0, ['hi']);
|
||||
expect(firstParamToken(shortStringArray)).toBe(11);
|
||||
expect(firstParamToken(shortStringArray)).toBe(34);
|
||||
|
||||
const longStringArray = createArray();
|
||||
longStringArray.add(0, ['a'.repeat(300)]);
|
||||
|
||||
@ -2,7 +2,13 @@
|
||||
import { logger } from '@motajs/common';
|
||||
import { afterAll, afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { ReplaySystem } from '../system';
|
||||
import { IReplayCommand, IReplaySandbox, IReplayStepHandler } from '../types';
|
||||
import {
|
||||
IReplayCommand,
|
||||
IReplaySandbox,
|
||||
IReplayStepHandler,
|
||||
ReplayCommandResult,
|
||||
ReplayCommandType
|
||||
} from '../types';
|
||||
|
||||
vi.hoisted(() => {
|
||||
vi.stubGlobal('main', { replayChecking: true });
|
||||
@ -22,12 +28,14 @@ interface IManualReplaySandbox extends IReplaySandbox {
|
||||
pausing: boolean;
|
||||
}
|
||||
|
||||
// 构造一个可注入执行与收尾行为的录像命令
|
||||
// 构造一个可注入执行与收尾行为的主动录像命令
|
||||
function createCommand(
|
||||
execute: (step: IReplayStepHandler) => Promise<boolean>,
|
||||
notExecuted?: () => Promise<boolean>
|
||||
execute: (step: IReplayStepHandler) => Promise<ReplayCommandResult>,
|
||||
finalize?: () => Promise<ReplayCommandResult>
|
||||
): IReplayCommand {
|
||||
return notExecuted ? { execute, finalize: notExecuted } : { execute };
|
||||
return finalize
|
||||
? { type: ReplayCommandType.Active, execute, finalize }
|
||||
: { type: ReplayCommandType.Active, execute };
|
||||
}
|
||||
|
||||
// 用录像系统构造一个可手动驱动的录像沙箱
|
||||
@ -53,7 +61,7 @@ async function waitForEnded(sandbox: IReplaySandbox): Promise<void> {
|
||||
|
||||
// 生成一个可外部兑现的异步结果
|
||||
function createDeferred() {
|
||||
return Promise.withResolvers<boolean>();
|
||||
return Promise.withResolvers<ReplayCommandResult>();
|
||||
}
|
||||
|
||||
describe('ReplaySandbox stepping', () => {
|
||||
@ -65,19 +73,17 @@ describe('ReplaySandbox stepping', () => {
|
||||
1,
|
||||
createCommand(async step => {
|
||||
executed.push(step.index);
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
})
|
||||
);
|
||||
system.record(1, 5);
|
||||
const sandbox = createSandbox(system);
|
||||
const stepped: IReplayStepHandler[] = [];
|
||||
sandbox
|
||||
.addHook({
|
||||
onStep: async step => {
|
||||
stepped.push(step);
|
||||
}
|
||||
})
|
||||
.load();
|
||||
sandbox.addHook({
|
||||
onStep: async step => {
|
||||
stepped.push(step);
|
||||
}
|
||||
});
|
||||
start(sandbox);
|
||||
|
||||
await expect(sandbox.step()).resolves.toBe(true);
|
||||
@ -88,23 +94,23 @@ describe('ReplaySandbox stepping', () => {
|
||||
expect(sandbox.getReplayed()).toBe(1);
|
||||
});
|
||||
|
||||
// 验证执行失败的命令触发告警码 158 并停止后续步骤
|
||||
it('warns code 158 and stops when a command returns false', async () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
// 验证执行失败的命令触发错误码 72 并停止后续步骤
|
||||
it('errors code 72 and stops when a command returns false', async () => {
|
||||
const error = vi.spyOn(logger, 'error').mockImplementation(() => {});
|
||||
const executed: number[] = [];
|
||||
const system = new ReplaySystem();
|
||||
system.registerCommand(
|
||||
1,
|
||||
createCommand(async () => {
|
||||
executed.push(1);
|
||||
return false;
|
||||
return ReplayCommandResult.Failed;
|
||||
})
|
||||
);
|
||||
system.registerCommand(
|
||||
2,
|
||||
createCommand(async () => {
|
||||
executed.push(2);
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
})
|
||||
);
|
||||
system.record(1);
|
||||
@ -115,8 +121,8 @@ describe('ReplaySandbox stepping', () => {
|
||||
await expect(sandbox.step()).resolves.toBe(false);
|
||||
|
||||
expect(executed).toEqual([1]);
|
||||
expect(warn).toHaveBeenCalledWith(158, '1', '[]');
|
||||
expect(sandbox.getReplayed()).toBe(1);
|
||||
expect(error).toHaveBeenCalledWith(72, 'execute');
|
||||
expect(sandbox.getReplayed()).toBe(2);
|
||||
});
|
||||
|
||||
// 验证未知指令触发告警码 157 并返回 false
|
||||
@ -141,11 +147,11 @@ describe('ReplaySandbox stepping', () => {
|
||||
createCommand(
|
||||
async () => {
|
||||
order.push('exec1');
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
},
|
||||
async () => {
|
||||
order.push('fin1');
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -153,7 +159,7 @@ describe('ReplaySandbox stepping', () => {
|
||||
2,
|
||||
createCommand(async () => {
|
||||
order.push('exec2');
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
})
|
||||
);
|
||||
system.record(1);
|
||||
@ -167,20 +173,20 @@ describe('ReplaySandbox stepping', () => {
|
||||
expect(order).toEqual(['exec1', 'fin1', 'exec2']);
|
||||
});
|
||||
|
||||
// 验证 notExecuted 收尾失败触发告警码 175 并停止
|
||||
it('warns code 175 when notExecuted fails', async () => {
|
||||
const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
|
||||
// 验证收尾失败触发错误码 72 并停止
|
||||
it('errors code 72 when finalize fails', async () => {
|
||||
const error = vi.spyOn(logger, 'error').mockImplementation(() => {});
|
||||
const system = new ReplaySystem();
|
||||
system.registerCommand(
|
||||
1,
|
||||
createCommand(
|
||||
async () => true,
|
||||
async () => false
|
||||
async () => ReplayCommandResult.Success,
|
||||
async () => ReplayCommandResult.Failed
|
||||
)
|
||||
);
|
||||
system.registerCommand(
|
||||
2,
|
||||
createCommand(async () => true)
|
||||
createCommand(async () => ReplayCommandResult.Success)
|
||||
);
|
||||
system.record(1);
|
||||
system.record(2);
|
||||
@ -190,7 +196,7 @@ describe('ReplaySandbox stepping', () => {
|
||||
await expect(sandbox.step()).resolves.toBe(true);
|
||||
await expect(sandbox.step()).resolves.toBe(false);
|
||||
|
||||
expect(warn).toHaveBeenCalledWith(175, '1');
|
||||
expect(error).toHaveBeenCalledWith(72, 'finalize');
|
||||
});
|
||||
|
||||
// 验证读取流过期时触发告警码 156 并返回 false
|
||||
@ -199,7 +205,7 @@ describe('ReplaySandbox stepping', () => {
|
||||
const system = new ReplaySystem();
|
||||
system.registerCommand(
|
||||
1,
|
||||
createCommand(async () => true)
|
||||
createCommand(async () => ReplayCommandResult.Success)
|
||||
);
|
||||
system.record(1);
|
||||
const sandbox = createSandbox(system);
|
||||
@ -219,7 +225,7 @@ describe('ReplaySandbox stepping', () => {
|
||||
1,
|
||||
createCommand(async step => {
|
||||
executed.push(step.index);
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
})
|
||||
);
|
||||
system.record(1);
|
||||
@ -247,14 +253,14 @@ describe('ReplaySandbox playback control', () => {
|
||||
1,
|
||||
createCommand(async step => {
|
||||
order.push(step.index);
|
||||
return true;
|
||||
return ReplayCommandResult.Success;
|
||||
})
|
||||
);
|
||||
system.record(1);
|
||||
system.record(1);
|
||||
const sandbox = createSandbox(system);
|
||||
const startHook = vi.fn();
|
||||
sandbox.addHook({ onStartReplay: startHook }).load();
|
||||
sandbox.addHook({ onStartReplay: startHook });
|
||||
|
||||
sandbox.play();
|
||||
await waitForEnded(sandbox);
|
||||
@ -276,11 +282,11 @@ describe('ReplaySandbox playback control', () => {
|
||||
system.record(1);
|
||||
const sandbox = createSandbox(system);
|
||||
const startHook = vi.fn();
|
||||
sandbox.addHook({ onStartReplay: startHook }).load();
|
||||
sandbox.addHook({ onStartReplay: startHook });
|
||||
|
||||
sandbox.play();
|
||||
sandbox.play();
|
||||
deferred.resolve(true);
|
||||
deferred.resolve(ReplayCommandResult.Success);
|
||||
await waitForEnded(sandbox);
|
||||
|
||||
expect(startHook).toHaveBeenCalledTimes(1);
|
||||
@ -291,12 +297,12 @@ describe('ReplaySandbox playback control', () => {
|
||||
const system = new ReplaySystem();
|
||||
system.registerCommand(
|
||||
1,
|
||||
createCommand(async () => true)
|
||||
createCommand(async () => ReplayCommandResult.Success)
|
||||
);
|
||||
system.record(1);
|
||||
const sandbox = createSandbox(system);
|
||||
const startHook = vi.fn();
|
||||
sandbox.addHook({ onStartReplay: startHook }).load();
|
||||
sandbox.addHook({ onStartReplay: startHook });
|
||||
|
||||
sandbox.play();
|
||||
await waitForEnded(sandbox);
|
||||
@ -315,7 +321,9 @@ describe('ReplaySandbox playback control', () => {
|
||||
1,
|
||||
createCommand(step => {
|
||||
order.push(step.index);
|
||||
return step.index === 1 ? first.promise : Promise.resolve(true);
|
||||
return step.index === 1
|
||||
? first.promise
|
||||
: Promise.resolve(ReplayCommandResult.Success);
|
||||
})
|
||||
);
|
||||
system.record(1);
|
||||
@ -323,16 +331,14 @@ describe('ReplaySandbox playback control', () => {
|
||||
const sandbox = createSandbox(system);
|
||||
const pauseHook = vi.fn();
|
||||
const resumeHook = vi.fn();
|
||||
sandbox
|
||||
.addHook({
|
||||
onPauseReplay: pauseHook,
|
||||
onResumeReplay: resumeHook
|
||||
})
|
||||
.load();
|
||||
sandbox.addHook({
|
||||
onPauseReplay: pauseHook,
|
||||
onResumeReplay: resumeHook
|
||||
});
|
||||
|
||||
sandbox.play();
|
||||
const paused = sandbox.pause();
|
||||
first.resolve(true);
|
||||
first.resolve(ReplayCommandResult.Success);
|
||||
await paused;
|
||||
|
||||
expect(pauseHook).toHaveBeenCalledTimes(1);
|
||||
@ -359,16 +365,14 @@ describe('ReplaySandbox playback control', () => {
|
||||
const sandbox = createSandbox(system);
|
||||
const pauseHook = vi.fn();
|
||||
const stopHook = vi.fn();
|
||||
sandbox
|
||||
.addHook({
|
||||
onPauseReplay: pauseHook,
|
||||
onStopReplay: stopHook
|
||||
})
|
||||
.load();
|
||||
sandbox.addHook({
|
||||
onPauseReplay: pauseHook,
|
||||
onStopReplay: stopHook
|
||||
});
|
||||
|
||||
sandbox.play();
|
||||
const stopped = sandbox.stop();
|
||||
first.resolve(true);
|
||||
first.resolve(ReplayCommandResult.Success);
|
||||
await stopped;
|
||||
|
||||
expect(pauseHook).toHaveBeenCalledTimes(1);
|
||||
@ -382,7 +386,7 @@ describe('ReplaySandbox playback control', () => {
|
||||
const system = new ReplaySystem();
|
||||
system.registerCommand(
|
||||
1,
|
||||
createCommand(async () => true)
|
||||
createCommand(async () => ReplayCommandResult.Success)
|
||||
);
|
||||
system.record(1);
|
||||
const sandbox = createSandbox(system);
|
||||
@ -400,7 +404,7 @@ describe('ReplaySandbox playback control', () => {
|
||||
const system = new ReplaySystem();
|
||||
const sandbox = createSandbox(system);
|
||||
const speedHook = vi.fn();
|
||||
sandbox.addHook({ onSpeedSet: speedHook }).load();
|
||||
sandbox.addHook({ onSpeedSet: speedHook });
|
||||
|
||||
sandbox.setSpeed(2);
|
||||
|
||||
|
||||
@ -75,13 +75,11 @@ describe('ReplaySystem registration and recording', () => {
|
||||
const recorded: Array<
|
||||
readonly [number, number, readonly ReplayParamValue[]]
|
||||
> = [];
|
||||
system
|
||||
.addHook({
|
||||
onRecordCommand: (code, index, params) => {
|
||||
recorded.push([code, index, params]);
|
||||
}
|
||||
})
|
||||
.load();
|
||||
system.addHook({
|
||||
onRecordCommand: (code, index, params) => {
|
||||
recorded.push([code, index, params]);
|
||||
}
|
||||
});
|
||||
|
||||
system.record(5, 1, true);
|
||||
|
||||
@ -91,7 +89,7 @@ describe('ReplaySystem registration and recording', () => {
|
||||
params: [1, true],
|
||||
index: 0
|
||||
});
|
||||
expect(recorded).toEqual([[5, 1, [1, true]]]);
|
||||
expect(recorded).toEqual([[5, 0, [1, true]]]);
|
||||
});
|
||||
|
||||
// 验证每个 ReplaySystem 实例的注册与录像相互独立
|
||||
@ -113,13 +111,11 @@ describe('ReplaySystem sandbox lifecycle', () => {
|
||||
const system = new ReplaySystem();
|
||||
const { calls, reseter } = createReseter();
|
||||
const created: IReplaySandbox[] = [];
|
||||
system
|
||||
.addHook({
|
||||
onCreateSandbox: sandbox => {
|
||||
created.push(sandbox);
|
||||
}
|
||||
})
|
||||
.load();
|
||||
system.addHook({
|
||||
onCreateSandbox: sandbox => {
|
||||
created.push(sandbox);
|
||||
}
|
||||
});
|
||||
const save = new Map<string, unknown>([['hp', 10]]);
|
||||
|
||||
const sandbox = system.createReplaySandbox({
|
||||
|
||||
@ -264,6 +264,9 @@ export class ReplaySandbox
|
||||
if (this.playing || this.ended) return;
|
||||
this.pausing = false;
|
||||
this.playing = true;
|
||||
if (!this.appendingStep && !this.reader.expired) {
|
||||
this.appendingStep = this.reader.read();
|
||||
}
|
||||
this.forEachHook(hook => hook.onStartReplay?.());
|
||||
this.startReplayLoop();
|
||||
}
|
||||
@ -292,6 +295,14 @@ export class ReplaySandbox
|
||||
}
|
||||
|
||||
async step(): Promise<boolean> {
|
||||
if (
|
||||
this.playing &&
|
||||
!this.appendingStep &&
|
||||
!this.reader.expired &&
|
||||
!this.ended
|
||||
) {
|
||||
this.appendingStep = this.reader.read();
|
||||
}
|
||||
const res = await this.replayStep();
|
||||
if (res.success) {
|
||||
await Promise.all(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user