import { test, beforeEach } from 'node:test' import assert from 'node:assert/strict' import { withModuleNav } from '../src/modules/nav.js' import { registerNav, _reset } from '../src/modules/registry.js' import { applyNavOverrides, buildPublicNav } from '../src/lib/navOverrides.js' // The interleave of module nav rows into core's nav (MODULE_API.md §3.3, Phase 2 // PR 8). Tested against the real merge next door rather than in isolation, // because the property that matters is a relationship between the two: a module // row has to be indistinguishable from a core row to everything downstream, and // the way to prove that is to run the downstream thing on it. const PUBLIC = [ { label: 'Home', to: '/', end: true }, { label: 'News', to: '/site/news' }, { label: 'About', to: '/site/about' }, ] const ADMIN = [ { items: [{ to: '/admin', label: 'Dashboard', end: true, roles: ['admin', 'moderator'] }] }, { title: 'Moderation', items: [{ to: '/admin/moderation', label: 'Moderation' }] }, { title: 'System', items: [{ to: '/admin/users', label: 'Users' }, { to: '/admin/settings', label: 'Settings' }] }, { items: [{ to: '/admin/account', label: 'Account' }] }, ] beforeEach(() => _reset()) test('with no module installed the base array is returned unchanged', () => { // Identity, not a copy: this is what makes the useMemo in each layout honest, // and what guarantees an instance with no modules renders what it renders now. assert.equal(withModuleNav(PUBLIC, 'public'), PUBLIC) assert.equal(withModuleNav(ADMIN, 'admin'), ADMIN) }) test('a flat nav places a module row by the order it asked for', () => { registerNav('uo', { area: 'public', items: [{ label: 'Atlas', to: '/uo/atlas', order: 1 }] }) assert.deepEqual( withModuleNav(PUBLIC, 'public').map((i) => i.label), ['Home', 'Atlas', 'News', 'About'], ) }) test('a flat row with no order appends rather than jumping to the front', () => { // The 0-default trap: `order ?? 0` would put an unordered row first, which is // the one place a module could take over the nav without asking for anything. registerNav('uo', { area: 'public', items: [{ label: 'Atlas', to: '/uo/atlas' }] }) assert.deepEqual( withModuleNav(PUBLIC, 'public').map((i) => i.label), ['Home', 'News', 'About', 'Atlas'], ) }) test('an explicit order beats a core row that merely sits at that index', () => { registerNav('uo', { area: 'public', items: [{ label: 'Atlas', to: '/uo/atlas', order: 2 }] }) const labels = withModuleNav(PUBLIC, 'public').map((i) => i.label) assert.deepEqual(labels, ['Home', 'News', 'Atlas', 'About']) }) test('an admin row lands INSIDE the core group it names', () => { registerNav('uo', { area: 'admin', items: [ { label: 'In-Game Ops', to: '/admin/uo/shard-ops', group: 'Moderation', order: 30 }, { label: 'Shard', to: '/admin/uo/link', group: 'System', order: 0 }, ], }) const nav = withModuleNav(ADMIN, 'admin') assert.deepEqual(nav.map((g) => g.title), [undefined, 'Moderation', 'System', undefined]) assert.deepEqual(nav[1].items.map((i) => i.label), ['Moderation', 'In-Game Ops']) // order 0 puts it above both core rows, which is the whole point of the field. assert.deepEqual(nav[2].items.map((i) => i.label), ['Shard', 'Users', 'Settings']) }) test('an unknown group appends a new group instead of dropping the row', () => { // A typo must cost a position, never a link. registerNav('uo', { area: 'admin', items: [{ label: 'Atlas', to: '/admin/uo/atlas', group: 'Moderaton' }] }) const nav = withModuleNav(ADMIN, 'admin') assert.equal(nav.length, ADMIN.length + 1) assert.deepEqual(nav.at(-1), { title: 'Moderaton', items: [{ label: 'Atlas', to: '/admin/uo/atlas', group: 'Moderaton', moduleId: 'uo' }] }) }) test('an admin row with no group gets a trailing untitled group of its own', () => { // NOT folded into one of core's untitled groups: those are Dashboard at the // top and Account at the bottom, and a module page belongs beside neither. registerNav('uo', { area: 'admin', items: [{ label: 'Atlas', to: '/admin/uo/atlas' }] }) const nav = withModuleNav(ADMIN, 'admin') assert.equal(nav.length, ADMIN.length + 1) assert.equal(nav.at(-1).title, undefined) assert.deepEqual(nav.at(-1).items.map((i) => i.label), ['Atlas']) assert.deepEqual(nav[0].items.map((i) => i.label), ['Dashboard']) assert.deepEqual(nav[3].items.map((i) => i.label), ['Account']) }) test('a row whose `to` collides with a core row is dropped, not rendered twice', () => { // `to` is the key the override layer stores under and React renders by. Two // rows sharing one would give an admin a single editor row that moves both. const warnings = [] const warn = console.warn console.warn = (msg) => warnings.push(msg) try { registerNav('uo', { area: 'public', items: [{ label: 'Not News', to: '/site/news' }, { label: 'Atlas', to: '/uo/atlas' }], }) const nav = withModuleNav(PUBLIC, 'public') assert.deepEqual(nav.map((i) => i.label), ['Home', 'News', 'About', 'Atlas']) assert.equal(warnings.length, 1) assert.match(warnings[0], /\/site\/news.*collides/) } finally { console.warn = warn } }) test('two modules cannot claim the same path either', () => { const warn = console.warn console.warn = () => {} try { registerNav('aa', { area: 'public', items: [{ label: 'First', to: '/shared' }] }) registerNav('zz', { area: 'public', items: [{ label: 'Second', to: '/shared' }] }) const labels = withModuleNav(PUBLIC, 'public').map((i) => i.label) assert.deepEqual(labels, ['Home', 'News', 'About', 'First']) } finally { console.warn = warn } }) test('a module row carries its moduleId through, which is how the gate finds it', () => { registerNav('uo', { area: 'public', items: [{ label: 'Atlas', to: '/uo/atlas', feature: 'atlas' }] }) const row = withModuleNav(PUBLIC, 'public').at(-1) assert.equal(row.moduleId, 'uo') assert.equal(row.feature, 'atlas') }) test('areas do not leak into one another', () => { registerNav('uo', { area: 'admin', items: [{ label: 'Shard', to: '/admin/uo/link', group: 'System' }] }) assert.equal(withModuleNav(PUBLIC, 'public'), PUBLIC) }) // ── The relationship that is the actual requirement ─────────────────────── test('an admin override applies to a module row exactly as to a core row', () => { // The reason the interleave happens BEFORE the merge and not after: the merge // drops any key its base array does not declare, so appending module rows // afterwards would make every one of them unorderable, unrelabellable and // unhideable — a visible regression the day the UO rows leave core. registerNav('uo', { area: 'public', items: [{ label: 'Atlas', to: '/uo/atlas' }] }) const base = withModuleNav(PUBLIC, 'public') const merged = applyNavOverrides(base, { '/uo/atlas': { label: 'Bestiary', order: 0 }, '/site/news': { order: 3 }, }) assert.deepEqual(merged.map((i) => i.label), ['Bestiary', 'Home', 'About', 'News']) }) test('an override can hide a module row, and the public tree can section it', () => { registerNav('uo', { area: 'public', items: [{ label: 'Atlas', to: '/uo/atlas' }, { label: 'Market', to: '/uo/market' }] }) const base = withModuleNav(PUBLIC, 'public') const hidden = buildPublicNav(base, { '/uo/atlas': { hidden: true } }) assert.equal(hidden.some((n) => n.to === '/uo/atlas'), false) const sectioned = buildPublicNav(base, { items: { '/uo/market': { section: 'sec_shard' } }, sections: [{ id: 'sec_shard', label: 'Shard', order: 0 }], }) assert.equal(sectioned[0].kind, 'section') assert.deepEqual(sectioned[0].items.map((i) => i.to), ['/uo/market']) }) test('a module row can be moved between admin groups by an override', () => { registerNav('uo', { area: 'admin', items: [{ label: 'Shard', to: '/admin/uo/link', group: 'System' }] }) const base = withModuleNav(ADMIN, 'admin') const merged = applyNavOverrides(base, { '/admin/uo/link': { group: 'Moderation' } }) assert.deepEqual(merged[1].items.map((i) => i.to), ['/admin/moderation', '/admin/uo/link']) assert.deepEqual(merged[2].items.map((i) => i.to), ['/admin/users', '/admin/settings']) }) test('a group a module created is itself a legal override destination', () => { // Falls out of building the destination set from the base nav it is handed — // recorded because it is the kind of thing that would otherwise be discovered // by an admin finding a section they cannot move anything into. registerNav('uo', { area: 'admin', items: [{ label: 'Atlas', to: '/admin/uo/atlas', group: 'Shard' }] }) const base = withModuleNav(ADMIN, 'admin') const merged = applyNavOverrides(base, { '/admin/users': { group: 'Shard' } }) assert.deepEqual(merged.at(-1).items.map((i) => i.to), ['/admin/uo/atlas', '/admin/users']) })