@bongos/core 1.19.261 → 1.19.263
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 +36 -21
- package/config/branding.neutral.json +52 -12
- package/docs/copy-inventory.md +133 -136
- package/docs/copy-registry.json +164 -221
- package/modules/hall-ui/public/goals-page.js +8 -60
- package/modules/hall-ui/public/goals.html +2 -0
- package/modules/hall-ui/public/task-lib.js +136 -0
- package/modules/hall-ui/public/work.html +2 -0
- package/modules/hall-ui/public/work.js +8 -70
- package/modules/provisioning/provisioning.js +23 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/provision.js +2 -2
- package/src/branding.js +6 -0
- package/src/module-api.js +5 -1
- package/tests/goal_criterion_confirm_ui.mjs +6 -0
- package/tests/provisioning_settings_env.mjs +121 -0
- package/tests/task_lib.mjs +115 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// tests/task_lib.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1002463 — the shared task-domain helpers extracted out of work.js and
|
|
4
|
+
// goals-page.js into modules/hall-ui/public/task-lib.js.
|
|
5
|
+
//
|
|
6
|
+
// WHY THIS FILE EXISTS. The extraction was verified by an equivalence pass against
|
|
7
|
+
// the pristine pre-refactor sources — and that pass MISSED two real regressions,
|
|
8
|
+
// because every input it fed deHashRefs was a string or null. A grader caught them:
|
|
9
|
+
// the digit bound had been "tidied" from \d{1,5} to \d+, and the null guard from
|
|
10
|
+
// (text == null ? '' : text) to (text || ''). Both are behaviour changes hiding in
|
|
11
|
+
// a refactor. The lesson is that equivalence proofs are only as good as their input
|
|
12
|
+
// domain, so the boundaries now have named, checked-in cases.
|
|
13
|
+
//
|
|
14
|
+
// Run: node tests/task_lib.mjs
|
|
15
|
+
|
|
16
|
+
import { strict as assert } from 'node:assert';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
import { join, dirname } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
+
const T = require(join(ROOT, 'modules/hall-ui/public/task-lib.js'));
|
|
24
|
+
|
|
25
|
+
let passed = 0, failed = 0;
|
|
26
|
+
function t(name, fn) {
|
|
27
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
28
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---- deHashRefs: the two regressions, pinned ------------------------------
|
|
32
|
+
t('deHashRefs rewrites a 1-5 digit ref (GitHub would autolink it to an issue)', () => {
|
|
33
|
+
assert.equal(T.deHashRefs('see #123'), 'see task 123');
|
|
34
|
+
assert.equal(T.deHashRefs('#1'), 'task 1');
|
|
35
|
+
assert.equal(T.deHashRefs('#12345'), 'task 12345');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
t('deHashRefs LEAVES a 6+ digit run alone — \\d{1,5} is load-bearing by omission', () => {
|
|
39
|
+
// \d{1,5} followed by \b cannot match inside a longer digit run: after 5 digits
|
|
40
|
+
// the next char is still a word char, so there is no boundary and no match at
|
|
41
|
+
// all. Widening to \d+ silently changed this. Pinned so it cannot drift back.
|
|
42
|
+
assert.equal(T.deHashRefs('#123456'), '#123456');
|
|
43
|
+
assert.equal(T.deHashRefs('#1002463'), '#1002463');
|
|
44
|
+
assert.equal(T.deHashRefs('#12345 and #123456'), 'task 12345 and #123456');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
t('deHashRefs keeps falsy-but-real input — (text == null) not (!text)', () => {
|
|
48
|
+
// `text || ''` would swallow these into the empty string.
|
|
49
|
+
assert.equal(T.deHashRefs(0), '0');
|
|
50
|
+
assert.equal(T.deHashRefs(false), 'false');
|
|
51
|
+
assert.equal(T.deHashRefs(NaN), 'NaN');
|
|
52
|
+
// ...while genuinely absent input is still the empty string.
|
|
53
|
+
assert.equal(T.deHashRefs(null), '');
|
|
54
|
+
assert.equal(T.deHashRefs(undefined), '');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ---- touchesConflict: the parallel-safety predicate -----------------------
|
|
58
|
+
t('touchesConflict: a path collides with itself, its parents and its children', () => {
|
|
59
|
+
assert.equal(T.touchesConflict(['src'], ['src']), true);
|
|
60
|
+
assert.equal(T.touchesConflict(['src'], ['src/gds/db.js']), true);
|
|
61
|
+
assert.equal(T.touchesConflict(['src/gds/db.js'], ['src']), true);
|
|
62
|
+
assert.equal(T.touchesConflict(['src'], ['srcery']), false, 'prefix must not count as a parent');
|
|
63
|
+
assert.equal(T.touchesConflict(['a'], ['b']), false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
t('touchesConflict: trailing slashes normalise, non-arrays are inert', () => {
|
|
67
|
+
assert.equal(T.touchesConflict(['src/'], ['src']), true);
|
|
68
|
+
assert.equal(T.touchesConflict(null, ['src']), false);
|
|
69
|
+
assert.equal(T.touchesConflict('src', ['src']), false, 'a bare string is not a list');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ---- priorityRank / creditsPerMinute: sort keys ---------------------------
|
|
73
|
+
t('priorityRank: unparseable or absent sorts LAST, not first', () => {
|
|
74
|
+
assert.equal(T.priorityRank('P1'), 1);
|
|
75
|
+
assert.equal(T.priorityRank('P5'), 5);
|
|
76
|
+
assert.equal(T.priorityRank(null), 99);
|
|
77
|
+
assert.equal(T.priorityRank('nonsense'), 99);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
t('creditsPerMinute: unpriced/unestimated sinks below a genuine zero ratio', () => {
|
|
81
|
+
assert.equal(T.creditsPerMinute({ credits_reward: 10, est_minutes: 5 }), 2);
|
|
82
|
+
assert.equal(T.creditsPerMinute({ credits_reward: 0, est_minutes: 5 }), -Infinity);
|
|
83
|
+
assert.equal(T.creditsPerMinute({ credits_reward: 10, est_minutes: 0 }), -Infinity);
|
|
84
|
+
assert.equal(T.creditsPerMinute({}), -Infinity);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ---- friendlyBatchReason: the bug this extraction fixed -------------------
|
|
88
|
+
t('friendlyBatchReason explains MODULE_CONFLICT — the case goals-page.js had lost', () => {
|
|
89
|
+
assert.match(
|
|
90
|
+
T.friendlyBatchReason({ reason: 'MODULE_CONFLICT', module_key: 'ideas', conflictsWithTaskId: 7 }),
|
|
91
|
+
/shares module "ideas" with task #7/
|
|
92
|
+
);
|
|
93
|
+
assert.match(
|
|
94
|
+
T.friendlyBatchReason({ reason: 'MODULE_CONFLICT', module_key: 'ideas' }),
|
|
95
|
+
/shares module "ideas" with an active claim/
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
t('friendlyBatchReason falls through to the raw code for an unmapped reason', () => {
|
|
100
|
+
// The failure mode this guards: a new server-side refusal code degrades to the
|
|
101
|
+
// machine string in the UI rather than throwing. Loud enough to notice, safe.
|
|
102
|
+
assert.equal(T.friendlyBatchReason({ reason: 'SOME_NEW_CODE' }), 'SOME_NEW_CODE');
|
|
103
|
+
assert.equal(T.friendlyBatchReason(null), 'not claimable');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ---- rankTagHtml: escaping ------------------------------------------------
|
|
107
|
+
t('rankTagHtml escapes its label and treats xenos as "no floor"', () => {
|
|
108
|
+
assert.match(T.rankTagHtml('xenos'), /Any rank/);
|
|
109
|
+
assert.match(T.rankTagHtml('metic'), /Metic\+/);
|
|
110
|
+
assert.match(T.rankTagHtml('archon'), /Archon</, 'archon takes no + suffix');
|
|
111
|
+
assert.doesNotMatch(T.rankTagHtml('<img src=x onerror=1>'), /<img/);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(`\ntask-lib tests: ${passed} passed, ${failed} failed`);
|
|
115
|
+
if (failed) process.exit(1);
|