@bongos/core 1.19.280 → 1.19.282
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 +31 -16
- package/docs/copy-inventory.md +89 -89
- package/docs/copy-registry.json +109 -109
- package/modules/hall-ui/public/goals-lib.js +99 -0
- package/modules/hall-ui/public/goals-page.js +137 -141
- package/modules/hall-ui/public/goals.css +12 -15
- package/modules/hall-ui/public/goals.html +2 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +5 -1
- package/tests/goal_criterion_confirm_ui.mjs +4 -0
- package/tests/goals_card_view.mjs +61 -0
- package/tests/goals_lib.mjs +70 -0
|
@@ -37,6 +37,9 @@ const SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/goals-page.j
|
|
|
37
37
|
// the REAL file in the same context rather than stubbing it — stubbing would let
|
|
38
38
|
// the extraction rot without this, the only test that executes either board.
|
|
39
39
|
const TASK_LIB_SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/task-lib.js'), 'utf8');
|
|
40
|
+
// task 1003135: the page's pure presentational helpers moved to goals-lib.js
|
|
41
|
+
// (window.OTBGoalsLib) — run the REAL file too, same reasoning as task-lib above.
|
|
42
|
+
const GOALS_LIB_SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/goals-lib.js'), 'utf8');
|
|
40
43
|
|
|
41
44
|
const { test, summary } = makeRunner();
|
|
42
45
|
|
|
@@ -132,6 +135,7 @@ async function render({ rank = 'metic', goalStatus = 'open', criteria = Object.v
|
|
|
132
135
|
sandbox.globalThis = sandbox;
|
|
133
136
|
vm.createContext(sandbox);
|
|
134
137
|
vm.runInContext(TASK_LIB_SRC, sandbox, { filename: 'task-lib.js' });
|
|
138
|
+
vm.runInContext(GOALS_LIB_SRC, sandbox, { filename: 'goals-lib.js' });
|
|
135
139
|
vm.runInContext(SRC, sandbox, { filename: 'goals-page.js' });
|
|
136
140
|
|
|
137
141
|
// Let boot()'s promise chain settle (me → goals → details → route → render).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// tests/goals_card_view.mjs — the Goals page list|cards toggle (task 1003136).
|
|
2
|
+
//
|
|
3
|
+
// Source-contract pins over goals-page.js: the DONE-WHEN properties that a DOM
|
|
4
|
+
// harness can't cheaply hold are exactly the ones that rot silently — the two
|
|
5
|
+
// views sharing one fact source, the drag landmine staying DELIBERATELY dead,
|
|
6
|
+
// and the view choice persisting off-URL. The interactive render itself is
|
|
7
|
+
// exercised by tests/goal_criterion_confirm_ui.mjs, which runs the real page.
|
|
8
|
+
//
|
|
9
|
+
// Run: node tests/goals_card_view.mjs
|
|
10
|
+
|
|
11
|
+
import { strict as assert } from 'node:assert';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { makeRunner } from './helpers.mjs';
|
|
16
|
+
|
|
17
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/goals-page.js'), 'utf8');
|
|
19
|
+
const CSS = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/goals.css'), 'utf8');
|
|
20
|
+
const { test, summary } = makeRunner();
|
|
21
|
+
|
|
22
|
+
await test('both views render from ONE fact source (goalRenderFacts) so they cannot drift', () => {
|
|
23
|
+
assert.match(SRC, /function goalRenderFacts\(/, 'the shared fact fn must exist');
|
|
24
|
+
const rowBody = SRC.slice(SRC.indexOf('function goalRowHtml'), SRC.indexOf('function goalCardHtml'));
|
|
25
|
+
assert.match(rowBody, /goalRenderFacts\(g/, 'the row must consume the shared facts');
|
|
26
|
+
const cardBody = SRC.slice(SRC.indexOf('function goalCardHtml'));
|
|
27
|
+
assert.match(cardBody.slice(0, 1200), /goalRenderFacts\(g/, 'the card must consume the shared facts');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await test('renderList forks on the f-goal-view toggle AFTER grouping/filter/sort (both views share them)', () => {
|
|
31
|
+
const body = SRC.slice(SRC.indexOf('function renderList'), SRC.indexOf('drag-to-reorder: RETIRED'));
|
|
32
|
+
assert.match(body, /#f-goal-view/, 'the fork reads the toggle');
|
|
33
|
+
const sortIdx = body.indexOf('sortGoals(');
|
|
34
|
+
const forkIdx = body.indexOf('#f-goal-view');
|
|
35
|
+
assert.ok(sortIdx >= 0 && forkIdx > sortIdx, 'grouping/sort must run before the view fork');
|
|
36
|
+
assert.match(body, /goalCardHtml/, 'cards view renders goalCardHtml');
|
|
37
|
+
assert.match(body, /goalRowHtml/, 'list view still renders goalRowHtml');
|
|
38
|
+
assert.match(body, /<div class="goal-grid">/, 'cards nest an id-less .goal-grid so the block override does not flatten them');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await test('the view choice persists via localStorage (otb.goals.view), never the URL', () => {
|
|
42
|
+
assert.match(SRC, /GOAL_VIEW_PREF = 'otb\.goals\.view'/);
|
|
43
|
+
assert.match(SRC, /setItem\(GOAL_VIEW_PREF/, 'onChange writes the pref');
|
|
44
|
+
assert.match(SRC, /getItem\(GOAL_VIEW_PREF\)/, 'boot reads the pref back');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await test('drag-to-reorder is deliberately retired: no listener, no draggable cards, decision stated', () => {
|
|
48
|
+
assert.ok(!/function wireDragReorder/.test(SRC), 'wireDragReorder must stay deleted');
|
|
49
|
+
assert.ok(!/addEventListener\('drag/.test(SRC), 'no drag listeners anywhere on the page');
|
|
50
|
+
const cardBody = SRC.slice(SRC.indexOf('function goalCardHtml'), SRC.indexOf('function renderList'));
|
|
51
|
+
assert.ok(!/draggable/.test(cardBody), 'goalCardHtml must not emit a draggable attribute');
|
|
52
|
+
assert.match(SRC, /RETIRED \(task 1003136/, 'the retirement decision is stated where the code lived');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await test('goals.css: the block override stays id-scoped so inner .goal-grid card grids survive', () => {
|
|
56
|
+
assert.match(CSS, /#goal-grid\.goal-grid \{ display: block/, 'the outer flatten must stay id-selected');
|
|
57
|
+
assert.match(CSS, /\.goal-grid \{ display: grid; grid-template-columns: repeat\(auto-fill/, 'the card grid rule must survive');
|
|
58
|
+
assert.ok(!/goal-card__handle|goal-card--dragging/.test(CSS), 'the drag affordance CSS is gone with the listener');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
summary();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// tests/goals_lib.mjs — the Goals page's extracted pure helpers (goals-lib.js,
|
|
2
|
+
// task 1003135). Pins (a) that the lib loads standalone in a bare window — it
|
|
3
|
+
// must not silently grow a dependency on the page's IIFE state, which is the
|
|
4
|
+
// whole reason it could move out of the oversized goals-page.js — and (b) the
|
|
5
|
+
// behaviour of the helpers the page destructures.
|
|
6
|
+
//
|
|
7
|
+
// Run: node tests/goals_lib.mjs
|
|
8
|
+
|
|
9
|
+
import { strict as assert } from 'node:assert';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import vm from 'node:vm';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { makeRunner } from './helpers.mjs';
|
|
15
|
+
|
|
16
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
|
+
const SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/goals-lib.js'), 'utf8');
|
|
18
|
+
const { test, summary } = makeRunner();
|
|
19
|
+
|
|
20
|
+
const windowObj = {};
|
|
21
|
+
const sandbox = { window: windowObj, Set, Map, Math, Number, String, Array, Object, Date, JSON };
|
|
22
|
+
sandbox.globalThis = sandbox;
|
|
23
|
+
vm.createContext(sandbox);
|
|
24
|
+
vm.runInContext(SRC, sandbox, { filename: 'goals-lib.js' });
|
|
25
|
+
const lib = windowObj.OTBGoalsLib;
|
|
26
|
+
|
|
27
|
+
await test('loads standalone in a bare window (no page-state dependency) and exports the full surface', () => {
|
|
28
|
+
assert.ok(lib, 'window.OTBGoalsLib must be set');
|
|
29
|
+
for (const k of ['DONE_STATUSES', 'CUT_STATUSES', 'isTodoStatus', 'ring', 'memberName',
|
|
30
|
+
'readinessOf', 'EMPTY_ROLLUP', 'goalRollLine', 'matchesTodoSearch', 'msOf', 'taskActivityMs']) {
|
|
31
|
+
assert.ok(k in lib, `missing export: ${k}`);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await test('readinessOf buckets match the task-1002439 rules', () => {
|
|
36
|
+
assert.equal(lib.readinessOf({ status: 'shipped' }), 'done');
|
|
37
|
+
assert.equal(lib.readinessOf({ status: 'abandoned' }), 'cut');
|
|
38
|
+
assert.equal(lib.readinessOf({ status: 'active' }), 'active');
|
|
39
|
+
assert.equal(lib.readinessOf({ status: 'ready' }), 'ready');
|
|
40
|
+
assert.equal(lib.readinessOf({ status: 'backlog', blocked_by: [{ satisfied: false }] }), 'blocked');
|
|
41
|
+
assert.equal(lib.readinessOf({ status: 'backlog' }), 'backlog');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
await test('matchesTodoSearch: bare number, #N and title fragments all match (task 1003135 search semantics)', () => {
|
|
45
|
+
const t = { id: 1002163, title: 'Show the goal id on the row' };
|
|
46
|
+
assert.ok(lib.matchesTodoSearch(t, ''));
|
|
47
|
+
assert.ok(lib.matchesTodoSearch(t, '1002163'));
|
|
48
|
+
assert.ok(lib.matchesTodoSearch(t, '#1002163'));
|
|
49
|
+
assert.ok(lib.matchesTodoSearch(t, '2163'), 'substring id match');
|
|
50
|
+
assert.ok(lib.matchesTodoSearch(t, 'goal id'));
|
|
51
|
+
assert.ok(!lib.matchesTodoSearch(t, 'zzz'));
|
|
52
|
+
assert.ok(lib.matchesTodoSearch(t, '#'), 'a lone # narrows nothing');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await test('goalRollLine renders only non-zero buckets; ring is well-formed SVG', () => {
|
|
56
|
+
const line = lib.goalRollLine({ ready: 2, active: 0, blocked: 1, backlog: 0 });
|
|
57
|
+
assert.match(line, /<b>2<\/b> ready/);
|
|
58
|
+
assert.match(line, /<b>1<\/b> blocked/);
|
|
59
|
+
assert.ok(!line.includes('in progress'), 'zero buckets are omitted');
|
|
60
|
+
assert.equal(lib.goalRollLine({ ready: 0, active: 0, blocked: 0, backlog: 0 }), '');
|
|
61
|
+
assert.match(lib.ring(2, 5, false), /aria-label="2 of 5 criteria satisfied"/);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await test('memberName label idiom: display name, then @login, then builder #id', () => {
|
|
65
|
+
assert.equal(lib.memberName({ display_name: 'A', github_login: 'a', builder_id: 1 }), 'A');
|
|
66
|
+
assert.equal(lib.memberName({ github_login: 'a', builder_id: 1 }), '@a');
|
|
67
|
+
assert.equal(lib.memberName({ builder_id: 1 }), 'builder #1');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
summary();
|