dex/test/pokedex-resolver.test.js
chris 3a54cdcc53 Add a Vitest unit suite for the pure-logic modules
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
2026-09-10 11:33:27 -04:00

81 lines
2.4 KiB
JavaScript
Raw Permalink 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 { describe, it, expect } from 'vitest';
import {
prettify,
dexesForVersionGroup,
resolvePokedex,
dexRows,
} from '../src/data/pokedex-resolver.js';
describe('prettify', () => {
it('title-cases a hyphenated key', () => {
expect(prettify('heartgold-soulsilver')).toBe('Heartgold Soulsilver');
expect(prettify('black-2-white-2')).toBe('Black 2 White 2');
expect(prettify('national')).toBe('National');
});
});
// A hand-built mini snapshot with the Map indexes loadSnapshot() would add.
function miniSnap() {
const pokedexes = [
{
key: 'national',
name: 'National',
entries: [
[1, 1],
[4, 4],
[999, 7],
],
},
{
key: 'kanto',
name: 'Kanto',
entries: [
[4, 1],
[1, 2],
],
},
];
const versionGroups = [
{ key: 'red-blue', name: 'Red Blue', generation: 1, pokedexKeys: ['kanto'] },
{ key: 'x-y', name: 'X Y', generation: 6, pokedexKeys: [] },
];
return {
pokedexByKey: new Map(pokedexes.map((d) => [d.key, d])),
versionGroupByKey: new Map(versionGroups.map((v) => [v.key, v])),
speciesById: new Map([
[1, { id: 1, name: 'bulbasaur' }],
[4, { id: 4, name: 'charmander' }],
]),
};
}
describe('dexesForVersionGroup', () => {
it('returns the groups regional dexes', () => {
expect(dexesForVersionGroup(miniSnap(), 'red-blue').map((d) => d.key)).toEqual(['kanto']);
});
it('falls back to National when a group lists no dex', () => {
expect(dexesForVersionGroup(miniSnap(), 'x-y').map((d) => d.key)).toEqual(['national']);
});
});
describe('resolvePokedex', () => {
it('honours settings.pokedex when it belongs to the game', () => {
const snap = miniSnap();
// one-dex game: always that dex
expect(resolvePokedex(snap, { versionGroup: 'red-blue', pokedex: 'kanto' }).key).toBe('kanto');
// stale pokedex from another game -> that game's first dex
expect(resolvePokedex(snap, { versionGroup: 'red-blue', pokedex: 'hoenn' }).key).toBe('kanto');
});
});
describe('dexRows', () => {
it('expands entries and drops species missing from the snapshot', () => {
const snap = miniSnap();
const rows = dexRows(snap, snap.pokedexByKey.get('national'));
expect(rows).toEqual([
{ species: { id: 1, name: 'bulbasaur' }, number: 1 },
{ species: { id: 4, name: 'charmander' }, number: 4 },
]);
});
});