|
|
|
|
@@ -39,12 +39,18 @@ const CHUNK = path.resolve(HERE, '..', 'dist', 'entry.js')
|
|
|
|
|
// nothing here renders, so a named stub is enough to be imported and passed on.
|
|
|
|
|
const stub = (name) => Object.assign(() => null, { displayName: name })
|
|
|
|
|
|
|
|
|
|
// Core's contribution catalogue, as of MODULE_API 1.6.0. Written down rather than
|
|
|
|
|
// imported — this suite runs against the BUILT chunk with no core in the process
|
|
|
|
|
// — which means it is a claim about core that has to be re-read when core's list
|
|
|
|
|
// changes. That is the same trade the rest of this fake makes.
|
|
|
|
|
const CORE_CONTRIBUTIONS = ['team.activity', 'team.forum', 'team.notify']
|
|
|
|
|
|
|
|
|
|
function fakeRg() {
|
|
|
|
|
const routes = { public: [], admin: [], player: [] }
|
|
|
|
|
const nav = { public: [], admin: [], player: [] }
|
|
|
|
|
const providers = new Map()
|
|
|
|
|
const extensions = new Map()
|
|
|
|
|
const declaredSlots = new Set()
|
|
|
|
|
const declaredSlots = new Map()
|
|
|
|
|
return {
|
|
|
|
|
version: '1.3.0',
|
|
|
|
|
react,
|
|
|
|
|
@@ -74,13 +80,18 @@ function fakeRg() {
|
|
|
|
|
extensions.set(slot, { id, Component })
|
|
|
|
|
},
|
|
|
|
|
// The INVERTED direction (core API 1.6.0): this module declares a place on
|
|
|
|
|
// its OWN page and core fills it. Core enforces the namespace, so the fake
|
|
|
|
|
// does too — a chunk that declared an unnamespaced slot would pass here and
|
|
|
|
|
// throw in a browser.
|
|
|
|
|
declareModuleSlot(id, name) {
|
|
|
|
|
// its OWN page and core fills it. Core enforces the namespace and the
|
|
|
|
|
// contribution name, so the fake does too — a chunk that declared an
|
|
|
|
|
// unnamespaced slot, or asked for a contribution core does not offer, would
|
|
|
|
|
// pass here and throw in a browser.
|
|
|
|
|
declareModuleSlot(id, name, options = {}) {
|
|
|
|
|
if (!name.startsWith(`${id}.`)) throw new Error(`declareModuleSlot: "${name}" must be namespaced "${id}."`)
|
|
|
|
|
if (declaredSlots.has(name)) throw new Error(`extension slot "${name}" already declared`)
|
|
|
|
|
declaredSlots.add(name)
|
|
|
|
|
const wants = options.core ?? null
|
|
|
|
|
if (wants !== null && !CORE_CONTRIBUTIONS.includes(wants)) {
|
|
|
|
|
throw new Error(`declareModuleSlot: "${name}" asks for core contribution "${wants}", which core does not offer`)
|
|
|
|
|
}
|
|
|
|
|
declaredSlots.set(name, wants)
|
|
|
|
|
},
|
|
|
|
|
routesFor: (area) => routes[area],
|
|
|
|
|
navFor: (area) => nav[area],
|
|
|
|
|
@@ -209,7 +220,7 @@ it('registers under exactly one module id, matching the manifest', () => {
|
|
|
|
|
assert.deepEqual([...owners], [manifest.id])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('declares its own guild slots, for core to fill', () => {
|
|
|
|
|
it('declares its own guild slots, each naming the core contribution it wants', () => {
|
|
|
|
|
// The inverted direction (TEAMS.md Part 3). Teams are a core primitive with no
|
|
|
|
|
// core page: core owns the activity feed and the forum, this module owns the
|
|
|
|
|
// word "guild", so this module declares the places and core puts them in.
|
|
|
|
|
@@ -219,10 +230,15 @@ it('declares its own guild slots, for core to fill', () => {
|
|
|
|
|
// away this module's ability to place them separately on its own page — and it
|
|
|
|
|
// does place them separately, the control above the roster and the other two
|
|
|
|
|
// below it.
|
|
|
|
|
assert.deepEqual([...registered.declaredSlots], [
|
|
|
|
|
'uo.guild.detail',
|
|
|
|
|
'uo.guild.forum',
|
|
|
|
|
'uo.guild.header',
|
|
|
|
|
//
|
|
|
|
|
// The second argument is what actually gets core's content here. **Core offers
|
|
|
|
|
// a contribution and never names a slot** — the first cut of this reached only
|
|
|
|
|
// this module, because core filled the literal name `uo.guild.detail` and any
|
|
|
|
|
// other game's page went empty with no error.
|
|
|
|
|
assert.deepEqual([...registered.declaredSlots.entries()], [
|
|
|
|
|
['uo.guild.detail', 'team.activity'],
|
|
|
|
|
['uo.guild.forum', 'team.forum'],
|
|
|
|
|
['uo.guild.header', 'team.notify'],
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@@ -230,7 +246,7 @@ it('every declared slot is rendered by the page that owns it', () => {
|
|
|
|
|
// A slot nothing renders is a slot core fills into the void. Asserted against
|
|
|
|
|
// the source rather than the chunk, since the chunk is minified.
|
|
|
|
|
const page = fs.readFileSync(path.resolve(HERE, '..', 'src', 'routes', 'public', 'Guild.jsx'), 'utf8')
|
|
|
|
|
for (const name of registered.declaredSlots) {
|
|
|
|
|
for (const name of registered.declaredSlots.keys()) {
|
|
|
|
|
assert.match(page, new RegExp(`name="${name.replace(/\./g, '\.')}"`))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|