diff --git a/client/src/entry.jsx b/client/src/entry.jsx index 1c10dc5..ef9177d 100644 --- a/client/src/entry.jsx +++ b/client/src/entry.jsx @@ -194,6 +194,14 @@ registry.registerExtension(ID, 'player.invite.accepted', InviteGameAccountStep) // enough; on a core that knows nothing of Teams it simply stays empty. registry.declareModuleSlot(ID, 'uo.guild.detail') +// A SECOND place on the same page, for core's Team forum (TEAMS.md Part 5). Two +// declarations rather than one, because a slot holds one component and this module +// wants to decide where each of core's two contributions sits on its own page — +// the feed reads as part of the guild's story, the forum is a room you go into. +// Neither knows the other exists, and a core that fills only one leaves the other +// empty. +registry.declareModuleSlot(ID, 'uo.guild.forum') + // `module.json`'s `coreApi` range is checked by the loader before this file is // ever served, so there is nothing to re-check here. It is logged because a // mismatch between the core that validated the manifest and the core that diff --git a/client/src/routes/public/Guild.jsx b/client/src/routes/public/Guild.jsx index 91ce62c..945cc21 100644 --- a/client/src/routes/public/Guild.jsx +++ b/client/src/routes/public/Guild.jsx @@ -102,6 +102,11 @@ export default function Guild() { that does not fill it, or when there is nothing to show. The guild is named in OUR terms — core maps its own Team from these two. */} + + {/* And the Team forum, in its own place below the feed. Core resolves + who may read it — membership and manual grants are core's rules — + so this module renders the room and never its door policy. */} + )} diff --git a/client/test/registration.test.js b/client/test/registration.test.js index f3a124c..5378bea 100644 --- a/client/test/registration.test.js +++ b/client/test/registration.test.js @@ -209,16 +209,22 @@ it('registers under exactly one module id, matching the manifest', () => { assert.deepEqual([...owners], [manifest.id]) }) -it('declares its own guild-detail slot, for core to fill', () => { +it('declares its own guild slots, for core to fill', () => { // The inverted direction (TEAMS.md Part 3). Teams are a core primitive with no - // core page: core owns the activity feed and this module owns the word "guild", - // so this module declares the place and core puts the feed in it. - assert.deepEqual([...registered.declaredSlots], ['uo.guild.detail']) + // 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. + // + // TWO slots rather than one because a slot holds one component: stacking the + // feed and the forum into a single fill would take away this module's ability + // to place them separately on its own page. + assert.deepEqual([...registered.declaredSlots], ['uo.guild.detail', 'uo.guild.forum']) }) -it('the declared slot is rendered by the page that owns it', () => { +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') - assert.match(page, /name="uo\.guild\.detail"/) + for (const name of registered.declaredSlots) { + assert.match(page, new RegExp(`name="${name.replace(/\./g, '\.')}"`)) + } })