Files
KateLegend2_proj/tests/ui/StorySceneCtrl.test.ts
T
2026-05-06 08:17:32 +08:00

117 lines
4.0 KiB
TypeScript

import { StorySceneCtrl, BASE_TYPING_CPS } from '@ui/StorySceneCtrl';
import { StorageMgr } from '@common/StorageMgr';
import { IStorySceneConfig } from '@data/Interfaces';
import { STORAGE_KEY } from '@common/Constants';
const SCENE: IStorySceneConfig = {
id: 'chapter_1_intro',
bgm: 'bgm_story',
maxDurationSec: 30,
pages: [
{ index: 1, illustration: 'p1', text: 'A' },
{ index: 2, illustration: 'p2', text: 'BCDE' },
{ index: 3, illustration: 'p3', text: 'FGH' },
],
};
function mem() {
const m = new Map<string, string>();
return {
getItem: (k: string) => (m.has(k) ? (m.get(k) as string) : null),
setItem: (k: string, v: string) => {
m.set(k, v);
},
removeItem: (k: string) => {
m.delete(k);
},
};
}
describe('StorySceneCtrl — first-time gate (req 19.5)', () => {
it('start() reports "already_seen" when storage flag is set', () => {
const storage = new StorageMgr(mem());
storage.set(STORAGE_KEY.StoryIntroSeen, true);
const ctrl = new StorySceneCtrl(SCENE, storage);
expect(ctrl.start()).toBe('already_seen');
});
it('start() reports "playing" on first run', () => {
const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
expect(ctrl.start()).toBe('playing');
expect(ctrl.status).toBe('typing');
});
});
describe('StorySceneCtrl — typewriter (req 19.2-19.3)', () => {
it('reveals characters at BASE_TYPING_CPS', () => {
const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
ctrl.start();
// Advance enough time to type "A".
ctrl.tick(1 / BASE_TYPING_CPS + 0.001);
expect(ctrl.visibleText).toBe('A');
// Page "A" complete → waiting_next.
expect(ctrl.status).toBe('waiting_next');
});
it('tap during typing fully reveals the current page', () => {
const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
ctrl.start();
ctrl.onTap(); // accelerate page 1 ("A")
expect(ctrl.visibleText).toBe('A');
expect(ctrl.status).toBe('waiting_next');
// Next tap → advance to page 2.
ctrl.onTap();
expect(ctrl.currentPageNumber).toBe(2);
expect(ctrl.status).toBe('typing');
});
});
describe('StorySceneCtrl — skip (req 19.4)', () => {
it('onSkip immediately finishes and marks seen', () => {
const storage = new StorageMgr(mem());
const onFinished = jest.fn();
const ctrl = new StorySceneCtrl(SCENE, storage, { onFinished });
ctrl.start();
ctrl.onSkip();
expect(ctrl.status).toBe('finished');
expect(onFinished).toHaveBeenCalledWith(true);
expect(storage.get(STORAGE_KEY.StoryIntroSeen, false)).toBe(true);
});
it('re-skipping after finish is a no-op', () => {
const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()));
ctrl.start();
ctrl.onSkip();
expect(() => ctrl.onSkip()).not.toThrow();
});
});
describe('StorySceneCtrl — reset() (req 19.6)', () => {
it('clears the "seen" flag', () => {
const storage = new StorageMgr(mem());
const ctrl = new StorySceneCtrl(SCENE, storage);
ctrl.start();
ctrl.onSkip();
expect(ctrl.hasBeenSeen()).toBe(true);
ctrl.reset();
expect(ctrl.hasBeenSeen()).toBe(false);
});
});
describe('StorySceneCtrl — natural finish (all pages)', () => {
it('calls onFinished(false) after advancing past the last page', () => {
const onFinished = jest.fn();
const ctrl = new StorySceneCtrl(SCENE, new StorageMgr(mem()), { onFinished });
ctrl.start();
// Full page 1.
ctrl.onTap();
ctrl.onTap(); // → page 2
ctrl.onTap(); // reveal page 2
ctrl.onTap(); // → page 3
ctrl.onTap(); // reveal page 3
ctrl.onTap(); // → finish
expect(onFinished).toHaveBeenCalledWith(false);
expect(ctrl.status).toBe('finished');
});
});