31 tests in test/, run in a plain Node env with a tiny localStorage shim (no jsdom — its Node-22.4 transitive-dep breakage isn't worth working around for logic tests): - type-chart: modern effectiveness, dual-type stacking, and the era rules (no Fairy pre-6, Steel resists Ghost/Dark pre-6, the Gen 1 Ghost→Psychic and Bug↔Poison quirks). - damage-calc: nature multipliers, the Gen 3+ stat formula (Garchomp reference values), STAB/effectiveness/immunity, pre/post-Gen-6 crit. - savedex: a synthetic Gen 1 SRAM (bitfields + checksum) round-trips; checksum-mismatch and size guards. - selection: normalizeSelection v2→v3 migration, per-game isolation, global favourite, stats vs statsNational. - pokedex-resolver: prettify, dex resolution against a mini snapshot. `npm test` wired into CI after format:check. Also: docker-compose host port 8080 → 4753 (README updated). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Ve7HLspzeG2xDPtJQ8vmu
62 lines
2.0 KiB
JavaScript
62 lines
2.0 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { parseSaveDex } from '../src/lib/savedex.js';
|
|
|
|
// --- Synthetic Gen 1 (R/B/Y) SRAM -------------------------------------
|
|
// 32 KB, with the owned/seen bitfields and the byte checksum the parser
|
|
// verifies. Offsets mirror src/lib/savedex.js `G1`.
|
|
const G1 = {
|
|
owned: 0x25a3,
|
|
seen: 0x25b6,
|
|
species: 151,
|
|
ckByte: 0x3523,
|
|
ckFrom: 0x2598,
|
|
ckTo: 0x3522,
|
|
};
|
|
|
|
function setBits(bytes, start, ids) {
|
|
for (const n of ids) bytes[start + ((n - 1) >> 3)] |= 1 << ((n - 1) & 7);
|
|
}
|
|
|
|
function makeGen1Save({ caught, seen }) {
|
|
const bytes = new Uint8Array(0x8000);
|
|
setBits(bytes, G1.owned, caught);
|
|
setBits(bytes, G1.seen, seen);
|
|
let sum = 0;
|
|
for (let i = G1.ckFrom; i <= G1.ckTo; i++) sum = (sum + bytes[i]) >>> 0;
|
|
bytes[G1.ckByte] = ~sum & 0xff;
|
|
return bytes.buffer;
|
|
}
|
|
|
|
describe('parseSaveDex — Gen 1', () => {
|
|
it('reads owned/seen and verifies the checksum', () => {
|
|
const caught = [1, 25, 151];
|
|
const seen = [1, 4, 25, 151];
|
|
const dex = parseSaveDex(makeGen1Save({ caught, seen }));
|
|
|
|
expect(dex.gen).toBe(1);
|
|
expect(dex.game).toBe('Red / Blue / Yellow');
|
|
expect(dex.versionGroups).toEqual(['red-blue', 'yellow']);
|
|
expect(dex.caught).toEqual(caught);
|
|
expect(dex.seen).toEqual(seen);
|
|
expect(dex.checksumOk).toBe(true);
|
|
expect(dex.nationalUnlocked).toBe(true);
|
|
});
|
|
|
|
it('still parses (checksumOk false) when the checksum byte is wrong', () => {
|
|
const buf = makeGen1Save({ caught: [1], seen: [1, 2] });
|
|
new Uint8Array(buf)[G1.ckByte] ^= 0xff;
|
|
const dex = parseSaveDex(buf);
|
|
expect(dex.gen).toBe(1);
|
|
expect(dex.caught).toEqual([1]);
|
|
expect(dex.checksumOk).toBe(false);
|
|
});
|
|
|
|
it('rejects a file that is too small', () => {
|
|
expect(() => parseSaveDex(new Uint8Array(1024).buffer)).toThrow(/too small/i);
|
|
});
|
|
|
|
it('rejects a large file with no recognisable Pokédex', () => {
|
|
expect(() => parseSaveDex(new Uint8Array(0x20000).buffer)).toThrow();
|
|
});
|
|
});
|