import { describe, expect, it } from 'vitest'; import * as path from 'node:path'; import { fileURLToPath } from 'node:url'; import { loadDefs, collectPackages } from './collect.js'; const fixtureRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '__fixtures__', 'harbor'); const multiRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '__fixtures__', 'vite-build', 'games'); describe('collectPackages', () => { it('collects the harbor package from markdown code blocks', () => { const defMap = loadDefs('', fixtureRoot); const packages = collectPackages(defMap, fixtureRoot); expect(packages).toHaveLength(1); const harbor = packages[0]!; expect(harbor.meta).toMatchObject({ id: 'harbor', title: 'Harbor', designer: 'Jane Doe' }); // Two tokens from two yaml blocks sharing a `file=` name. expect([...harbor.parts.keys()].sort()).toEqual(['token#grain', 'token#wood']); const wood = harbor.parts.get('token#wood')!; expect(wood).toMatchObject({ type: 'token', id: 'wood', size: [20, 20, 3], fillet: 2, }); expect(wood.face).toBe('./assets/tokens.png'); expect(wood.faceCrop).toEqual([1, 0, 5, 2]); // Relative assets resolve against the source file's directory. The fixture // markdown sits at the games root, so the virtual file is `parts/tokens.yaml`. expect(wood.baseUrl).toBe('parts/'); // Two surfaces: the table board and its child player board. expect([...harbor.surfaces.keys()].sort()).toEqual(['board#harbor', 'board#player']); const board = harbor.surfaces.get('board#harbor')!; expect(board.size).toEqual([300, 200]); expect(board.mount).toEqual({ kind: 'table', x: 0, y: 0, rotation: 0 }); expect(board.children).toEqual(['board#player']); expect(board.layout).toHaveLength(2); const dock = board.layout[0]!; expect(dock.route).toBe('/dock/:seat'); expect(dock.candidates).toEqual([ { seat: '0', x: 40, y: 0, rotation: 0 }, { seat: '1', x: 40, y: 20, rotation: 0 }, ]); const deck = board.layout[1]!; expect(deck.route).toBe('/deck'); expect(deck).toMatchObject({ x: -100, y: 0, rotation: 0 }); const player = harbor.surfaces.get('board#player')!; expect(player.mount).toEqual({ kind: 'child', x: 100, y: 50, rotation: 0 }); expect(player.layout).toHaveLength(1); // One setup, declaring the enabled surfaces. expect([...harbor.setups.keys()]).toEqual(['game#main']); const setup = harbor.setups.get('game#main')!; expect(setup.surfaces).toEqual(['board#harbor', 'board#player']); expect(setup.setup).toEqual([ { path: '/dock/0', parts: 'harbor:token#wood' }, { path: '/deck', parts: 'harbor:token#grain' }, ]); }); it('scopes include patterns to the package declaration directory', () => { // Two packages share a games root. Each uses the default `./**/*.yaml` // include, which must resolve relative to its own folder so neither // absorbs the other's defs (both define a `game#main` setup). const defMap = loadDefs('', multiRoot); const packages = collectPackages(defMap, multiRoot); expect(packages).toHaveLength(2); const azul = packages.find((p) => p.meta.id === 'azul')!; const harbor = packages.find((p) => p.meta.id === 'harbor')!; expect([...azul.parts.keys()]).toEqual(['tile#blue']); expect([...azul.setups.keys()]).toEqual(['game#main']); expect([...harbor.parts.keys()]).toEqual(['token#wood']); expect([...harbor.setups.keys()]).toEqual(['game#main']); }); it('throws on a duplicate type#id', () => { const defMap = loadDefs('', fixtureRoot); // Inject a duplicate part into the map under a new file name. const tokensKey = [...defMap.defs.keys()].find((k) => k.endsWith('parts/tokens.yaml'))!; const tokens = defMap.defs.get(tokensKey)!; defMap.defs.set(tokensKey.replace('tokens.yaml', 'dup.yaml'), [tokens[0]!]); expect(() => collectPackages(defMap, fixtureRoot)).toThrow(/Duplicate part/); }); });