@bongos/core 1.23.10 → 1.23.11
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 +45 -35
- package/docs/adr/0337-the-hub-account-is-the-person-a-hall-account-is-their-seat-in-one-project.md +1 -0
- package/docs/copy-inventory.md +847 -806
- package/docs/copy-registry.json +1499 -1073
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +9 -3
- package/docs/page-readings.json +581 -575
- package/modules/public-landing/public/account.html +2 -1
- package/modules/public-landing/public/assets/hub-bar.js +4 -2
- package/modules/public-landing/public/community.html +2 -1
- package/modules/public-landing/public/community.states.json +3 -1
- package/modules/public-landing/public/connections-nosuggest.states.json +15 -0
- package/modules/public-landing/public/connections.html +23 -3
- package/modules/public-landing/public/connections.states.json +1 -0
- package/modules/public-landing/public/contact.html +2 -1
- package/modules/public-landing/public/guild.html +2 -1
- package/modules/public-landing/public/index.html +6 -2
- package/modules/public-landing/public/invitations.html +2 -1
- package/modules/public-landing/public/privacy.html +2 -1
- package/modules/public-landing/public/projects.html +4 -1
- package/modules/public-landing/public/terms.html +2 -1
- package/modules/ui-design/kit/fixtures/connections-nosuggest.json +12 -0
- 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/tests/hub_account_page.mjs +7 -1
- package/tests/hub_community_page.mjs +21 -3
- package/tests/hub_connections_page.mjs +53 -4
- package/tests/hub_guild_page.mjs +1 -0
- package/tests/landing_legal_pages.mjs +7 -1
|
@@ -128,6 +128,7 @@ function el(tag) {
|
|
|
128
128
|
n.appendChild = (c) => { c.parent = n; n.children.push(c); return c; };
|
|
129
129
|
n.setAttribute = (k, v) => { n.attrs[k] = String(v); };
|
|
130
130
|
n.addEventListener = (t, f) => { (n._on[t] ||= []).push(f); };
|
|
131
|
+
n.scrollIntoView = () => { n.scrolled = (n.scrolled || 0) + 1; };
|
|
131
132
|
return n;
|
|
132
133
|
}
|
|
133
134
|
const settle = async () => { for (let i = 0; i < 60; i++) await Promise.resolve(); };
|
|
@@ -161,7 +162,7 @@ const GRAPH = () => ({
|
|
|
161
162
|
], count: 2 } },
|
|
162
163
|
});
|
|
163
164
|
|
|
164
|
-
async function boot({ me = { builder: { github_login: 'builder' } }, reads = GRAPH(), writes = [] } = {}) {
|
|
165
|
+
async function boot({ me = { builder: { github_login: 'builder' } }, reads = GRAPH(), writes = [], hash = '' } = {}) {
|
|
165
166
|
const ids = new Map();
|
|
166
167
|
for (const id of [...PAGE.matchAll(/ id="([^"]+)"/g)].map((m) => m[1])) {
|
|
167
168
|
const n = el('x');
|
|
@@ -180,15 +181,18 @@ async function boot({ me = { builder: { github_login: 'builder' } }, reads = GRA
|
|
|
180
181
|
return Promise.resolve({ ok: a.status >= 200 && a.status < 300, status: a.status, json: () => (a.body === undefined ? Promise.reject(new SyntaxError('no body')) : Promise.resolve(a.body)) });
|
|
181
182
|
};
|
|
182
183
|
const document = { getElementById: (id) => ids.get(id) || null, createElement: el };
|
|
183
|
-
const
|
|
184
|
-
const
|
|
184
|
+
const winOn = {};
|
|
185
|
+
const window = { HubBar: { paint: () => {}, ready: Promise.resolve(me) }, addEventListener: (t, f) => { (winOn[t] ||= []).push(f); } };
|
|
186
|
+
const location = { hash };
|
|
187
|
+
const sandbox = { window, document, fetch, location, console, Promise, JSON, String, Object, Array, URL, Date, isNaN, encodeURIComponent };
|
|
185
188
|
vm.createContext(sandbox);
|
|
186
189
|
vm.runInContext(SCRIPT, sandbox, { filename: 'connections.html' });
|
|
187
190
|
await settle();
|
|
188
191
|
const click = async (b) => { for (const f of b._on.click || []) f({}); await settle(); };
|
|
189
192
|
const key = async (n, k) => { const e = { key: k, preventDefault() { e.prevented = true; } }; for (const f of n._on.keydown || []) f(e); await settle(); return e; };
|
|
190
193
|
const gets = () => requests.filter((r) => r.method === 'GET').map((r) => r.url);
|
|
191
|
-
|
|
194
|
+
const hashTo = async (h) => { location.hash = h; for (const f of winOn.hashchange || []) f({}); await settle(); };
|
|
195
|
+
return { ids, requests, gets, click, key, hashTo, $: (id) => ids.get(id) };
|
|
192
196
|
}
|
|
193
197
|
const button = (row, label) => all(row, 'btn').find((b) => b.textContent === label);
|
|
194
198
|
|
|
@@ -283,6 +287,51 @@ test('a failed suggestions read drops only that panel', async () => {
|
|
|
283
287
|
assert.equal(kids(ui.$('connList')).length, 2);
|
|
284
288
|
});
|
|
285
289
|
|
|
290
|
+
test('opened by the Community menu’s item (#connSuggest): scrolled to once, and an empty panel says so', async () => {
|
|
291
|
+
// the panel draws after the browser's own jump to the fragment, so the page makes it, once
|
|
292
|
+
let ui = await boot({ hash: '#connSuggest', writes: [{ status: 201, body: { status: 'pending' } }] });
|
|
293
|
+
assert.equal(ui.$('connSuggest').hidden, false);
|
|
294
|
+
assert.equal(ui.$('suggestNone').hidden, true, 'suggestions need no explaining');
|
|
295
|
+
assert.equal(ui.$('connSuggest').scrolled, 1);
|
|
296
|
+
await ui.click(button(kids(ui.$('suggestList'))[0], 'Connect'));
|
|
297
|
+
assert.equal(ui.gets().length, 6);
|
|
298
|
+
assert.equal(ui.$('connSuggest').scrolled, 1, 'a re-read after Connect does not jump the page again');
|
|
299
|
+
|
|
300
|
+
const none = GRAPH();
|
|
301
|
+
none['/connections/suggestions'].body = { suggestions: [] };
|
|
302
|
+
ui = await boot({ hash: '#connSuggest', reads: none });
|
|
303
|
+
assert.equal(ui.$('connSuggest').hidden, false, 'asked for by name, the panel answers rather than vanishing');
|
|
304
|
+
assert.equal(ui.$('suggestNone').hidden, false);
|
|
305
|
+
assert.match(PAGE, /<p class="mineEmpty" id="suggestNone" hidden>No one to suggest yet\. Suggestions come from the connections and projects you have in common with other builders\.<\/p>/);
|
|
306
|
+
|
|
307
|
+
const failed = GRAPH();
|
|
308
|
+
failed['/connections/suggestions'] = { status: 500, body: {} };
|
|
309
|
+
ui = await boot({ hash: '#connSuggest', reads: failed });
|
|
310
|
+
assert.equal(ui.$('connSuggest').hidden, true, 'an unread list is not "no one to suggest"');
|
|
311
|
+
assert.equal(ui.$('connSuggest').scrolled, undefined);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('the menu item pressed while already on the page: the hash change is answered as an arrival', async () => {
|
|
315
|
+
const none = GRAPH();
|
|
316
|
+
none['/connections/suggestions'].body = { suggestions: [] };
|
|
317
|
+
let ui = await boot({ reads: none });
|
|
318
|
+
assert.equal(ui.$('connSuggest').hidden, true, 'a plain visit: no suggestions, no empty shell');
|
|
319
|
+
await ui.hashTo('#connSuggest');
|
|
320
|
+
assert.equal(ui.gets().length, 6, 'one re-read');
|
|
321
|
+
assert.equal(ui.$('connSuggest').hidden, false);
|
|
322
|
+
assert.equal(ui.$('suggestNone').hidden, false);
|
|
323
|
+
|
|
324
|
+
ui = await boot();
|
|
325
|
+
await ui.hashTo('#connSuggest');
|
|
326
|
+
assert.equal(ui.$('connSuggest').scrolled, 1, 'scrolled to once the panel is drawn');
|
|
327
|
+
await ui.hashTo('#connList');
|
|
328
|
+
assert.equal(ui.gets().length, 6, 'any other hash reads nothing');
|
|
329
|
+
|
|
330
|
+
ui = await boot({ me: null });
|
|
331
|
+
await ui.hashTo('#connSuggest');
|
|
332
|
+
assert.deepEqual(ui.requests, [], 'signed out, the door stands and nothing is read');
|
|
333
|
+
});
|
|
334
|
+
|
|
286
335
|
test('Accept, Decline, Remove and Withdraw: one call each, then the page re-reads', async () => {
|
|
287
336
|
const cases = [
|
|
288
337
|
['incomingList', 'Accept', 'POST', '/api/bongos/connections/51/accept', { status: 200, body: { status: 'accepted' } }, 'Connected.'],
|
package/tests/hub_guild_page.mjs
CHANGED
|
@@ -216,6 +216,7 @@ function boot({ pathname = '/g/forge-crew', me = null, answer = () => NOT_FOUND,
|
|
|
216
216
|
querySelectorAll(sel) {
|
|
217
217
|
if (sel === '.trig') return trigs;
|
|
218
218
|
if (sel === '.menuBand') return bands;
|
|
219
|
+
if (sel === '.knowCell') return [];
|
|
219
220
|
const m = sel.match(/^input\[name="(\w+)"\]$/);
|
|
220
221
|
if (!m) throw new Error(`unstubbed selector: ${sel}`);
|
|
221
222
|
return radios.filter((r) => r.name === m[1]);
|
|
@@ -195,6 +195,7 @@ for (const p of PAGES) {
|
|
|
195
195
|
['Your projects', '/projects?view=mine'], ['New project', '/projects?view=new'],
|
|
196
196
|
// the Community menu's three sub-pages, folded into the band under 480px (task 1003363); apex-only
|
|
197
197
|
['Leaderboard', '/community'], ['Find a builder', '/community/find'], ['Open to recruiters', '/community/recruiters'],
|
|
198
|
+
['Builders you may know', '/connections#connSuggest'],
|
|
198
199
|
], 'this page has no views of its own, so every item names /projects in full');
|
|
199
200
|
assert.deepEqual(bandLinks(bandOf(stripApexOnly(html, false), 'menuProjects')).map((l) => l[0]), ['Sky', 'Table', 'Your projects', 'New project'], 'a child has no /community');
|
|
200
201
|
assert.deepEqual(bandLinks(bandOf(html, 'menuProfile')), [
|
|
@@ -264,6 +265,8 @@ function bootBar(html, answer, pathname) {
|
|
|
264
265
|
els.set(m[3], el(m[3], /\shidden\b/.test(m[2] + m[4]), attrs));
|
|
265
266
|
}
|
|
266
267
|
const trigs = [...els.values()].filter((e) => /\btrig\b/.test(e.attrs.class || ''));
|
|
268
|
+
// the signed-in-only "Builders you may know" cells (task 1004256), by class; a child has none
|
|
269
|
+
const knowCells = [...html.matchAll(/<li class="bandCell(?: bandFold)? knowCell" hidden>/g)].map(() => ({ hidden: true }));
|
|
267
270
|
const bands = [...html.matchAll(/<div class="menuBand" id="(\w+)"[\s\S]*?\n<\/div>/g)].map((m) => {
|
|
268
271
|
const band = els.get(m[1]);
|
|
269
272
|
band.items = [...m[0].matchAll(/<li class="bandCell"(?: id="(\w+)")?><a class="([^"]+)" href="([^"]+)"([^>]*)>([^<]+)<\/a>/g)].map((a) => ({
|
|
@@ -287,7 +290,7 @@ function bootBar(html, answer, pathname) {
|
|
|
287
290
|
const event = (extra) => ({ prevented: false, preventDefault() { this.prevented = true; }, ...extra });
|
|
288
291
|
const calls = [];
|
|
289
292
|
const dom = {
|
|
290
|
-
els, calls, assigned: null,
|
|
293
|
+
els, calls, knowCells, assigned: null,
|
|
291
294
|
active: () => document.activeElement,
|
|
292
295
|
band: (id) => els.get(id),
|
|
293
296
|
item: (id, text) => els.get(id).items.find((i) => i.text === text),
|
|
@@ -312,6 +315,7 @@ function bootBar(html, answer, pathname) {
|
|
|
312
315
|
querySelectorAll(sel) {
|
|
313
316
|
if (sel === '.trig') return trigs;
|
|
314
317
|
if (sel === '.menuBand') return bands;
|
|
318
|
+
if (sel === '.knowCell') return knowCells;
|
|
315
319
|
throw new Error(`unstubbed selector: ${sel}`);
|
|
316
320
|
},
|
|
317
321
|
};
|
|
@@ -341,6 +345,7 @@ for (const p of PAGES) {
|
|
|
341
345
|
assert.equal(out.els.get('who').hidden, true, 'no chip without a session');
|
|
342
346
|
assert.equal(out.els.get('signinTop').hidden, false);
|
|
343
347
|
assert.equal(out.els.get('mineCell').hidden, true, 'and the Projects band offers no Your projects');
|
|
348
|
+
assert.deepEqual(out.knowCells.map((c) => c.hidden), hub ? [true, true] : [], 'nor Builders you may know; a child has no community menu at all');
|
|
344
349
|
assert.equal(await out.window.HubBar.ready, null);
|
|
345
350
|
|
|
346
351
|
const dom = bootBar(served, signedIn, p.route);
|
|
@@ -352,6 +357,7 @@ for (const p of PAGES) {
|
|
|
352
357
|
assert.equal(dom.els.get('who').getAttribute('aria-label'), 'Account, octocat');
|
|
353
358
|
for (const id of ['whoImg', 'bandWhoImg']) assert.equal(dom.els.get(id).src, 'https://avatars.example/u/1');
|
|
354
359
|
assert.equal(dom.els.get('mineCell').hidden, false);
|
|
360
|
+
assert.deepEqual(dom.knowCells.map((c) => c.hidden), hub ? [false, false] : []);
|
|
355
361
|
assert.deepEqual(dom.band('menuProfile').items.map((i) => i.text), hub ? ['My projects', 'Invitations', 'Connections', 'Account', 'Sign out'] : ['My projects', 'Sign out']);
|
|
356
362
|
});
|
|
357
363
|
}
|