Build and Push Docker Image / build-and-push (push) Successful in 10m22s
- Reader: chapter cross-reference map (M) from IQ Bible's reference graph - Admin: Health tab with disk use, snapshots (back up, download, restore), failure logs - Passkey sign-in (WebAuthn), signed-in device list, sign out others, optional sign-in alerts - Settings split into Account, Preferences, Notifications, Security, Your data tabs - ntfy server field: catch a topic typed into the server box Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
66 lines
3.0 KiB
JavaScript
66 lines
3.0 KiB
JavaScript
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([]);
|
|
});
|