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

115 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});