@bongos/core 1.21.56 → 1.21.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +47 -22
- package/docs/copy-inventory.md +57 -19
- package/docs/copy-registry.json +450 -83
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +32 -4
- package/docs/page-readings.json +69 -1
- package/modules/public-landing/public/guild.html +571 -0
- package/modules/public-landing/public/guild.probes.json +53 -0
- package/modules/public-landing/public/guild.states.json +38 -0
- package/modules/ui-design/kit/fixtures/guilds.json +73 -0
- package/modules/ui-design/kit/serve.js +46 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/src/platform-server.js +13 -0
- package/tests/hub_guild_page.mjs +537 -0
- package/tests/privacy_matrix.mjs +126 -14
- package/tests/render_check.mjs +2 -2
- package/tests/ui_design_kit.mjs +3 -3
package/tests/privacy_matrix.mjs
CHANGED
|
@@ -153,14 +153,21 @@ function identitySentinels(acct) {
|
|
|
153
153
|
// directory GET /scouting Recruiter — 'full' | 'name-only' | 'absent'
|
|
154
154
|
// sliver GET /scouting/:handle Recruiter — 'full' (D3) | 'name-only' (D3, hidden) | 404
|
|
155
155
|
// self GET /me/cross-project Self — 'own' (own numbers, any flags) | 'n/a' (no session exists)
|
|
156
|
+
// guild GET /guilds/:slug Anon · Builder · Recruiter · Self — the guild page's PUBLIC
|
|
157
|
+
// roster (task 1002301; ADR 0336 D4), viewer-independent: 'listed' | 'absent'.
|
|
158
|
+
// Every fixture account is a member with shown_publicly ON, so the account
|
|
159
|
+
// predicates alone decide; hide_stats never hides membership (D4 §2)
|
|
160
|
+
// members GET /guilds/:slug/members a fellow Member — the own-scope roster: 'listed' |
|
|
161
|
+
// 'absent'. Joining is consent to be seen BY THE GUILD (D4 §1), so a private
|
|
162
|
+
// member is listed here and nowhere public; a provisional account is never
|
|
156
163
|
const MATRIX = {
|
|
157
|
-
PROV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'n/a' },
|
|
158
|
-
PUB: { profile: 'full', search: 'listed', board: 'listed', directory: 'full', sliver: 'full', self: 'own' },
|
|
159
|
-
SHY: { profile: 'identity', search: 'name-only', board: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own' },
|
|
160
|
-
PRIV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'own' },
|
|
161
|
-
PRIVD: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'full', self: 'own' },
|
|
162
|
-
PRIVDH: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'name-only', self: 'own' },
|
|
163
|
-
OPTOUT: { profile: 'full', search: 'listed', board: 'listed', directory: 'absent', sliver: 404, self: 'own' },
|
|
164
|
+
PROV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'n/a', guild: 'absent', members: 'absent' },
|
|
165
|
+
PUB: { profile: 'full', search: 'listed', board: 'listed', directory: 'full', sliver: 'full', self: 'own', guild: 'listed', members: 'listed' },
|
|
166
|
+
SHY: { profile: 'identity', search: 'name-only', board: 'absent', directory: 'name-only', sliver: 'name-only', self: 'own', guild: 'listed', members: 'listed' },
|
|
167
|
+
PRIV: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 404, self: 'own', guild: 'absent', members: 'listed' },
|
|
168
|
+
PRIVD: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'full', self: 'own', guild: 'absent', members: 'listed' },
|
|
169
|
+
PRIVDH: { profile: 404, search: 'absent', board: 'absent', directory: 'absent', sliver: 'name-only', self: 'own', guild: 'absent', members: 'listed' },
|
|
170
|
+
OPTOUT: { profile: 'full', search: 'listed', board: 'listed', directory: 'absent', sliver: 404, self: 'own', guild: 'listed', members: 'listed' },
|
|
164
171
|
};
|
|
165
172
|
|
|
166
173
|
// ===========================================================================
|
|
@@ -243,10 +250,59 @@ function activityScope(sql, params) {
|
|
|
243
250
|
return String(params[Number(m[1]) - 1]);
|
|
244
251
|
}
|
|
245
252
|
|
|
253
|
+
// THE GUILDS (task 1002301): one public and one unlisted guild, each with every
|
|
254
|
+
// fixture account as a member and shown_publicly ON — the most permissive guild
|
|
255
|
+
// flag, so the account predicates alone decide each cell. PUB owns both.
|
|
256
|
+
const GUILDS = [
|
|
257
|
+
{ id: '880001', slug: 'mx-guild', name: 'Matrix Guild', description: 'mx-guild-desc', visibility: 'public', created_at: 'T0' },
|
|
258
|
+
{ id: '880002', slug: 'mx-unlisted', name: 'Matrix Unlisted', description: 'mx-unlisted-desc', visibility: 'unlisted', created_at: 'T0' },
|
|
259
|
+
];
|
|
260
|
+
const GUILD_MEMBERS = GUILDS.flatMap((g) => ACCOUNTS.map((a) => ({
|
|
261
|
+
guild_id: g.id, github_id: a.github_id, membership_kind: a.key === 'PUB' ? 'owner' : 'member', shown_publicly: true, counted_in_totals: true,
|
|
262
|
+
})));
|
|
263
|
+
/** The members-of-a-guild statements: the guild and shown terms consumed here, the rest read as an account filter. */
|
|
264
|
+
function guildRosterFilter(sql, params) {
|
|
265
|
+
let where = lift(sql, /JOIN platform_identity_accounts pa ON pa\.github_id = m\.github_id WHERE (.+?) ORDER BY/, 'guild roster');
|
|
266
|
+
if (!/\bm\.guild_id = \$1\b/.test(where)) throw new Error(`fake pool: a guild roster with no guild scope — ${where}`);
|
|
267
|
+
where = where.replace(/\bm\.guild_id = \$1\b/, '');
|
|
268
|
+
const shownOnly = /\bm\.shown_publicly\b/.test(where);
|
|
269
|
+
where = where.replace(/\bm\.shown_publicly\b/, '');
|
|
270
|
+
const byAccount = accountFilter(where, []);
|
|
271
|
+
return (m) => m.guild_id === String(params[0]) && (!shownOnly || m.shown_publicly) && byAccount(ACCOUNTS.find((a) => a.github_id === m.github_id));
|
|
272
|
+
}
|
|
273
|
+
|
|
246
274
|
const pool = {
|
|
247
275
|
query: async (text, params = []) => {
|
|
248
276
|
const sql = String(text).replace(/\s+/g, ' ').trim();
|
|
249
277
|
|
|
278
|
+
// THE GUILD PAGE's reads. The public guild: its WHERE is read, not assumed.
|
|
279
|
+
if (/^SELECT id, slug, name, description, created_at FROM platform_identity_guilds WHERE /.test(sql)) {
|
|
280
|
+
if (!/WHERE lower\(slug\) = \$1 AND visibility = 'public'$/.test(sql)) throw new Error(`fake pool: the public guild read moved — ${sql}`);
|
|
281
|
+
const rows = GUILDS.filter((g) => g.slug === String(params[0]) && g.visibility === 'public').map(({ visibility, ...g }) => g);
|
|
282
|
+
return { rows, rowCount: rows.length };
|
|
283
|
+
}
|
|
284
|
+
// ... a member's own row in a guild (the own-scope door).
|
|
285
|
+
if (/FROM platform_identity_guilds g JOIN platform_identity_guild_members m ON m\.guild_id = g\.id AND m\.github_id = \$2::bigint WHERE lower\(g\.slug\) = \$1$/.test(sql)) {
|
|
286
|
+
const g = GUILDS.find((x) => x.slug === String(params[0]));
|
|
287
|
+
const m = g && GUILD_MEMBERS.find((x) => x.guild_id === g.id && x.github_id === String(params[1]));
|
|
288
|
+
const rows = m ? [{ ...g, ...m, joined_at: 'T2' }] : [];
|
|
289
|
+
return { rows, rowCount: rows.length };
|
|
290
|
+
}
|
|
291
|
+
// ... both rosters: public (shown AND visible) and members' (active).
|
|
292
|
+
if (/FROM platform_identity_guild_members m JOIN platform_identity_accounts pa/.test(sql)) {
|
|
293
|
+
// the projection is pinned: a new column here is a new disclosure, and this fails first
|
|
294
|
+
const cols = (/^SELECT (.+?) FROM /.exec(sql) || [, ''])[1];
|
|
295
|
+
if (cols !== 'pa.handle, pa.display_name, pa.avatar_url, m.membership_kind') throw new Error(`fake pool: the guild roster selects something new — ${cols}`);
|
|
296
|
+
const keep = guildRosterFilter(sql, params);
|
|
297
|
+
const rows = GUILD_MEMBERS.filter(keep).map((m) => {
|
|
298
|
+
const a = ACCOUNTS.find((x) => x.github_id === m.github_id);
|
|
299
|
+
return { handle: a.handle, display_name: a.display_name, avatar_url: a.avatar_url, membership_kind: m.membership_kind };
|
|
300
|
+
});
|
|
301
|
+
return { rows, rowCount: rows.length };
|
|
302
|
+
}
|
|
303
|
+
// ... and the weekly totals: never computed on a read (guild-totals.js); none stored here.
|
|
304
|
+
if (/FROM platform_identity_guild_totals WHERE guild_id = \$1$/.test(sql)) return { rows: [], rowCount: 0 };
|
|
305
|
+
|
|
250
306
|
// THE LEADERBOARD's totals: the rollup floor sits on the act CTE's join.
|
|
251
307
|
if (/platform_identity_activity_snapshots/.test(sql)) {
|
|
252
308
|
const where = lift(sql, /JOIN platform_identity_accounts pa ON pa\.github_id = a\.github_id WHERE (.+?) \), per_project AS/, 'leaderboard totals');
|
|
@@ -346,6 +402,8 @@ const ANON = null;
|
|
|
346
402
|
const STRANGER = { name: 'Builder', github_id: '7100001', rank: 'xenos', curate: false };
|
|
347
403
|
const RECRUITER = { name: 'Recruiter', github_id: '7100002', rank: 'archon', curate: true };
|
|
348
404
|
const selfOf = (k) => ({ name: 'Self', github_id: ACCT[k].github_id, rank: 'xenos', curate: false });
|
|
405
|
+
// a fellow guild member: PUB's session, which owns both fixture guilds
|
|
406
|
+
const MEMBER = { name: 'Member', github_id: ACCT.PUB.github_id, rank: 'xenos', curate: false };
|
|
349
407
|
|
|
350
408
|
const moduleApiPath = require.resolve('../src/module-api.js');
|
|
351
409
|
const helpers = require('../src/bongos/routes/_helpers.js');
|
|
@@ -413,7 +471,7 @@ const { attachFail } = require('../src/bongos/middleware/error-envelope.js');
|
|
|
413
471
|
const app = express();
|
|
414
472
|
app.use(express.json());
|
|
415
473
|
app.use(attachFail);
|
|
416
|
-
for (const file of ['public-profile', 'profile', 'community-search', 'community-leaderboard', 'scouting']) {
|
|
474
|
+
for (const file of ['public-profile', 'profile', 'community-search', 'community-leaderboard', 'scouting', 'guilds-public', 'guilds']) {
|
|
417
475
|
app.use('/api/bongos', require(`../modules/platform-identity/routes/${file}.js`)());
|
|
418
476
|
}
|
|
419
477
|
const server = app.listen(0, '127.0.0.1');
|
|
@@ -535,6 +593,52 @@ for (const k of KEYS) {
|
|
|
535
593
|
});
|
|
536
594
|
}
|
|
537
595
|
|
|
596
|
+
// The guild page's two rosters (task 1002301; ADR 0336 D4). One read per viewer
|
|
597
|
+
// serves every row, so each row's cell is checked against the same bodies.
|
|
598
|
+
const GUILD_PATH = '/api/bongos/guilds/mx-guild';
|
|
599
|
+
const ROSTER_KEYS = ['avatar_url', 'display_name', 'handle', 'membership_kind'];
|
|
600
|
+
const ALL_VIEWERS = () => [ANON, STRANGER, RECRUITER, MEMBER, ...KEYS.map(selfOf)];
|
|
601
|
+
|
|
602
|
+
test('the guild page’s public roster — the same bytes to Anon, Builder, Recruiter, a Member and every Self; listed or absent per the matrix', async () => {
|
|
603
|
+
const seen = [];
|
|
604
|
+
for (const who of ALL_VIEWERS()) seen.push(await get(who, GUILD_PATH));
|
|
605
|
+
for (const r of seen.slice(1)) {
|
|
606
|
+
assert.equal(r.status, seen[0].status, `${r.who} got a different status — the public guild read must not vary by viewer (ADR 0336 D4)`);
|
|
607
|
+
assert.equal(r.text, seen[0].text, `${r.who} got a different body — the public guild read must not vary by viewer (ADR 0336 D4)`);
|
|
608
|
+
}
|
|
609
|
+
const [r] = seen;
|
|
610
|
+
assert.equal(r.status, 200);
|
|
611
|
+
const listed = new Set(r.json.members.map((m) => m.handle));
|
|
612
|
+
for (const k of KEYS) assert.equal(listed.has(ACCT[k].handle), MATRIX[k].guild === 'listed', `${k} on the public roster: expected ${MATRIX[k].guild}`);
|
|
613
|
+
assert.equal(r.json.size, r.json.members.length, 'size is the roster’s own length — never a "+N more" (ADR 0336 D4)');
|
|
614
|
+
for (const m of r.json.members) assert.deepEqual(Object.keys(m).sort(), ROSTER_KEYS, 'handle, name, avatar, kind — never a GitHub id or login');
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
test('the guild members’ roster — a fellow member sees every active member, private ones included; Anon is refused', async () => {
|
|
618
|
+
const r = await get(MEMBER, `${GUILD_PATH}/members`);
|
|
619
|
+
assert.equal(r.status, 200);
|
|
620
|
+
const listed = new Set(r.json.members.map((m) => m.handle));
|
|
621
|
+
for (const k of KEYS) assert.equal(listed.has(ACCT[k].handle), MATRIX[k].members === 'listed', `${k} on the members’ roster: expected ${MATRIX[k].members}`);
|
|
622
|
+
assert.equal(r.json.count, r.json.members.length);
|
|
623
|
+
for (const m of r.json.members) assert.deepEqual(Object.keys(m).sort(), ROSTER_KEYS);
|
|
624
|
+
assert.deepEqual(Object.keys(r.json.you).sort(), ['counted_in_totals', 'membership_kind', 'shown_publicly'], 'the reader’s own two switches, and nobody else’s');
|
|
625
|
+
assert.equal((await get(ANON, `${GUILD_PATH}/members`)).status, 401);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
test('an unlisted guild’s public read is a 404 to everyone, its own members included, byte-identical to a slug nobody holds; members reach it through the own-scope door', async () => {
|
|
629
|
+
const none = await get(ANON, '/api/bongos/guilds/mx-nobody');
|
|
630
|
+
assert.equal(none.status, 404);
|
|
631
|
+
for (const who of ALL_VIEWERS()) {
|
|
632
|
+
const r = await get(who, '/api/bongos/guilds/mx-unlisted');
|
|
633
|
+
assert.equal(r.status, 404, `${r.who}: an unlisted guild has no public existence (ADR 0336 D4)`);
|
|
634
|
+
assert.equal(r.text.replaceAll('mx-unlisted', 'S'), none.text.replaceAll('mx-nobody', 'S'), `${r.who}: no existence oracle`);
|
|
635
|
+
}
|
|
636
|
+
const own = await get(MEMBER, '/api/bongos/guilds/mx-unlisted/members');
|
|
637
|
+
assert.equal(own.status, 200);
|
|
638
|
+
const listed = new Set(own.json.members.map((m) => m.handle));
|
|
639
|
+
for (const k of KEYS) assert.equal(listed.has(ACCT[k].handle), MATRIX[k].members === 'listed', `${k} on the unlisted guild’s members’ roster`);
|
|
640
|
+
});
|
|
641
|
+
|
|
538
642
|
test('the sliver’s key lists are D3’s, transcribed: handle, name, avatar, disciplines, a recent-activity boolean — or the first three', () => {
|
|
539
643
|
// The cells above compare a response to the MODULE's lists; this pins those
|
|
540
644
|
// lists to the spec, so a sixth key added to the module cannot pass with them.
|
|
@@ -594,6 +698,10 @@ test('the recruiting directory — the recruiter sees the full and name-only tie
|
|
|
594
698
|
// ===========================================================================
|
|
595
699
|
|
|
596
700
|
const isOwnRead = (r, a) => r.whoId === a.github_id && r.path === '/api/bongos/me/cross-project';
|
|
701
|
+
// The one place ADR 0336 D4 lets a private member be named: their own guild's
|
|
702
|
+
// members' roster, read by a fellow member — joining is consent to be seen BY THE
|
|
703
|
+
// GUILD, never by the world. A provisional account is not on it (accountActiveSql).
|
|
704
|
+
const isFellowMemberRead = (r) => r.who === 'Member' && /^\/api\/bongos\/guilds\/[^/?]+\/members$/.test(r.path);
|
|
597
705
|
// What a response DISCLOSES: its body minus the fields that only echo the
|
|
598
706
|
// caller's own request back (search's `query` and `discipline`). A discipline
|
|
599
707
|
// the caller typed into the URL is not the server revealing it; the results
|
|
@@ -608,7 +716,7 @@ function disclosed(r) {
|
|
|
608
716
|
}
|
|
609
717
|
|
|
610
718
|
test('the walk covered every surface for every row (so the claims below are about something)', () => {
|
|
611
|
-
for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/scouting']) {
|
|
719
|
+
for (const p of ['/profiles/', '/me/cross-project', '/community/search', '/community/leaderboard', '/scouting', '/guilds/mx-guild', '/guilds/mx-guild/members']) {
|
|
612
720
|
assert.ok(SEEN.some((r) => r.path.includes(p) && r.status === 200), `no 200 from ${p} — the sweep below would be vacuous for it`);
|
|
613
721
|
}
|
|
614
722
|
assert.ok(SEEN.length >= KEYS.length * 6, `only ${SEEN.length} responses recorded`);
|
|
@@ -630,21 +738,25 @@ test('hide_stats beats everything: no hidden account’s numbers reach anyone bu
|
|
|
630
738
|
assert.ok(SEEN.some((r) => r.who === 'Anon' && r.text.includes(String(ROLLUP[ACCT.PUB.github_id].credits))), 'a shown record must reach Anon, or absence above is vacuous');
|
|
631
739
|
});
|
|
632
740
|
|
|
633
|
-
test('provisional and private (not discoverable) are absent from every cross-project surface, for every viewer but Self', () => {
|
|
741
|
+
test('provisional and private (not discoverable) are absent from every cross-project surface, for every viewer but Self — a private member’s own guild aside', () => {
|
|
634
742
|
for (const k of ['PROV', 'PRIV']) {
|
|
635
743
|
const a = ACCT[k];
|
|
636
|
-
|
|
744
|
+
const ownGuild = (x) => k === 'PRIV' && isFellowMemberRead(x);
|
|
745
|
+
for (const r of SEEN.filter((x) => !isOwnRead(x, a) && !ownGuild(x) && !x.path.endsWith(`/profiles/${a.handle}`) && !x.path.endsWith(`/scouting/${a.handle}`))) {
|
|
637
746
|
for (const s of identitySentinels(a)) assert.ok(!disclosed(r).includes(s), `${k}'s "${s}" reached ${r.who} through GET ${r.path}`);
|
|
638
747
|
}
|
|
639
748
|
}
|
|
640
749
|
});
|
|
641
750
|
|
|
642
|
-
test('private · discoverable reaches the recruiter’s sliver and nowhere else', () => {
|
|
751
|
+
test('private · discoverable reaches the recruiter’s sliver and, as a member, its own guilds’ members’ rosters — nowhere else', () => {
|
|
643
752
|
for (const k of ['PRIVD', 'PRIVDH']) {
|
|
644
753
|
const a = ACCT[k];
|
|
645
754
|
const leaks = SEEN.filter((r) => !isOwnRead(r, a) && identitySentinels(a).some((s) => disclosed(r).includes(s)));
|
|
646
|
-
assert.deepEqual(leaks.map((r) => `${r.who} ${r.path}`), [
|
|
647
|
-
|
|
755
|
+
assert.deepEqual([...new Set(leaks.map((r) => `${r.who} ${r.path}`))].sort(), [
|
|
756
|
+
'Member /api/bongos/guilds/mx-guild/members',
|
|
757
|
+
'Member /api/bongos/guilds/mx-unlisted/members',
|
|
758
|
+
`Recruiter /api/bongos/scouting/${a.handle}`,
|
|
759
|
+
], `${k} may be named only by the sliver, to the recruiter (D3), and to its fellow guild members (ADR 0336 D4)`);
|
|
648
760
|
}
|
|
649
761
|
});
|
|
650
762
|
|
package/tests/render_check.mjs
CHANGED
|
@@ -24,8 +24,8 @@ test('a shared asset reaches every page in its dir whose HTML names it', () => {
|
|
|
24
24
|
// sky-data.js is shared by the two doors of one page (task 1004232): /thinking and /goal-map.
|
|
25
25
|
assert.deepEqual(names(['modules/hall-ui/public/sky-data.js']), ['goal-map.states.json', 'thinking.states.json']);
|
|
26
26
|
assert.deepEqual(names(['modules/hall-ui/public/text-fit.js']), ['goals.states.json']);
|
|
27
|
-
// the account page links the hub's sheet too since task 1004259
|
|
28
|
-
assert.deepEqual(names(['modules/public-landing/public/assets/cosmos.css']), ['account.states.json', 'projects.states.json']);
|
|
27
|
+
// the account page links the hub's sheet too since task 1004259, and the guild page since task 1002301
|
|
28
|
+
assert.deepEqual(names(['modules/public-landing/public/assets/cosmos.css']), ['account.states.json', 'guild.states.json', 'projects.states.json']);
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
test('the page a builder changed comes before the pages a shared file reaches, so the render cap never drops it', () => {
|
package/tests/ui_design_kit.mjs
CHANGED
|
@@ -34,7 +34,7 @@ test('the platform floors are a pinned list — a floor cannot vanish quietly',
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
test('the shipped states files validate and sit beside the page they name', () => {
|
|
37
|
-
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/projects-dns-pending.states.json', 'modules/public-landing/public/projects-hall-live.states.json','modules/public-landing/public/index.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/account-private.states.json', 'modules/hall-ui/public/ranks.states.json']) {
|
|
37
|
+
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/projects-empty.states.json', 'modules/public-landing/public/projects-dns-pending.states.json', 'modules/public-landing/public/projects-hall-live.states.json','modules/public-landing/public/index.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/account-private.states.json', 'modules/public-landing/public/guild.states.json', 'modules/hall-ui/public/ranks.states.json']) {
|
|
38
38
|
const json = readJson(rel);
|
|
39
39
|
const v = lib.validateStatesFile(json);
|
|
40
40
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
@@ -43,14 +43,14 @@ test('the shipped states files validate and sit beside the page they name', () =
|
|
|
43
43
|
}
|
|
44
44
|
// the apex is dark only (task 1003336): its states files name the one mode, and
|
|
45
45
|
// render.js reads it as the default so a plain `--page /` never renders light
|
|
46
|
-
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/index.states.json', 'modules/public-landing/public/account.states.json']) {
|
|
46
|
+
for (const rel of ['modules/public-landing/public/projects.states.json', 'modules/public-landing/public/index.states.json', 'modules/public-landing/public/account.states.json', 'modules/public-landing/public/guild.states.json']) {
|
|
47
47
|
assert.deepEqual(readJson(rel).modes, ['dark'], `${rel}: the apex renders dark only`);
|
|
48
48
|
assert.equal(readJson(rel).modeQuery, false, `${rel}: no query walks a second mode`);
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
test('the shipped probes files validate', () => {
|
|
53
|
-
for (const rel of ['modules/public-landing/public/projects.probes.json', 'modules/public-landing/public/index.probes.json', 'modules/public-landing/public/account.probes.json']) {
|
|
53
|
+
for (const rel of ['modules/public-landing/public/projects.probes.json', 'modules/public-landing/public/index.probes.json', 'modules/public-landing/public/account.probes.json', 'modules/public-landing/public/guild.probes.json']) {
|
|
54
54
|
const v = lib.validateProbesFile(readJson(rel));
|
|
55
55
|
assert.deepEqual(v.errors, [], `${rel}: ${v.errors.join('; ')}`);
|
|
56
56
|
}
|