import { readFileSync } from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; import { advancementsFileToEvents, statsFileToEvent } from '../src/parsers/vanilla.ts'; const fx = (p: string) => readFileSync(join(import.meta.dirname, 'fixtures', p), 'utf8'); const ALICE = '308a693c-2bd8-4d6b-8790-670aef5be601'; describe('statsFileToEvent', () => { it('stats file maps play_time', () => { const e = statsFileToEvent(ALICE, fx(`stats/${ALICE}.json`), 1760300000000); expect(e.type).toBe('stats_snapshot'); if (e.type !== 'stats_snapshot') return; expect(e.full).toBe(true); expect(e.ts).toBe(1760300000000); expect(e.name).toBeNull(); expect(e.stats['minecraft:custom']?.['minecraft:play_time']).toBe(1175736); expect(e.stats['minecraft:mined']?.['mekanism:enrichment_chamber']).toBe(2); }); it('id stable for same content', () => { const a = statsFileToEvent(ALICE, fx(`stats/${ALICE}.json`), 1); const b = statsFileToEvent(ALICE, fx(`stats/${ALICE}.json`), 1); const c = statsFileToEvent(ALICE, fx(`stats/${ALICE}.json`), 2); expect(a.id).toBe(b.id); expect(a.id).not.toBe(c.id); }); }); describe('advancementsFileToEvents', () => { it('emits done, non-recipe advancements with earliest criterion time', () => { const events = advancementsFileToEvents(ALICE, fx(`advancements/${ALICE}.json`)); const ids = events.map((e) => (e.type === 'advancement' ? e.advancementId : '')); expect(ids.sort()).toEqual(['minecraft:story/enter_the_nether', 'minecraft:story/root']); const nether = events.find((e) => e.type === 'advancement' && e.advancementId === 'minecraft:story/enter_the_nether')!; expect(nether.ts).toBe(Date.parse('2026-10-12T12:42:23+02:00')); }); }); describe('advancement names', () => { it('file-derived advancements carry no name so they never overwrite the player name', () => { const events = advancementsFileToEvents(ALICE, fx(`advancements/${ALICE}.json`)); expect(events.every((e) => e.type === 'advancement' && e.name === null)).toBe(true); }); });