| [70677e96] | 1 | // The Guardian PWA is built in the browser from a state blob, so the usual
|
|---|
| 2 | // tests cannot reach it. These two failure modes bit during the rebuild and are
|
|---|
| 3 | // cheap to guard: a renderer writing into a section that no longer exists, and
|
|---|
| 4 | // a grouping key quietly dropped from a route so every panel comes up empty.
|
|---|
| 5 | import { test } from 'node:test';
|
|---|
| 6 | import assert from 'node:assert/strict';
|
|---|
| 7 | import fs from 'fs';
|
|---|
| 8 |
|
|---|
| 9 | const read = (p) => fs.readFileSync(new URL(p, import.meta.url), 'utf8');
|
|---|
| 10 | const client = read('../src/assets/js/guardian.js');
|
|---|
| 11 | const page = read('../src/views/pages/guardian.ejs');
|
|---|
| 12 | const route = read('../src/routes/guardian.js');
|
|---|
| 13 |
|
|---|
| 14 | test('the client only touches element ids the page actually has', () => {
|
|---|
| 15 | // g-fatal is the exception: the crash banner is created by the client itself
|
|---|
| 16 | // (getElementById || createElement), precisely because the page may be too
|
|---|
| 17 | // broken to have it.
|
|---|
| 18 | const ids = new Set([...page.matchAll(/id="([^"]+)"/g)].map((m) => m[1])).add('g-fatal');
|
|---|
| 19 | const used = [...client.matchAll(/getElementById\('([^']+)'\)/g)].map((m) => m[1]);
|
|---|
| 20 | const missing = [...new Set(used)].filter((id) => !ids.has(id));
|
|---|
| 21 | assert.deepEqual(missing, [], `guardian.js writes into ${missing.join(', ')}, which the page does not have`);
|
|---|
| 22 | });
|
|---|
| 23 |
|
|---|
| 24 | test('the sections that moved into the panels are gone from the page', () => {
|
|---|
| 25 | for (const id of ['feed-section', 'follow-section', 'feed-list', 'follow-list']) {
|
|---|
| 26 | assert.ok(!page.includes(`id="${id}"`), `${id} moved into the per-ward panel and must not be a section of its own`);
|
|---|
| 27 | }
|
|---|
| 28 | });
|
|---|
| 29 |
|
|---|
| 30 | test('every panel section has something to group by', () => {
|
|---|
| 31 | // A child's panel is filled by matching these against the ward's actor URI.
|
|---|
| 32 | // Lose one and that section silently shows "nothing yet" for every child.
|
|---|
| 33 | assert.match(route, /authorUri: p\.author_uri/, "the wards' posts");
|
|---|
| 34 | assert.match(route, /wardUri: w\.uri/, 'follow requests on a local ward');
|
|---|
| 35 | assert.match(route, /wardUri: rev\.ward_uri/, 'follow requests forwarded from a remote ward');
|
|---|
| 36 | for (const key of ['authorUri', 'wardUri', 'actor_uri']) {
|
|---|
| 37 | assert.ok(client.includes(key), `the panel groups on ${key}`);
|
|---|
| 38 | }
|
|---|
| 39 | });
|
|---|
| 40 |
|
|---|
| 41 | test('the labels the panel renders are all served to it', () => {
|
|---|
| 42 | // uiStrings() picks the keys by hand, so a label used in the client but not
|
|---|
| 43 | // listed there renders as an empty string with no error anywhere. Two shapes
|
|---|
| 44 | // count as served: an entry in the keys array, and a direct s.foo = ...
|
|---|
| 45 | // assignment underneath it (wave/waved arrived that way).
|
|---|
| 46 | const served = new Set([
|
|---|
| 47 | ...[...route.matchAll(/'([a-z_]+)'/g)].map((m) => m[1]),
|
|---|
| 48 | ...[...route.matchAll(/\bs\.([a-z_]+)\s*=/g)].map((m) => m[1]),
|
|---|
| 49 | ]);
|
|---|
| 50 | const used = [...new Set([...client.matchAll(/T\.([a-z_]+)/g)].map((m) => m[1]))];
|
|---|
| 51 | const missing = used.filter((k) => !served.has(k));
|
|---|
| 52 | assert.deepEqual(missing, [], `uiStrings() does not serve: ${missing.join(', ')}`);
|
|---|
| 53 | });
|
|---|