feat(modules): a third slot, nav icons, and api.BASE (phase 3, slice 3)
The three things core owes the client half before it can leave, all additive, all MODULE_API 1.2.0 → 1.3.0. `player.invite.accepted` is the third extension slot. Core's invite page owned a UO game-account step — it read a `gameAccountSignup` flag out of core's own settings and posted to a shard route — and an invite is a core concept that staff receive too, so the page stays and its optional next step becomes a slot. Named for the place, like the other two. Whether there is a step at all is the filling module's call, made from data core does not have; core keeps the shell, the skip control and the destination. `icon` on a nav item, because without it the six extracted UO rows would have been the only text-only entries in a sidebar where every other row has a glyph. Core supplies no fallback — an invented one is core making a presentation choice for content it knows nothing about. `icon` was already among the fields an override may not touch, so the concept predates a module being able to send one. `api.BASE` was in §3.5 from the first draft and never actually published. `request` is fetch-only, so an EventSource builds its own URL, and the shard's live feed is two of them; the alternative is a module hardcoding `/api/v1`, which asserts something about core that core has not promised. `AcceptInvite` is the one legitimate reader of `extensionFor` outside Slot.jsx: the answer decides a NAVIGATION, not a decoration. Decoration goes inside `<Slot wrap>`, which is why `hasExtension` stayed deleted. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,36 @@ test('a module row carries its moduleId through, which is how the gate finds it'
|
||||
assert.equal(row.feature, 'atlas')
|
||||
})
|
||||
|
||||
test('a module row carries its icon through, and an override cannot touch it', () => {
|
||||
// 1.3.0. Without an `icon` the six extracted UO rows would have been the only
|
||||
// text-only entries in a sidebar where every other row has a glyph. Core
|
||||
// renders whatever component the row carries and supplies no fallback — an
|
||||
// invented one would be core making a presentation choice for content it knows
|
||||
// nothing about.
|
||||
const Glyph = () => null
|
||||
registerNav('uo', {
|
||||
area: 'admin',
|
||||
items: [{ label: 'Shard', to: '/admin/uo/link', group: 'System', icon: Glyph }],
|
||||
})
|
||||
const merged = withModuleNav(ADMIN, 'admin')
|
||||
const row = merged.find((g) => g.title === 'System').items.find((i) => i.to === '/admin/uo/link')
|
||||
assert.equal(row.icon, Glyph)
|
||||
|
||||
// `icon` was already on navOverrides' list of fields an override may not
|
||||
// touch, from long before a module could supply one. It still is.
|
||||
const overridden = applyNavOverrides(merged, { '/admin/uo/link': { label: 'Renamed', icon: 'nope' } })
|
||||
const after = overridden.find((g) => g.title === 'System').items.find((i) => i.to === '/admin/uo/link')
|
||||
assert.equal(after.label, 'Renamed')
|
||||
assert.equal(after.icon, Glyph)
|
||||
})
|
||||
|
||||
test('a row with no icon simply has none, the same as a core row with none', () => {
|
||||
registerNav('uo', { area: 'player', items: [{ label: 'Characters', to: '/player/uo/characters', order: 0 }] })
|
||||
const row = withModuleNav([{ to: '/account', label: 'Account' }], 'player')[0]
|
||||
assert.equal(row.to, '/player/uo/characters')
|
||||
assert.equal(row.icon, undefined)
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user