@bongos/core 1.20.66 → 1.20.67
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 +29 -24
- package/docs/copy-inventory.md +156 -154
- package/docs/copy-registry.json +196 -178
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +28 -28
- package/modules/hall-ui/public/goals-lib.js +37 -5
- package/modules/hall-ui/public/goals-render.js +14 -5
- package/modules/hall-ui/public/task.js +25 -2
- package/modules/lifecycle/goal-rollup.js +13 -7
- package/modules/lifecycle/task-status.js +4 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/hall-preview/refresh-fixtures.js +5 -1
- package/src/module-api.js +1 -1
- package/tests/claim_from_task_and_home.mjs +22 -1
- package/tests/dep_blocked_ready_cards.mjs +70 -0
- package/tests/goal_sort_staleness.mjs +8 -3
- package/tests/goals_lib.mjs +58 -2
package/tests/goals_lib.mjs
CHANGED
|
@@ -25,7 +25,16 @@ const SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/goals-lib.js
|
|
|
25
25
|
const TASK_LIB_SRC = fs.readFileSync(path.join(ROOT, 'modules/hall-ui/public/task-lib.js'), 'utf8');
|
|
26
26
|
const { test, summary } = makeRunner();
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// window.OTB.escapeHtml is the ONE sibling the lib reaches for that is not a
|
|
29
|
+
// loadable file (it lives in the hall's shared dom-utils). The real one, copied
|
|
30
|
+
// to its behaviour, so a tag that forgets to escape a dependency title fails
|
|
31
|
+
// here rather than passing against a pass-through fake.
|
|
32
|
+
const windowObj = {
|
|
33
|
+
OTB: {
|
|
34
|
+
escapeHtml: (s) => String(s ?? '').replace(/&/g, '&').replace(/</g, '<')
|
|
35
|
+
.replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''),
|
|
36
|
+
},
|
|
37
|
+
};
|
|
29
38
|
const sandbox = { window: windowObj, Set, Map, Math, Number, String, Array, Object, Date, JSON };
|
|
30
39
|
sandbox.globalThis = sandbox;
|
|
31
40
|
vm.createContext(sandbox);
|
|
@@ -39,7 +48,8 @@ await test('loads standalone in a bare window (no page-state dependency) and exp
|
|
|
39
48
|
assert.ok(lib, 'window.OTBGoalsLib must be set');
|
|
40
49
|
assert.equal('state' in sandbox, false, 'the lib must not reach for page state');
|
|
41
50
|
for (const k of ['DONE_STATUSES', 'CUT_STATUSES', 'isTodoStatus', 'ring', 'memberName',
|
|
42
|
-
'
|
|
51
|
+
'hasUnmetDeps', 'taskStatusTagHtml', 'readinessOf', 'EMPTY_ROLLUP', 'goalRollLine',
|
|
52
|
+
'matchesTodoSearch', 'msOf', 'taskActivityMs']) {
|
|
43
53
|
assert.ok(k in lib, `missing export: ${k}`);
|
|
44
54
|
}
|
|
45
55
|
});
|
|
@@ -53,6 +63,52 @@ await test('readinessOf buckets match the task-1002439 rules', () => {
|
|
|
53
63
|
assert.equal(lib.readinessOf({ status: 'backlog' }), 'backlog');
|
|
54
64
|
});
|
|
55
65
|
|
|
66
|
+
// Task 1004487 — the case the backlog-only condition missed. ADR 0015 rules
|
|
67
|
+
// that a dependency added AFTER promotion leaves the row at `ready` and that
|
|
68
|
+
// the readers are what change (core_264 did it for the claimable feed), so a
|
|
69
|
+
// `ready` row with an unshipped dependency must bucket as blocked here too —
|
|
70
|
+
// otherwise the Goals page counts work POST /claims refuses as claimable.
|
|
71
|
+
await test('a READY task with an unshipped dependency is blocked, not ready', () => {
|
|
72
|
+
assert.equal(lib.readinessOf({ status: 'ready', blocked_by: [{ satisfied: false }] }), 'blocked');
|
|
73
|
+
// …and a satisfied edge does not demote a genuinely claimable row.
|
|
74
|
+
assert.equal(lib.readinessOf({ status: 'ready', blocked_by: [{ satisfied: true }] }), 'ready');
|
|
75
|
+
assert.equal(lib.readinessOf({ status: 'ready', blocked_by: [] }), 'ready');
|
|
76
|
+
// A live claim still outranks the dependency question: the task is being
|
|
77
|
+
// worked, so "in progress" is the true answer whatever its edges say.
|
|
78
|
+
assert.equal(lib.readinessOf({ status: 'active', blocked_by: [{ satisfied: false }] }), 'active');
|
|
79
|
+
// Settled work is never re-bucketed by a stale edge either.
|
|
80
|
+
assert.equal(lib.readinessOf({ status: 'shipped', blocked_by: [{ satisfied: false }] }), 'done');
|
|
81
|
+
assert.equal(lib.readinessOf({ status: 'abandoned', blocked_by: [{ satisfied: false }] }), 'cut');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
await test('hasUnmetDeps mirrors the DEPS_NOT_SHIPPED rule and tolerates a missing array', () => {
|
|
85
|
+
assert.equal(lib.hasUnmetDeps({ blocked_by: [{ satisfied: true }, { satisfied: false }] }), true);
|
|
86
|
+
assert.equal(lib.hasUnmetDeps({ blocked_by: [{ satisfied: true }] }), false);
|
|
87
|
+
// A caller that never asked for ?include=deps gets no array at all; the page
|
|
88
|
+
// must render rather than throw, and must not invent a block it cannot see.
|
|
89
|
+
assert.equal(lib.hasUnmetDeps({ status: 'ready' }), false);
|
|
90
|
+
assert.equal(lib.hasUnmetDeps(null), false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await test("taskStatusTagHtml says blocked on a dep-blocked ready row, and the real status otherwise", () => {
|
|
94
|
+
const blocked = lib.taskStatusTagHtml({
|
|
95
|
+
status: 'ready',
|
|
96
|
+
blocked_by: [{ id: 640, title: 'Palette overhaul', satisfied: false },
|
|
97
|
+
{ id: 587, title: 'Already landed', satisfied: true }],
|
|
98
|
+
});
|
|
99
|
+
assert.match(blocked, />blocked<\/span>$/, 'the word the claim gate would use, not the status column');
|
|
100
|
+
assert.match(blocked, /title="Waiting on #640 Palette overhaul"/, 'it names the unmet edge');
|
|
101
|
+
assert.doesNotMatch(blocked, /587/, 'and not the one that already shipped');
|
|
102
|
+
// Every other row is untouched — this must not become a second status vocabulary.
|
|
103
|
+
assert.equal(lib.taskStatusTagHtml({ status: 'active' }),
|
|
104
|
+
'<span class="task__tag" title="Task status">active</span>');
|
|
105
|
+
assert.equal(lib.taskStatusTagHtml({ status: 'ready', blocked_by: [{ satisfied: true }] }),
|
|
106
|
+
'<span class="task__tag" title="Task status">ready</span>');
|
|
107
|
+
// A dependency title is ledger prose on a title attribute — escape it.
|
|
108
|
+
const nasty = lib.taskStatusTagHtml({ status: 'ready', blocked_by: [{ id: 1, title: '"><b>x', satisfied: false }] });
|
|
109
|
+
assert.doesNotMatch(nasty, /<b>/);
|
|
110
|
+
});
|
|
111
|
+
|
|
56
112
|
await test('matchesTodoSearch: bare number, #N and title fragments all match (task 1003135 search semantics)', () => {
|
|
57
113
|
const t = { id: 1002163, title: 'Show the goal id on the row' };
|
|
58
114
|
assert.ok(lib.matchesTodoSearch(t, ''));
|