@bongos/core 1.21.91 → 1.21.92
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 +39 -39
- package/docs/copy-inventory.md +357 -367
- package/docs/copy-registry.json +559 -641
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +15 -18
- package/docs/page-readings.json +1235 -918
- package/modules/public-landing/public/projects-hall-live.states.json +6 -35
- package/modules/public-landing/public/projects.html +1019 -983
- package/modules/public-landing/public/projects.probes.json +15 -18
- package/modules/public-landing/public/projects.states.json +22 -25
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/onboard.mjs +8 -8
- package/tests/project_invite_ui.mjs +6 -5
- package/tests/projects_hub.mjs +77 -98
- package/tests/projects_hub_app_status.mjs +3 -3
- package/tests/projects_hub_app_step.mjs +8 -8
- package/tests/projects_hub_cloud_hop.mjs +29 -53
- package/tests/projects_hub_look.mjs +29 -39
- package/tests/projects_hub_module_picker.mjs +32 -39
- package/tests/projects_hub_pre_uat.mjs +11 -14
- package/tests/wizard_demo.mjs +30 -25
- package/tests/wizard_draft_resume.mjs +24 -32
- package/tests/wizard_front_door.mjs +91 -243
- package/tests/wizard_intent_resume.mjs +28 -48
- package/tests/wizard_one_page.mjs +201 -0
- package/tests/wizard_physics.mjs +46 -41
- package/tests/wizard_physics_more.mjs +71 -106
- package/tests/wizard_ship_mode.mjs +1 -1
- package/tests/wizard_six_screens.mjs +0 -129
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
// projects.html's one big inline <script> — so the block is sliced out by its
|
|
18
18
|
// own comment sentinel (up to the repo picker that follows it) and run with the
|
|
19
19
|
// things it closes over ($, root, esc, state, saveDraft, paintSetup) supplied by the
|
|
20
|
-
// harness.
|
|
21
|
-
// sliced out beside it, so
|
|
20
|
+
// harness. Part 02's row machinery (ROWS … openRow/closeRow and the delegated click) is
|
|
21
|
+
// sliced out beside it, so opening and closing the row runs the page's own code. Both slices
|
|
22
22
|
// are assertions: rename an edge and this file fails loudly rather than quietly
|
|
23
23
|
// testing nothing.
|
|
24
24
|
//
|
|
@@ -61,7 +61,8 @@ const REND = ' /* what each row says: its value';
|
|
|
61
61
|
const rfrom = HUB.indexOf(RSTART), rto = HUB.indexOf(REND, rfrom);
|
|
62
62
|
assert.ok(rfrom > -1 && rto > rfrom, 'the review rows are sliced by ROWS and rowText');
|
|
63
63
|
const REVIEW = HUB.slice(rfrom, rto).replace(/\r\n/g, '\n');
|
|
64
|
-
assert.ok(REVIEW.includes('function
|
|
64
|
+
assert.ok(REVIEW.includes('function openRow(key)') && REVIEW.includes('function firstInvalidRow()'), 'and they carry the rows\' Change and Create\'s check');
|
|
65
|
+
assert.doesNotMatch(REVIEW, /function confirmRow\(|function confirmAllRows\(/, 'and nothing that confirms a row (task 1004648)');
|
|
65
66
|
|
|
66
67
|
// The payload GET /provisioning/starter-bundles serves (ADR 0237 + task 1002339).
|
|
67
68
|
// Deliberately a FIXTURE and not a live require: this asserts the picker reads
|
|
@@ -185,8 +186,8 @@ function whyOf(html) {
|
|
|
185
186
|
function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
186
187
|
const els = new Map();
|
|
187
188
|
const $ = (id) => { if (!els.has(id)) els.set(id, makeEl(id)); return els.get(id); };
|
|
188
|
-
// step
|
|
189
|
-
const state = { step:
|
|
189
|
+
// step 1 is the page, where part 02's Extras row lives (task 1004648)
|
|
190
|
+
const state = { step: 1, projType: null, teamShape: null, modules: null, ...seed };
|
|
190
191
|
const rows = {};
|
|
191
192
|
const rowFor = (sel) => { const m = /data-row="([a-z]+)"/.exec(sel); return m ? (rows[m[1]] = rows[m[1]] || makeRow(m[1])) : null; };
|
|
192
193
|
let saves = 0;
|
|
@@ -207,6 +208,7 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
|
207
208
|
phys: () => ({ answers: { framing: 'managed' } }),
|
|
208
209
|
validateCode: () => true, validateAddress: () => true, validateRender: () => true,
|
|
209
210
|
checkSlugAvailable: () => {}, renderAddrPanel: () => {}, syncHome: () => {}, syncAppStep: () => {}, syncRenderSetup: () => {},
|
|
211
|
+
syncShipStep: () => {}, renderLook: () => {}, paintCreate: () => {},
|
|
210
212
|
API: '/api/bongos',
|
|
211
213
|
fetch: () => { fetches++; return Promise.resolve(fetchOk
|
|
212
214
|
? { ok: true, json: () => Promise.resolve(payload) }
|
|
@@ -247,13 +249,11 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
|
247
249
|
};
|
|
248
250
|
band._handlers.click({ target });
|
|
249
251
|
},
|
|
250
|
-
// the
|
|
251
|
-
//
|
|
252
|
-
looksRight: () => sandbox.confirmAllRows(),
|
|
252
|
+
// Change opens the Extras row, and Done — the same control, open — closes it;
|
|
253
|
+
// Create's own check is firstInvalidRow (task 1004648)
|
|
253
254
|
change: () => rowClick('data-row-chg'),
|
|
254
255
|
use: () => rowClick('data-row-chg'),
|
|
255
|
-
|
|
256
|
-
unconfirmed: () => [...sandbox.unconfirmedRows()],
|
|
256
|
+
blocking: () => sandbox.firstInvalidRow(),
|
|
257
257
|
forCreate: () => sandbox.modulesForCreate(),
|
|
258
258
|
picked: () => sandbox.pickedModules(),
|
|
259
259
|
// BV1.R21 (task 1002340) — the search box. type() is the real `input` event
|
|
@@ -344,21 +344,21 @@ test('switching every module off is a real answer, not a reset to the bundle', a
|
|
|
344
344
|
assert.deepEqual(h.rows().filter((r) => r.pressed), [], 'and the panel shows it');
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
-
test('an
|
|
347
|
+
test('an untouched row answers NOTHING, and Create does not wait for it (task 1004648)', async () => {
|
|
348
348
|
// Arrange
|
|
349
349
|
const h = boot({ state: { projType: 'game' } });
|
|
350
350
|
await h.open();
|
|
351
351
|
|
|
352
|
-
// Assert — the row is shown prefilled, and
|
|
352
|
+
// Assert — the row is shown prefilled, and nothing is said on the owner's behalf:
|
|
353
353
|
// the preselect and the server's fallback are the same declaration (ADR 0237)
|
|
354
354
|
assert.equal(h.state.modules, null, 'nothing is answered on the owner\'s behalf');
|
|
355
355
|
assert.equal(h.forCreate(), undefined, 'so a create now would carry no modules key');
|
|
356
356
|
assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'while the set they are shown is still the set they get');
|
|
357
|
-
assert.
|
|
357
|
+
assert.equal(h.blocking(), null, 'and no row needs confirming before Create: the summary wins');
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
-
test('
|
|
361
|
-
// A visitor who changed something and then
|
|
360
|
+
test('Change, a toggle, then Done: the toggle stands as the answer', async () => {
|
|
361
|
+
// A visitor who changed something and then closed the row meant "this", not "throw that away".
|
|
362
362
|
const h = boot({ state: { projType: 'game' } });
|
|
363
363
|
await h.open();
|
|
364
364
|
h.change();
|
|
@@ -367,19 +367,16 @@ test('Edit, a toggle, then Done: the toggle stands as the answer', async () => {
|
|
|
367
367
|
h.use();
|
|
368
368
|
|
|
369
369
|
assert.deepEqual(h.state.modules, ['agents', 'npm-release', 'discord']);
|
|
370
|
-
assert.
|
|
370
|
+
assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord'], 'and rides the create request');
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
test('
|
|
373
|
+
test('opening and closing the row without a toggle answers nothing: the suggestion stays the server\'s', async () => {
|
|
374
374
|
const h = boot({ state: { projType: 'game' } });
|
|
375
375
|
await h.open();
|
|
376
|
-
|
|
377
|
-
h.
|
|
378
|
-
|
|
379
|
-
assert.
|
|
380
|
-
assert.deepEqual(h.forCreate(), ['npm-release', 'discord'], 'and rides the create request');
|
|
381
|
-
assert.equal(h.confirmed(), true);
|
|
382
|
-
assert.ok(!h.unconfirmed().includes('extras'), 'and Create no longer waits on it');
|
|
376
|
+
h.change();
|
|
377
|
+
h.use();
|
|
378
|
+
assert.equal(h.state.modules, null, 'looking is not answering');
|
|
379
|
+
assert.equal(h.forCreate(), undefined, 'so the server applies the same bundle the row showed');
|
|
383
380
|
});
|
|
384
381
|
|
|
385
382
|
test('changing the project type re-preselects while nothing has been said, and never after', async () => {
|
|
@@ -419,14 +416,12 @@ test('a catalog that will not load degrades to a clause — it never blocks a pr
|
|
|
419
416
|
assert.deepEqual(h.rows(), [], 'no rows are invented');
|
|
420
417
|
assert.match(h.coreNote(), /couldn’t load/, 'the panel says so in the owner\'s words');
|
|
421
418
|
assert.equal(h.state.modules, null, 'nothing is answered on their behalf');
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
h.looksRight();
|
|
426
|
-
assert.equal(h.confirmed(), true, 'the wizard still walks on');
|
|
419
|
+
// A failed read must never block Create, and the row said "the suggested set", so Create
|
|
420
|
+
// must not file an EMPTY list — that would switch off the very bundle the server applies.
|
|
421
|
+
assert.equal(h.blocking(), null, 'Create still goes on');
|
|
427
422
|
// (through JSON: a vm-born array never strictly equals one born here, so a bare
|
|
428
423
|
// notDeepEqual would pass whatever the page did)
|
|
429
|
-
assert.notEqual(JSON.stringify(h.forCreate()), '[]', 'and
|
|
424
|
+
assert.notEqual(JSON.stringify(h.forCreate()), '[]', 'and "the suggested set" never files "none"');
|
|
430
425
|
});
|
|
431
426
|
|
|
432
427
|
test('every rendered value goes through the page escaper', async () => {
|
|
@@ -460,11 +455,11 @@ test('the catalog read is LAZY and asked once — /projects serves four views fr
|
|
|
460
455
|
});
|
|
461
456
|
|
|
462
457
|
test('the Extras row names the set even when the picker was never opened (task 1002339)', async () => {
|
|
463
|
-
// A resumed draft lands straight on the
|
|
458
|
+
// A resumed draft lands straight on the page, so the catalog has not loaded
|
|
464
459
|
// and the module NAMES are not known. "None — just the core" would be a false
|
|
465
460
|
// promise before Create: the server is about to apply the type's bundle. The row
|
|
466
461
|
// says that instead, then repaints when the read lands.
|
|
467
|
-
const h = boot({ state: { projType: 'game', step:
|
|
462
|
+
const h = boot({ state: { projType: 'game', step: 1 } }); // 1 = the page since task 1004648 (4 = the review before it)
|
|
468
463
|
|
|
469
464
|
// Arrange + Act — the review asks for the value before the read can answer
|
|
470
465
|
const first = h.extrasRow();
|
|
@@ -554,18 +549,16 @@ test('changing the team shape later re-preselects, with no second read', async (
|
|
|
554
549
|
assert.equal(h.fetches(), 1, 'and the answer cost no round trip');
|
|
555
550
|
});
|
|
556
551
|
|
|
557
|
-
test('after a team answer the
|
|
552
|
+
test('after a team answer the untouched row still answers nothing, and shows the tailored set', async () => {
|
|
558
553
|
// ADR 0240 §2's property, extended to the interview's answer (ADR 0243): the
|
|
559
|
-
// preselect and the server's fallback are the same declaration, so an
|
|
560
|
-
// row must still answer NOTHING,
|
|
554
|
+
// preselect and the server's fallback are the same declaration, so an untouched
|
|
555
|
+
// row must still answer NOTHING, while showing the tailored set the server will apply.
|
|
561
556
|
const h = boot({ state: { projType: 'game', teamShape: 'solo' }, payload: TAILORED });
|
|
562
557
|
await h.open();
|
|
563
558
|
|
|
564
|
-
assert.equal(h.state.modules, null, '
|
|
559
|
+
assert.equal(h.state.modules, null, 'untouched answers nothing, tailored or not');
|
|
565
560
|
assert.equal(h.forCreate(), undefined, 'so the create request would carry no modules key');
|
|
566
561
|
assert.deepEqual(h.picked(), ['discord'], 'while the set they were shown is still the set they get');
|
|
567
|
-
h.looksRight();
|
|
568
|
-
assert.deepEqual(h.forCreate(), ['discord'], 'and confirming files exactly that set');
|
|
569
562
|
});
|
|
570
563
|
|
|
571
564
|
test('once the owner has answered, the reasons go — the panel stops arguing', async () => {
|
|
@@ -873,7 +866,7 @@ test('with nothing switched on the count says only what is shown', async () => {
|
|
|
873
866
|
});
|
|
874
867
|
|
|
875
868
|
test('the Extras row names the hidden picks too — a filter never reaches the create request', async () => {
|
|
876
|
-
const h = boot({ state: { projType: 'game', step:
|
|
869
|
+
const h = boot({ state: { projType: 'game', step: 1 } }); // 1 = the page since task 1004648 (4 = the review before it)
|
|
877
870
|
await h.open();
|
|
878
871
|
h.click('agents');
|
|
879
872
|
assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord']);
|
|
@@ -232,16 +232,13 @@ test('the wizard’s 401 recoveries keep the repo scope they already granted', (
|
|
|
232
232
|
});
|
|
233
233
|
|
|
234
234
|
test('a resumed draft past the code row still probes repo access before Create', () => {
|
|
235
|
-
// task
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
// the
|
|
239
|
-
|
|
240
|
-
assert.match(SCRIPT,
|
|
241
|
-
|
|
242
|
-
assert.ok(SCRIPT.includes(
|
|
243
|
-
`else if (step > ${REVIEW} && step < ${DONE} && authKnown && repoAccess === null) ensureRepoAccess();`),
|
|
244
|
-
'every screen between the review and the done panel re-probes');
|
|
235
|
+
// task 1004648: the Home step is part 02's Code row on the one page, so the page probes on
|
|
236
|
+
// entry — Create would otherwise file a repo it promised to make but never did
|
|
237
|
+
// (ensureFreshRepo skips the create whenever repoAccess is not true). There is no screen
|
|
238
|
+
// between the page and Create any more for a resumed draft to land past it on.
|
|
239
|
+
assert.match(SCRIPT, /if \(step === 1\) paintPage\(\);/, 'the page paints on entry');
|
|
240
|
+
assert.match(SCRIPT, /function paintPage\(\) \{[\s\S]*?if \(authKnown\) ensureRepoAccess\(\);/, 'and the page probes on entry');
|
|
241
|
+
assert.match(SCRIPT, /function onAuth\(\) \{[\s\S]*?if \(currentView === 'new'\) enter\(false\);/, 'and again once /me settles, so a pre-auth paint is caught up');
|
|
245
242
|
});
|
|
246
243
|
|
|
247
244
|
test('a half-typed custom domain says why Done refuses it; a taken handle silences the format cheer', () => {
|
|
@@ -257,7 +254,7 @@ test('no wall-clock promises, no operator vocabulary on the stranger’s path',
|
|
|
257
254
|
/* Since ADR 0345 (task 1004351) the APP has its own row; since task 1004504 the review is
|
|
258
255
|
the row-by-row confirm, and that row speaks the owner's words. The assertion still
|
|
259
256
|
guards the same property this test is about: owner vocabulary, and no wall-clock promise. */
|
|
260
|
-
assert.match(HUB, /<div class="setRow" data-row="app">\s*<span class="k">
|
|
257
|
+
assert.match(HUB, /<div class="setRow" data-row="app">\s*<span class="k">Hosting<\/span>/, 'part 02 names the app\'s hosting in plain words');
|
|
261
258
|
assert.ok(SCRIPT.includes("return { v: appPlatformChoice() === 'render' ? 'Render' : 'Decide later', s: appRowDetail() };"), 'its value is a place or "Decide later", never a release name');
|
|
262
259
|
assert.doesNotMatch(SCRIPT, /current release|release channel/, 'no release vocabulary on the stranger\'s path');
|
|
263
260
|
assert.doesNotMatch(HUB, /\/new-project<\/code> assistant/, 'the done panel’s closing line');
|
|
@@ -480,9 +477,9 @@ test('decision 5 — the Type step says where the type can be changed (task 1003
|
|
|
480
477
|
// design of record's words, which make no promise about changing it. What must hold: the
|
|
481
478
|
// narrowed promise never comes back, and any change-later promise carries its address.
|
|
482
479
|
assert.doesNotMatch(HUB, /id="panel-type"/, 'the Type step is gone');
|
|
483
|
-
const what = (HUB.match(/<
|
|
484
|
-
assert.ok(what, '
|
|
485
|
-
assert.match(what, /<span id="typeLbl">Kind<\/span>/, 'the kind is asked
|
|
480
|
+
const what = (HUB.match(/<div class="part" id="part-project"[\s\S]*?\n {14}<\/div>\n/) || [''])[0];
|
|
481
|
+
assert.ok(what, 'part 01 exists (task 1004648)');
|
|
482
|
+
assert.match(what, /<span id="typeLbl">Kind<\/span>/, 'the kind is asked in part 01, Your project');
|
|
486
483
|
assert.doesNotMatch(what, /stays as you pick it/, 'the narrowed promise must not survive');
|
|
487
484
|
if (/change it later/.test(what)) {
|
|
488
485
|
assert.match(what, /change it later from the project’s own page/, 'a change-later promise names WHERE — a promise with an address');
|
package/tests/wizard_demo.mjs
CHANGED
|
@@ -238,42 +238,46 @@ function bootHub() {
|
|
|
238
238
|
const b = new El(`step-${k}${by}`); b.attrs['data-demo-step'] = k; b.attrs['data-by'] = by; return b;
|
|
239
239
|
});
|
|
240
240
|
const shown = [];
|
|
241
|
-
// the
|
|
242
|
-
const state = { step: 1, demo: null, description: '', teamShape: null, termsAck:
|
|
243
|
-
const sandbox = { $, state, saveDraft() {}, show: (n) => shown.push(n),
|
|
241
|
+
// the demo is a small link under the heading (task 1004648): it waits for nothing
|
|
242
|
+
const state = { step: 1, demo: null, description: '', teamShape: null, termsAck: false };
|
|
243
|
+
const sandbox = { $, state, saveDraft() {}, show: (n) => shown.push(n), root: { querySelectorAll: () => steppers } };
|
|
244
244
|
vm.createContext(sandbox);
|
|
245
245
|
vm.runInContext(SLICE, sandbox);
|
|
246
246
|
const click = (id) => $(id).listeners.click.call($(id));
|
|
247
247
|
return { $, state, shown, steppers, click, sandbox };
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
test('the
|
|
250
|
+
test('the small link opens the demo in place, the steppers stay in their limits, and the first read follows them', () => {
|
|
251
251
|
const h = bootHub();
|
|
252
|
-
h.
|
|
252
|
+
h.$('demoSetup').hidden = true;
|
|
253
253
|
h.click('wizDemo');
|
|
254
|
-
assert.equal(h.$('
|
|
255
|
-
assert.equal(h.$('forkIntro').hidden, false, 'the doors stay');
|
|
256
|
-
h.state.termsAck = true;
|
|
257
|
-
h.click('wizDemo');
|
|
258
|
-
assert.equal(h.$('forkIntro').hidden, true);
|
|
259
|
-
assert.equal(h.$('demoSetup').hidden, false);
|
|
254
|
+
assert.equal(h.$('demoSetup').hidden, false, 'the demo opens at the top of part 01');
|
|
260
255
|
assert.equal(h.$('wizDemo').attrs['aria-expanded'], 'true');
|
|
261
|
-
assert.equal(h.$('wizBack').hidden, false, 'the one Back walks back to the doors');
|
|
262
256
|
for (let i = 0; i < 20; i += 1) h.steppers[0].listeners.click.call(h.steppers[0]);
|
|
263
257
|
assert.equal(h.$('demoPeople').textContent, '12', 'people stop at twelve');
|
|
264
258
|
for (let i = 0; i < 20; i += 1) h.steppers[3].listeners.click.call(h.steppers[3]);
|
|
265
259
|
assert.equal(h.$('demoHours').textContent, '1', 'hours stop at one');
|
|
266
260
|
assert.equal(h.$('demoFitSum').textContent, '12 people × 1 hour = 12 builder-hours.');
|
|
267
261
|
assert.equal(h.$('demoFitVerdict').textContent, demo.fitCheck(12, 1).sentence);
|
|
268
|
-
h.
|
|
269
|
-
assert.equal(h.$('
|
|
262
|
+
h.click('wizDemo');
|
|
263
|
+
assert.equal(h.$('demoSetup').hidden, true, 'the same link closes it again');
|
|
270
264
|
assert.equal(h.$('wizDemo').attrs['aria-expanded'], 'false');
|
|
271
|
-
assert.
|
|
272
|
-
assert.match(HUB,
|
|
273
|
-
|
|
265
|
+
assert.doesNotMatch(HUB, /id="demoReady"|id="demoRing"|id="demoNext1"|id="demoBack1"|id="demoBack2"|id="demoStageCap"|id="forkIntro"/, 'no second demo screen and no fork');
|
|
266
|
+
assert.match(HUB, /<p class="pageSub" id="pageSub">Three parts, one page\. Just looking\? <button type="button" class="linklike" id="wizDemo" aria-controls="demoSetup" aria-expanded="false">Try a small demo instead<\/button>\.<\/p>/,
|
|
267
|
+
'the demo is a small link under the heading, in the mockup\'s words');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('"Make a real project instead" drops the demo and its answers, and the page stays where it is', () => {
|
|
271
|
+
const h = bootHub();
|
|
272
|
+
h.state.demo = { people: 3, hours_each: 4 };
|
|
273
|
+
h.click('wizDemo');
|
|
274
|
+
h.click('demoCancel');
|
|
275
|
+
assert.equal(h.state.demo, null, 'a walked-back demo does not ride the create');
|
|
276
|
+
assert.equal(h.$('demoSetup').hidden, true);
|
|
277
|
+
assert.deepEqual(h.shown, [1], 'the page repaints: the quick checks come back');
|
|
274
278
|
});
|
|
275
279
|
|
|
276
|
-
test('
|
|
280
|
+
test('"Use these" marks the page a demo: the idea, the team, the invites — the rest of the page unchanged', () => {
|
|
277
281
|
const h = bootHub();
|
|
278
282
|
h.click('wizDemo');
|
|
279
283
|
assert.equal(h.$('demoInvites').hidden, false, 'the invites sit on the same screen');
|
|
@@ -286,14 +290,15 @@ test('the demo screen\'s Continue carries the demo into the ordinary wizard: the
|
|
|
286
290
|
assert.equal(h.state.description, 'A shared reading list.', 'the idea is the description');
|
|
287
291
|
assert.equal(h.$('f-desc').value, 'A shared reading list.', 'and the sentence field shows it');
|
|
288
292
|
assert.equal(h.state.teamShape, 'small-team');
|
|
289
|
-
assert.
|
|
290
|
-
assert.deepEqual(h.shown, [
|
|
291
|
-
assert.match(HUB, /<input type="text" id="demoIdea" maxlength="600"/, 'the idea is one line
|
|
292
|
-
assert.match(HUB, /<button type="button" class="btn" id="demoStart">
|
|
293
|
+
assert.equal(h.$('demoSetup').hidden, true, 'the demo closes once used');
|
|
294
|
+
assert.deepEqual(h.shown, [1], 'and the page repaints, marked demo (task 1004648)');
|
|
295
|
+
assert.match(HUB, /<input type="text" id="demoIdea" maxlength="600"/, 'the idea is one line');
|
|
296
|
+
assert.match(HUB, /<button type="button" class="btn small" id="demoStart">Use these<\/button>/);
|
|
293
297
|
});
|
|
294
298
|
|
|
295
|
-
test('a demo
|
|
296
|
-
assert.match(HUB,
|
|
299
|
+
test('a demo\'s quick checks stand down and it sends only its team shape', () => {
|
|
300
|
+
assert.match(HUB, /\$\('checksMain'\)\.hidden = physStopped \|\| !!state\.demo;/, 'the checks stand down for a demo');
|
|
301
|
+
assert.match(HUB, /function physScreen\(\) \{\s*if \(state\.demo\) return Promise\.resolve\(true\);/, 'and there is nothing of its own to screen');
|
|
297
302
|
const fn = (HUB.match(/function createDetail\(\) \{[\s\S]*?\n {4}\}/) || [null])[0];
|
|
298
303
|
assert.ok(fn, 'createDetail() was found');
|
|
299
304
|
const box = { physDetail: () => ({ framing: 'hybrid', reward_intents: ['cash'] }), state: {} };
|
|
@@ -308,7 +313,7 @@ test('a demo skips the quick checks and sends only its team shape', () => {
|
|
|
308
313
|
|
|
309
314
|
test('the create sends the demo, the recap names it, and the project page asks the question', () => {
|
|
310
315
|
assert.match(HUB, /demo: state\.demo \? \{ people: state\.demo\.people, hours_each: state\.demo\.hours_each \} : undefined,/);
|
|
311
|
-
assert.match(HUB, /if \(state\.demo\) bits\.push\('demo'\);/, 'the
|
|
316
|
+
assert.match(HUB, /if \(state\.demo\) bits\.push\('demo'\);/, 'the recap beside Create says it is a demo');
|
|
312
317
|
assert.match(HUB, /<p class="demoKeepH">Keep your planet\?<\/p>/);
|
|
313
318
|
assert.match(HUB, /data-demo-settle="carried">Carry it over</);
|
|
314
319
|
assert.match(HUB, /data-demo-settle="kept">Keep it as a demo</);
|
|
@@ -39,7 +39,7 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
39
39
|
const HUB = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'projects.html'), 'utf8');
|
|
40
40
|
|
|
41
41
|
// ── slice 1 · the step-resolution block inside enter() ────────────────────────────
|
|
42
|
-
const STEP_RE = /var
|
|
42
|
+
const STEP_RE = /var resumeStep = state\.step;[\s\S]*?if \(focus\) \$\('newName'\)\.focus\(\);/;
|
|
43
43
|
const stepSlice = (HUB.match(STEP_RE) || [null])[0];
|
|
44
44
|
|
|
45
45
|
test('the step-resolution slice is still findable (a rename must fail loudly, not silently pass)', () => {
|
|
@@ -69,35 +69,28 @@ function runStep({ authKnown, draftStep }) {
|
|
|
69
69
|
return { rendered, persisted: JSON.parse(store.get('draft')), state };
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
test('pre-auth: the placeholder renders
|
|
73
|
-
// step
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
assert.
|
|
72
|
+
test('pre-auth: the placeholder renders the page but the draft KEEPS the real step', () => {
|
|
73
|
+
// step 2 is the created page since task 1004648 — its own branch above this slice takes it
|
|
74
|
+
// once /me answers, so the placeholder must not overwrite it
|
|
75
|
+
const { rendered, persisted } = runStep({ authKnown: false, draftStep: 2 });
|
|
76
|
+
assert.deepEqual(rendered, [1], 'renders the page (step 1) before identity settles');
|
|
77
|
+
assert.equal(persisted.step, 2, 'the draft must not be overwritten with the placeholder');
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
test('pre-auth: the in-memory step is restored too, so onAuth() can re-enter to it', () => {
|
|
80
81
|
// onAuth() calls enter() again and reads state.step from MEMORY, not from storage — so
|
|
81
82
|
// restoring only the stored copy would fix nothing.
|
|
82
|
-
for (const step of [
|
|
83
|
+
for (const step of [0, 1, 2]) {
|
|
83
84
|
const { state } = runStep({ authKnown: false, draftStep: step });
|
|
84
85
|
assert.equal(state.step, step, `step ${step} comes back`);
|
|
85
86
|
}
|
|
86
87
|
});
|
|
87
88
|
|
|
88
|
-
test('post-auth: the
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
test('a draft already at step 1 is unchanged, and an out-of-range step is not resurrected', () => {
|
|
95
|
-
assert.equal(runStep({ authKnown: false, draftStep: 1 }).persisted.step, 1);
|
|
96
|
-
// 6 is the done panel since task 1004504 (12 before it, 11 before task 1004421's Look step, 9 before task
|
|
97
|
-
// 1004417's front door, 8 before task 1004351), reached by its own branch above this slice; 0 is the gate.
|
|
98
|
-
// Neither is a resumable wizard position, so the placeholder is allowed to stand.
|
|
99
|
-
for (const bad of [0, 6, 13, 99]) {
|
|
100
|
-
assert.equal(runStep({ authKnown: false, draftStep: bad }).persisted.step, 1, `step ${bad} is not resumed`);
|
|
89
|
+
test('post-auth: the page is rendered, and the draft says so', () => {
|
|
90
|
+
for (const step of [0, 1, 2, 13]) {
|
|
91
|
+
const { rendered, persisted } = runStep({ authKnown: true, draftStep: step });
|
|
92
|
+
assert.deepEqual(rendered, [1], `from step ${step}: one page`);
|
|
93
|
+
assert.equal(persisted.step, 1);
|
|
101
94
|
}
|
|
102
95
|
});
|
|
103
96
|
|
|
@@ -216,6 +209,7 @@ test('the team pill is a plain pick on the Team size row — a second click keep
|
|
|
216
209
|
paintTeam() {}, saveDraft() { saves += 1; }, paintSetup() { paints += 1; },
|
|
217
210
|
unconfirm(keys) { unconfirmed.push(...keys); },
|
|
218
211
|
};
|
|
212
|
+
// task 1004648: nothing is confirmed any more, so nothing is "asked again" — the row repaints
|
|
219
213
|
sandbox.globalThis = sandbox;
|
|
220
214
|
vm.createContext(sandbox);
|
|
221
215
|
vm.runInContext(teamSlice, sandbox);
|
|
@@ -228,8 +222,8 @@ test('the team pill is a plain pick on the Team size row — a second click keep
|
|
|
228
222
|
click('community');
|
|
229
223
|
assert.equal(state.teamShape, 'community', 'a DIFFERENT pill replaces it');
|
|
230
224
|
assert.equal(saves, 3, 'every pick is saved into the draft, so the GitHub round-trip keeps it');
|
|
231
|
-
assert.deepEqual(
|
|
232
|
-
assert.equal(paints, 3, 'and the
|
|
225
|
+
assert.deepEqual(unconfirmed, [], 'no row is un-confirmed: the summary wins (task 1004648)');
|
|
226
|
+
assert.equal(paints, 3, 'and the rows repaint, the suggested extras with them');
|
|
233
227
|
assert.deepEqual([...HUB.matchAll(/data-team="([a-z-]+)"/g)].map((m) => m[1]), ['small-team', 'community'],
|
|
234
228
|
'the row offers who can join a team; one person is the "One person" pill, not a team shape');
|
|
235
229
|
});
|
|
@@ -268,17 +262,15 @@ function runEnter({ draftStep, focus = false, resumeIntent = false, created = {
|
|
|
268
262
|
}
|
|
269
263
|
|
|
270
264
|
test('back from GitHub mid-draft, with an earlier project stored: the draft step, not the old done panel', () => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
assert.equal(log.offer && log.offer.id, 101, 'the earlier project stays one click away on the resume line');
|
|
276
|
-
}
|
|
265
|
+
const log = runEnter({ draftStep: 1 });
|
|
266
|
+
assert.deepEqual(log.created, [], 'the earlier project\'s part 03 is not entered');
|
|
267
|
+
assert.deepEqual(log.shown, [1], 'the draft keeps its place: the page');
|
|
268
|
+
assert.equal(log.offer && log.offer.id, 101, 'the earlier project stays one click away on the resume line');
|
|
277
269
|
});
|
|
278
270
|
|
|
279
|
-
test('the
|
|
280
|
-
assert.equal(runEnter({ draftStep:
|
|
271
|
+
test('the created page still comes back when no second draft is in flight, or on a deliberate nav', () => {
|
|
272
|
+
assert.equal(runEnter({ draftStep: 2 }).created.length, 1, 'a reload on the created page stays on it');
|
|
281
273
|
assert.equal(runEnter({ draftStep: 0 }).created.length, 1, 'no draft position: the watched project');
|
|
282
|
-
assert.deepEqual(runEnter({ draftStep:
|
|
283
|
-
assert.equal(runEnter({ draftStep:
|
|
274
|
+
assert.deepEqual(runEnter({ draftStep: 1, focus: true }).created, [{ id: 101, focus: true }], 'a deliberate New project nav is enterCreated\'s to decide');
|
|
275
|
+
assert.equal(runEnter({ draftStep: 1, resumeIntent: true }).created.length, 1, 'the resume link still resumes');
|
|
284
276
|
});
|