import { test, expect } from 'vitest'; import { chapterId, layoutMap, linkedChapters, parseGraph, ringsFrom } from './xrefMap.js'; const raw = { nodes: [ { id: 'GEN.1', book: 'GEN', name_en: 'Genesis', chapter: 1 }, { id: 'PSA.104', book: 'PSA', name_en: 'Psalms', chapter: 104 }, { id: 'JHN.1', book: 'JHN', name_en: 'John', chapter: 1 }, { id: 'COL.1', book: 'COL', name_en: 'Colossians', chapter: 1 }, { id: 'junk' }, null, ], edges: [ { source: 'GEN.1', target: 'PSA.104', weight: 4 }, { source: 'GEN.1', target: 'JHN.1', weight: 9 }, { source: 'JHN.1', target: 'COL.1', weight: 2 }, { source: 'GEN.1', target: 'NOPE.9', weight: 1 }, // points at a chapter that isn't in the picture { source: 'GEN.1', target: 'GEN.1', weight: 5 }, // a chapter linked to itself ], }; test('the API\'s graph is cleaned: bad nodes, dangling and self links are dropped', () => { const g = parseGraph(raw); expect(g.nodes.map((n) => n.id)).toEqual(['GEN.1', 'PSA.104', 'JHN.1', 'COL.1']); expect(g.edges).toHaveLength(3); expect(parseGraph(null)).toEqual({ nodes: [], edges: [], truncated: false }); expect(chapterId('GEN', 1)).toBe('GEN.1'); }); test('chapters are placed in rings by how many links away they are', () => { const g = parseGraph(raw); const ring = ringsFrom(g.nodes, g.edges, 'GEN.1'); expect([...ring]).toEqual([['GEN.1', 0], ['PSA.104', 1], ['JHN.1', 1], ['COL.1', 2]]); }); test('the layout puts the centre in the middle and the rest around it in Bible order', () => { const g = parseGraph(raw); const map = layoutMap(g, 'GEN.1', 700); const at = Object.fromEntries(map.nodes.map((n) => [n.id, n])); expect(at['GEN.1']).toMatchObject({ x: 350, y: 350, ring: 0 }); expect(at['PSA.104'].ring).toBe(1); expect(at['COL.1'].ring).toBe(2); // ring 1 is Psalms then John, starting at the top and going clockwise expect(at['PSA.104'].y).toBeLessThan(350); expect(at['JHN.1'].y).toBeGreaterThan(at['PSA.104'].y); // every point is inside the square expect(map.nodes.every((n) => n.x >= 0 && n.x <= 700 && n.y >= 0 && n.y <= 700)).toBe(true); // heavier links are drawn heavier, and every line has a path const heavy = map.edges.find((e) => e.weight === 9); const light = map.edges.find((e) => e.weight === 2); expect(heavy.width).toBeGreaterThan(light.width); expect(map.edges.every((e) => e.path.startsWith('M'))).toBe(true); expect(light.path).toContain('Q'); // a link that doesn't touch the middle bows toward it expect(heavy.path).toContain('L'); }); test('the linked chapters are listed strongest first', () => { const g = parseGraph(raw); expect(linkedChapters(g, 'GEN.1').map((c) => [c.id, c.weight])).toEqual([['JHN.1', 9], ['PSA.104', 4]]); }); test('a map with only the centre, or nothing linked, still lays out', () => { const lone = { nodes: [{ id: 'GEN.1', book: 'GEN', name: 'Genesis', chapter: 1 }], edges: [] }; expect(layoutMap(lone, 'GEN.1').nodes).toHaveLength(1); expect(layoutMap({ nodes: [], edges: [] }, 'GEN.1').nodes).toEqual([]); });