117 lines
4.0 KiB
TypeScript
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');
|
|
});
|
|
});
|