115 lines
4.1 KiB
TypeScript
115 lines
4.1 KiB
TypeScript
import { TutorialMgr } from '@logic/TutorialMgr';
|
||
import { ScoreSystem, BASE_ENEMY_SCORE, COMBO_BONUS } from '@logic/ScoreSystem';
|
||
import { WeaponType } from '@data/Interfaces';
|
||
import { StorageMgr } from '@common/StorageMgr';
|
||
|
||
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('TutorialMgr — built-in sequences for 1-1..1-3 (req 11.1-11.3)', () => {
|
||
it('maybeStart returns the first step of 1-1', () => {
|
||
const t = new TutorialMgr(new StorageMgr(mem()));
|
||
const step = t.maybeStart('1-1');
|
||
expect(step).toBeDefined();
|
||
expect(step!.id).toBe('attack');
|
||
});
|
||
|
||
it('reportAction advances through the sequence', () => {
|
||
const t = new TutorialMgr(new StorageMgr(mem()));
|
||
t.maybeStart('1-1');
|
||
expect(t.reportAction('fire_shuriken')).toMatchObject({ id: 'joystick' });
|
||
expect(t.reportAction('move')).toMatchObject({ id: 'jump' });
|
||
expect(t.reportAction('jump')).toBe('finished');
|
||
});
|
||
|
||
it('completed tutorials are persisted and skipped on replay (req 11.4)', () => {
|
||
const storage = new StorageMgr(mem());
|
||
const t = new TutorialMgr(storage);
|
||
t.maybeStart('1-1');
|
||
t.reportAction('fire_shuriken');
|
||
t.reportAction('move');
|
||
t.reportAction('jump');
|
||
expect(t.isCompleted('1-1')).toBe(true);
|
||
expect(t.maybeStart('1-1')).toBeNull();
|
||
});
|
||
|
||
it('resetAll clears the completion set (req 11.5)', () => {
|
||
const t = new TutorialMgr(new StorageMgr(mem()));
|
||
t.maybeStart('1-2');
|
||
t.reportAction('parabolic_jump');
|
||
t.reportAction('attack_switch');
|
||
t.reportAction('parry');
|
||
t.reportAction('jump_attack');
|
||
t.reportAction('pickup_crystal');
|
||
t.resetAll();
|
||
expect(t.maybeStart('1-2')).toBeDefined();
|
||
});
|
||
|
||
it('reportAction with wrong action is a no-op', () => {
|
||
const t = new TutorialMgr(new StorageMgr(mem()));
|
||
t.maybeStart('1-1');
|
||
expect(t.reportAction('wrong')).toBe('no_op');
|
||
expect(t.currentStep()!.id).toBe('attack');
|
||
});
|
||
});
|
||
|
||
describe('ScoreSystem — scoring table (req 12.1-12.6)', () => {
|
||
it('sword kill is ×2, shuriken kill is ×1', () => {
|
||
const s = new ScoreSystem();
|
||
s.recordEnemyKill(WeaponType.NinjaSword);
|
||
s.recordEnemyKill(WeaponType.Shuriken);
|
||
expect(s.snapshot().baseScore).toBe(BASE_ENEMY_SCORE * 3);
|
||
});
|
||
|
||
it('parry kill is ×3 base', () => {
|
||
const s = new ScoreSystem();
|
||
s.recordParryKill();
|
||
expect(s.snapshot().baseScore).toBe(BASE_ENEMY_SCORE * 3);
|
||
});
|
||
|
||
it('5 blade contacts award a +1500 combo bonus (req 12.4)', () => {
|
||
const s = new ScoreSystem();
|
||
for (let i = 0; i < 5; i++) s.recordBladeContact();
|
||
const snap = s.snapshot();
|
||
expect(snap.comboBonus).toBe(COMBO_BONUS);
|
||
expect(snap.comboCount).toBe(1);
|
||
expect(snap.consecutiveBladeHits).toBe(0);
|
||
});
|
||
|
||
it('breakBladeChain resets the streak', () => {
|
||
const s = new ScoreSystem();
|
||
s.recordBladeContact();
|
||
s.recordBladeContact();
|
||
s.breakBladeChain();
|
||
s.recordBladeContact();
|
||
expect(s.snapshot().consecutiveBladeHits).toBe(1);
|
||
});
|
||
|
||
it('flawless run triples total score; taking damage removes the bonus (req 12.5)', () => {
|
||
const s = new ScoreSystem();
|
||
s.recordEnemyKill(WeaponType.Shuriken);
|
||
const flawlessSnap = s.snapshot();
|
||
expect(flawlessSnap.flawlessMultiplier).toBe(3);
|
||
s.markTaken();
|
||
const damagedSnap = s.snapshot();
|
||
expect(damagedSnap.flawlessMultiplier).toBe(1);
|
||
});
|
||
|
||
it('remaining time bonus adds 10 pts / sec (req 12.6)', () => {
|
||
const s = new ScoreSystem();
|
||
s.recordEnemyKill(WeaponType.Shuriken);
|
||
s.setRemainingTimeBonus(30);
|
||
expect(s.snapshot().timeBonus).toBe(300);
|
||
});
|
||
});
|