866f1e1bf1
- Combat: relabel HP controls Dmg/Heal, disabled at 0 (were silent no-ops) - Characters: per-card Delete (confirm) + Export; header Import from JSON - Dice: tumbling roll animation that settles on the result (a11y-safe) - Compendium: per-category filters (monster type/CR, spell level/school, item rarity/type) + Clear - Character import/export to portable JSON (reassigns id, validates on import) - New: 4 io unit tests, e2e fixes spec (HP damage, delete, CR filter) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
44 lines
1.8 KiB
TypeScript
44 lines
1.8 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { parseCharacterImport, CharacterImportError } from './character';
|
|
import { characterSchema, characterDefaults, type Character } from '@/lib/schemas';
|
|
|
|
function sampleCharacter(): Character {
|
|
return characterSchema.parse({
|
|
id: 'orig', campaignId: 'orig-camp', system: '5e', kind: 'pc',
|
|
name: 'Imported Hero', ancestry: 'Elf', className: 'Ranger', level: 4,
|
|
abilities: { str: 12, dex: 16, con: 14, int: 10, wis: 13, cha: 8 },
|
|
hp: { current: 30, max: 30, temp: 0 }, speed: 30, armorBonus: 2,
|
|
skillRanks: { stealth: 'expert' }, saveRanks: {}, perceptionRank: 'trained',
|
|
...characterDefaults(),
|
|
notes: 'hi', createdAt: 'x', updatedAt: 'x',
|
|
});
|
|
}
|
|
|
|
describe('parseCharacterImport', () => {
|
|
it('round-trips a wrapped export, reassigning id + campaign', () => {
|
|
const c = sampleCharacter();
|
|
const file = JSON.stringify({ format: 'ttrpg-manager:character', version: 1, character: c });
|
|
const imported = parseCharacterImport(file, 'new-camp');
|
|
expect(imported.name).toBe('Imported Hero');
|
|
expect(imported.campaignId).toBe('new-camp');
|
|
expect(imported.id).not.toBe('orig');
|
|
expect(imported.abilities.dex).toBe(16);
|
|
expect(imported.skillRanks['stealth']).toBe('expert');
|
|
});
|
|
|
|
it('accepts a bare character object too', () => {
|
|
const c = sampleCharacter();
|
|
const imported = parseCharacterImport(JSON.stringify(c), 'camp2');
|
|
expect(imported.name).toBe('Imported Hero');
|
|
expect(imported.campaignId).toBe('camp2');
|
|
});
|
|
|
|
it('rejects invalid JSON', () => {
|
|
expect(() => parseCharacterImport('{not json', 'c')).toThrow(CharacterImportError);
|
|
});
|
|
|
|
it('rejects a file that is not a character', () => {
|
|
expect(() => parseCharacterImport(JSON.stringify({ foo: 1 }), 'c')).toThrow(CharacterImportError);
|
|
});
|
|
});
|