import { test } from 'node:test'; import assert from 'node:assert/strict'; import { FrameSet, type FrameSource, type FrameTarget } from '../../YouleNexus/assets/framework/ui/frame-set.ts'; /** * Mock FrameSource:按 prefix 返回 SpriteFrame 数组。 * SpriteFrame 是 opaque(任何非 null 对象即可,FrameSet 只引用不深查)。 */ function makeFrames(prefix: string, count: number): any[] { return Array.from({ length: count }, (_, i) => ({ __mock: true, name: frameName(prefix, i + 1, count) })); } function frameName(prefix: string, i: number, count: number): string { return count <= 1 ? `${prefix}.png` : `${prefix}_${String(i).padStart(String(count).length, '0')}.png`; } /** Mock 加载器:同步、按 prefix 全量返回该 prefix 的所有帧。 */ function mockSource(frames: Map): FrameSource { return { async loadByPrefix(prefix: string) { return frames.get(prefix) ?? []; }, }; } /** Mock 目标:仅记录最后一次 setFrame 调用。 */ function mockTarget(): FrameTarget & { lastFrame: any | null; lastError: Error | null } { return { lastFrame: null, lastError: null, setSpriteFrame(f) { this.lastFrame = f; } }; } test('FrameSet 首次 setFrame 触发按 prefix 加载', async () => { const frames = makeFrames('atlas-login/00014', 12); const source = mockSource(new Map([['atlas-login/00014', frames]])); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target: mockTarget() }); const before = (set as any).state; assert.equal(before, 'idle'); await set.setFrame(1); assert.equal((set as any).state, 'ready'); }); test('FrameSet setFrame(1) 返回第一帧(与 frameFileName 公式对齐)', async () => { const frames = makeFrames('atlas-login/00014', 12); const source = mockSource(new Map([['atlas-login/00014', frames]])); const target = mockTarget(); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target }); await set.setFrame(1); assert.equal(target.lastFrame?.name, 'atlas-login/00014_01.png'); }); test('FrameSet setFrame(10) 返回第 10 帧(旧引擎实证:通知=FrameIndex=10)', async () => { const frames = makeFrames('atlas-login/00014', 12); const source = mockSource(new Map([['atlas-login/00014', frames]])); const target = mockTarget(); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target }); await set.setFrame(10); assert.equal(target.lastFrame?.name, 'atlas-login/00014_10.png'); }); test('FrameSet setFrame(0) 按 1 处理(旧引擎 2 个对象 FrameIndex=0)', async () => { const frames = makeFrames('atlas-login/00014', 12); const source = mockSource(new Map([['atlas-login/00014', frames]])); const target = mockTarget(); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target }); await set.setFrame(0); assert.equal(target.lastFrame?.name, 'atlas-login/00014_01.png'); }); test('FrameSet 越界帧号显式抛错(第二准则:不兜底)', async () => { const frames = makeFrames('atlas-login/00014', 12); const source = mockSource(new Map([['atlas-login/00014', frames]])); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target: mockTarget() }); await set.setFrame(1); // 先把 state 推到 ready await assert.rejects(() => set.setFrame(13), (e: Error) => { return e.message.includes('atlas-login/00014') && e.message.includes('13'); }); }); test('FrameSet 源返回空数组显式抛错(prefix 错配要早暴露)', async () => { const source = mockSource(new Map()); // 空 const set = new FrameSet({ prefix: 'atlas-login/wrong', source, target: mockTarget() }); await assert.rejects(() => set.setFrame(1), (e: Error) => e.message.includes('atlas-login/wrong')); }); test('FrameSet 不重复 IO:连续 setFrame 只 loadByPrefix 一次', async () => { const frames = makeFrames('atlas-login/00014', 12); let calls = 0; const source: FrameSource = { async loadByPrefix(prefix: string) { calls++; return frames; }, }; const target = mockTarget(); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target }); await set.setFrame(1); await set.setFrame(5); await set.setFrame(10); assert.equal(calls, 1, '只首次触发加载'); }); test('FrameSet 并发去重:两次 setFrame(1) 同时调用也只 loadByPrefix 一次', async () => { const frames = makeFrames('atlas-login/00014', 12); let calls = 0; const source: FrameSource = { async loadByPrefix(prefix: string) { calls++; // 模拟异步:制造竞态 await new Promise((r) => setTimeout(r, 5)); return frames; }, }; const target = mockTarget(); const set = new FrameSet({ prefix: 'atlas-login/00014', source, target }); await Promise.all([set.setFrame(1), set.setFrame(1), set.setFrame(1)]); assert.equal(calls, 1); }); test('FrameSet currentFrame 默认 0(与 B §3 cpid=43 初始一致)', () => { const set = new FrameSet({ prefix: 'atlas-login/00014', source: mockSource(new Map()), target: mockTarget() }); assert.equal(set.currentFrame, 0); });