@bongos/core 1.19.486 → 1.19.488

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/tests/modules.mjs CHANGED
@@ -140,6 +140,21 @@ test('disciplines: vanilla offers engineer + ideator (core); art-pipeline adds a
140
140
  assert.deepEqual(enabledDisciplines(m), ['engineer', 'ideator', 'artist']);
141
141
  });
142
142
 
143
+ test('disciplines: ui-design contributes `ui` — the manifest key IS the unlock (task 1003487)', () => {
144
+ // enabledDisciplines() already merged contributes.disciplines; ui-design simply
145
+ // never declared any, so the craft existed in the DB CHECK, in discipline-modes
146
+ // and on tasks while staying unreachable from every surface a builder chooses with.
147
+ if (!EFFECTIVE['ui-design']) return; // only meaningful where the module is installed
148
+ assert.deepEqual(EFFECTIVE['ui-design'].contributes.disciplines, ['ui'],
149
+ 'the module declares the craft it owns');
150
+ const on = resolveModules({ instance: { modules: { 'ui-design': true } }, registry: EFFECTIVE });
151
+ assert.ok(enabledDisciplines(on).includes('ui'),
152
+ 'a builder can save it, because updateBuilderDisciplines validates against this very list');
153
+ // and it withdraws with the module, exactly as artist does with art-pipeline
154
+ const off = resolveModules({ instance: { modules: { 'ui-design': false } }, registry: EFFECTIVE });
155
+ assert.ok(!enabledDisciplines(off).includes('ui'), 'module off => craft withdrawn');
156
+ });
157
+
143
158
  test('clientModules is a safe boolean projection covering every known module + disciplines', () => {
144
159
  const c = clientModules(resolveModules({ instance: OTB, registry: SAMPLE_REG }));
145
160
  for (const k of KNOWN_MODULES) assert.equal(typeof c.enabled[k], 'boolean');
@@ -180,5 +195,8 @@ test('a branded sibling (GDS_BRANDING_FILE set, no GDS_MODULES_FILE) defaults to
180
195
  // A non-default brand must fall through to the neutral (all-off) module set.
181
196
  const m = loadModules({ env: { GDS_BRANDING_FILE: 'config/branding.cloudbongos.json' } });
182
197
  for (const k of KNOWN_MODULES) assert.equal(m[k], false, `${k} must be off for a branded sibling`);
183
- assert.deepEqual(enabledDisciplines(m), ['engineer', 'ideator']);
198
+ // The two core crafts, plus 'ui' from ui-design — which is default-on for EVERY
199
+ // instance ("every instance has a face") and is not one of KNOWN_MODULES, so the
200
+ // all-off assertion above never reaches it (task 1003487).
201
+ assert.deepEqual(enabledDisciplines(m), ['engineer', 'ideator', 'ui']);
184
202
  });
@@ -68,6 +68,82 @@ t('loadFloors: real config keeps floor >= 2 per discipline (a spare on every cla
68
68
  }
69
69
  });
70
70
 
71
+ // ---- the ui craft is stocked, so a ui-only newcomer is never starved (task 1003487) ----
72
+ t('ui: the craft is offered, has chores, and none of them needs a browser', () => {
73
+ assert.ok(r.validDisciplines().has('ui'),
74
+ 'ui-design is default-on, so enabledDisciplines() offers ui and restockAll will stamp it');
75
+ assert.ok(r.templateDisciplines().has('ui'), 'ui has chores defined, so restockDiscipline accepts it');
76
+ const chores = r.loadTemplates().ui;
77
+ assert.ok(Array.isArray(chores) && chores.length >= 1, 'at least one ui chore exists');
78
+ for (const c of chores) {
79
+ assert.ok(c.template_id && c.version_id && c.title, `${c.template_id || '?'} has id/version/title`);
80
+ assert.equal(c.discipline, 'ui');
81
+ assert.ok(c.est_minutes <= 60, `${c.template_id} is inside the newcomer scope ceiling`);
82
+ // Xenos-safe: a design chore necessarily READS the branding pack, so the
83
+ // property that matters is that every one of them says, in its done_when,
84
+ // that the world and the pipeline core are not what gets edited.
85
+ const dw = (c.done_when || []).join(' ');
86
+ assert.ok(/NOT modified|were untouched|untouched/i.test(dw),
87
+ `${c.template_id} states what must stay untouched`);
88
+ assert.ok(/permission|infra|migration/i.test(dw),
89
+ `${c.template_id} names the protected core as off-limits`);
90
+ }
91
+ // The kit needs Chrome/Edge, which a newcomer's machine may not have — so at
92
+ // least one ui chore must be verifiable without a browser, or the craft starves
93
+ // exactly the people it was opened for.
94
+ assert.ok(chores.some((c) => !/kit\/render\.js/.test((c.done_when || []).join(' '))),
95
+ 'a ui newcomer with no browser still has a chore they can finish');
96
+ });
97
+
98
+ t('ui: the floor config stocks the craft', () => {
99
+ assert.ok(r.loadFloors().ui >= 1, 'ui declares a standing pool, so the shelf refills');
100
+ });
101
+
102
+ // ---- one file read per operation (task 1003487 re-grade: the efficiency finding) ----
103
+ // The templates file was being re-read and re-parsed by every helper on the path —
104
+ // templateDisciplines() and loadTemplates() back to back per call, and once per
105
+ // craft inside restockAll. These pin the single-read contract so it cannot regress.
106
+ {
107
+ const nodefs = require('node:fs');
108
+ const realRead = nodefs.readFileSync;
109
+ let reads = 0;
110
+ nodefs.readFileSync = (p, ...rest) => {
111
+ if (String(p).includes('newcomer-floor.templates.json')) reads += 1;
112
+ return realRead(p, ...rest);
113
+ };
114
+ try {
115
+ reads = 0;
116
+ await r.restockForClaim({ source: 'not-a-chore', discipline: 'ui' });
117
+ assert.equal(reads, 0, 'a non-chore task is rejected before any file read');
118
+
119
+ reads = 0;
120
+ const byDisc = r.loadTemplates();
121
+ assert.equal(reads, 1, 'loadTemplates reads once');
122
+ r.templateDisciplines(byDisc);
123
+ assert.equal(reads, 1, 'templateDisciplines re-uses an already-loaded map');
124
+ await r.restockDiscipline('bogus-craft', byDisc);
125
+ assert.equal(reads, 1, 'restockDiscipline re-uses it too');
126
+ } finally {
127
+ nodefs.readFileSync = realRead;
128
+ }
129
+ passed += 1;
130
+ console.log(' PASS restock reads newcomer-floor.templates.json once per operation');
131
+ }
132
+
133
+ t('restockForClaim threads its loaded map into the no-match fallback', () => {
134
+ // The read-counting block above drives the helpers directly; this pins the real
135
+ // call chain it cannot reach without a database — the fallback branch taken when
136
+ // a chore's source_ref does not resolve to a known template.
137
+ const src = require('node:fs').readFileSync(
138
+ new URL('../modules/onboarding/newcomer-restock.js', import.meta.url), 'utf8');
139
+ const body = src.slice(src.indexOf('async function restockForClaim'),
140
+ src.indexOf('async function restockAll'));
141
+ assert.ok(/const byDiscipline = loadTemplates\(\);/.test(body), 'it loads the templates once');
142
+ assert.ok(!/loadTemplates\(\)\[/.test(body), 'and never re-indexes a second fresh load');
143
+ assert.ok(/restockDiscipline\(task\.discipline, byDiscipline\)/.test(body),
144
+ 'the fallback re-uses that map rather than letting the default param re-read the file');
145
+ });
146
+
71
147
  // ---- restockForClaim: skip path is DB-free (awaited) ----
72
148
  // (returns an array of per-template results; the skip path is a 1-element array.)
73
149
  assert.deepEqual(await r.restockForClaim(null), [{ skipped: true }]);