@bongos/core 1.21.30 → 1.21.31
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 +23 -23
- package/docs/copy-inventory.md +366 -381
- package/docs/copy-registry.json +464 -629
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +644 -654
- package/modules/public-landing/public/projects-hall-live.states.json +1 -41
- package/modules/public-landing/public/projects.html +109 -72
- package/modules/public-landing/public/projects.probes.json +5 -5
- package/modules/public-landing/public/projects.states.json +7 -7
- 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/projects_hub.mjs +7 -4
- package/tests/projects_hub_app_step.mjs +4 -3
- package/tests/projects_hub_module_picker.mjs +12 -10
- package/tests/projects_hub_pre_uat.mjs +1 -1
- package/tests/wizard_physics_more.mjs +58 -34
- package/tests/wizard_six_screens.mjs +10 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.31",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -8936,5 +8936,11 @@
|
|
|
8936
8936
|
"id": "1004493",
|
|
8937
8937
|
"text": "The hall's sidebar now follows the mode you pick. Building shows the everyday tabs, Artistry shows Studio, Goals and Your thinking, Ideating shows the Sky, Your thinking and Goals, and Governing is the shortest list: Blockers,"
|
|
8938
8938
|
}
|
|
8939
|
+
],
|
|
8940
|
+
"1.21.31": [
|
|
8941
|
+
{
|
|
8942
|
+
"id": "1004550",
|
|
8943
|
+
"text": "Setting up a new project is quicker: the final check screen now takes one click to accept, with less text and no per-row confirm buttons."
|
|
8944
|
+
}
|
|
8939
8945
|
]
|
|
8940
8946
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.31'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -2316,8 +2316,11 @@ test('the extras picker ships as real markup on the review\'s Extras row and is
|
|
|
2316
2316
|
assert.match(EXTRAS_ROW, /id="modCards"/, 'the band the picker paints its rows into');
|
|
2317
2317
|
assert.match(EXTRAS_ROW, /class="typeCards"/, 'and it is the SAME band the type and team questions use — no new grammar');
|
|
2318
2318
|
assert.match(EXTRAS_ROW, /id="modCoreNote"/, 'the always-on core is stated on the row');
|
|
2319
|
-
// the suggested set is accepted by
|
|
2320
|
-
|
|
2319
|
+
// the suggested set is accepted by a control — the page's "Looks right, continue", or
|
|
2320
|
+
// Done on the open row (task 1004550) — never inferred
|
|
2321
|
+
assert.match(HUB, /<div class="setRow" data-row="extras">[\s\S]*?<button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button>/,
|
|
2322
|
+
'the row can be opened and changed');
|
|
2323
|
+
assert.match(HUB, /<button type="submit" class="btn" id="reviewGo">Looks right, continue<\/button>/,
|
|
2321
2324
|
'accepting the suggested set is a control, not an inference');
|
|
2322
2325
|
// confirming turns the shown set into an answer given; the row's validity never refuses it
|
|
2323
2326
|
const confirm = (SCRIPT.match(/function confirmRow\(key\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
@@ -2397,6 +2400,6 @@ test('the kit stub answers the picker\'s read, so the panel renders in the desig
|
|
|
2397
2400
|
assert.match(stub, /rest === '\/provisioning\/starter-bundles'/, 'the stub serves the picker\'s read');
|
|
2398
2401
|
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
2399
2402
|
assert.ok(probes.some((p) => /the wizard driver \(tasks? 1003346\b/.test(p.name)
|
|
2400
|
-
&& p.steps.some((st) => st[1] === '#
|
|
2401
|
-
'and the end-to-end driver confirms the
|
|
2403
|
+
&& p.steps.some((st) => st[1] === '#reviewGo')),
|
|
2404
|
+
'and the end-to-end driver confirms the review rather than stopping at it (tasks 1004504, 1004550)');
|
|
2402
2405
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/projects_hub_app_step.mjs — the create wizard's "where your app runs" question,
|
|
2
2
|
// EXECUTED (task 1004351, ADR 0345), and its Render setup (task 1004352, ADR 0348). Since
|
|
3
3
|
// task 1004504 the old App step is the review's "App hosting" row: its controls moved into
|
|
4
|
-
// the row whole, and
|
|
4
|
+
// the row whole, and Done (the row's Edit, open) runs the check its Next used to.
|
|
5
5
|
//
|
|
6
6
|
// A static regex cannot tell a guard that works from one that is never consulted, so —
|
|
7
7
|
// as tests/projects_hub_module_picker.mjs does for the picker — this slices the block out
|
|
@@ -162,7 +162,7 @@ test('the row is real markup: Render and Decide later, Decide later the default'
|
|
|
162
162
|
assert.doesNotMatch(PANEL, /Coming soon/, 'and is no longer marked soon');
|
|
163
163
|
assert.match(PANEL, /<input type="radio" name="appPlatform" value="later" checked>/, 'Decide later is the default');
|
|
164
164
|
assert.ok(PANEL.indexOf('value="later"') < PANEL.indexOf('value="render"'), 'Decide later comes first');
|
|
165
|
-
assert.match(
|
|
165
|
+
assert.match(HUB.slice(HUB.indexOf('<div class="setRow" data-row="app">')), /^[\s\S]*?<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button>/, 'Edit opens the row, and reads Done to confirm it (task 1004550)');
|
|
166
166
|
assert.doesNotMatch(HUB, /id="panel-app"/, 'the old App panel is gone');
|
|
167
167
|
});
|
|
168
168
|
|
|
@@ -230,7 +230,8 @@ test('the hall is spelled one way on this page', () => {
|
|
|
230
230
|
test('the key field says what the key is used for, and claims no protection it lacks', () => {
|
|
231
231
|
const setup = (PANEL.match(/<div class="renderSetup"[\s\S]*?<div class="hint" id="h-render"/) || [''])[0];
|
|
232
232
|
assert.match(setup, /<input type="password" id="f-renderKey" autocomplete="off"/, 'masked, and never autofilled');
|
|
233
|
-
assert.match(setup, /
|
|
233
|
+
assert.match(setup, /Used once to create your app on your Render account, then thrown away — not kept, not even in this browser/);
|
|
234
|
+
assert.match(setup, /A key can act on your whole Render account/, 'what a key can reach is still said (task 1004550 shortened the rest)');
|
|
234
235
|
assert.match(setup, /Render bills you directly/);
|
|
235
236
|
assert.match(setup, /href="https:\/\/dashboard\.render\.com\/u\/settings#api-keys"/, 'links to where Render issues keys');
|
|
236
237
|
assert.doesNotMatch(setup, /encrypt|secure|safe|protected/i, 'no protection claim the design does not give (ADR 0327 §2)');
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// tests/projects_hub_module_picker.mjs — the create wizard's module picker,
|
|
2
2
|
// EXECUTED (BV1.R20, task 1002339; goal 1000046 criterion `proj-module-select`).
|
|
3
3
|
// Since task 1004504 the picker is the review's Extras row: its controls moved into the
|
|
4
|
-
// row whole, "Looks right
|
|
5
|
-
// Create waits for every row,
|
|
4
|
+
// row whole, the page's one "Looks right, continue" (or Done on the open row) confirms
|
|
5
|
+
// the set on screen (task 1004550), and there is no Skip — Create waits for every row,
|
|
6
|
+
// this one included.
|
|
6
7
|
//
|
|
7
8
|
// tests/projects_hub.mjs pins this surface statically, on purpose (C7: "real
|
|
8
9
|
// markup, not JS-built"). But the picker is the one part of the wizard whose
|
|
@@ -146,11 +147,11 @@ function makeEl(id) {
|
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
// One review row, shaped like the markup: its hidden .opts
|
|
150
|
+
// One review row, shaped like the markup: its hidden .opts and its one Edit / Done control.
|
|
150
151
|
function makeRow(key) {
|
|
151
152
|
const row = makeEl('row-' + key);
|
|
152
153
|
row.setAttribute('data-row', key);
|
|
153
|
-
const parts = { '.opts': makeEl('opts-' + key), '[data-row-
|
|
154
|
+
const parts = { '.opts': makeEl('opts-' + key), '[data-row-chg]': makeEl('chg-' + key) };
|
|
154
155
|
parts['.opts'].hidden = true;
|
|
155
156
|
row.querySelector = (sel) => parts[sel] || null;
|
|
156
157
|
return row;
|
|
@@ -220,7 +221,7 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
|
220
221
|
// a click inside the review, through its one delegated listener, as the browser sends it
|
|
221
222
|
const rowClick = (attr) => {
|
|
222
223
|
const row = rowFor('#reviewRows .setRow[data-row="extras"]');
|
|
223
|
-
const t = { hasAttribute: (a) => a === attr, closest: (sel) => (sel === '.setRow' ? row : null) };
|
|
224
|
+
const t = { hasAttribute: (a) => a === attr, closest: (sel) => (sel === '.setRow' ? row : null), focus() {} };
|
|
224
225
|
$('reviewRows')._handlers.click({ target: { closest: () => t } });
|
|
225
226
|
};
|
|
226
227
|
return {
|
|
@@ -246,10 +247,11 @@ function boot({ state: seed = {}, fetchOk = true, payload = PAYLOAD } = {}) {
|
|
|
246
247
|
};
|
|
247
248
|
band._handlers.click({ target });
|
|
248
249
|
},
|
|
249
|
-
// "Looks right"
|
|
250
|
-
|
|
250
|
+
// the page's "Looks right, continue" (every row through its own check), Edit to
|
|
251
|
+
// open the Extras row, and Done — the same control, open — to confirm it
|
|
252
|
+
looksRight: () => sandbox.confirmAllRows(),
|
|
251
253
|
change: () => rowClick('data-row-chg'),
|
|
252
|
-
use: () => rowClick('data-row-
|
|
254
|
+
use: () => rowClick('data-row-chg'),
|
|
253
255
|
confirmed: () => !!(state.confirmed && state.confirmed.extras),
|
|
254
256
|
unconfirmed: () => [...sandbox.unconfirmedRows()],
|
|
255
257
|
forCreate: () => sandbox.modulesForCreate(),
|
|
@@ -355,7 +357,7 @@ test('an unconfirmed row answers NOTHING, and Create waits for it (no Skip since
|
|
|
355
357
|
assert.ok(h.unconfirmed().includes('extras'), 'and the review holds Continue until the row is confirmed');
|
|
356
358
|
});
|
|
357
359
|
|
|
358
|
-
test('
|
|
360
|
+
test('Edit, a toggle, then Done: the toggle stands as the answer', async () => {
|
|
359
361
|
// A visitor who changed something and then confirmed meant "this", not "throw that away".
|
|
360
362
|
const h = boot({ state: { projType: 'game' } });
|
|
361
363
|
await h.open();
|
|
@@ -368,7 +370,7 @@ test('Change, a toggle, then Use this: the toggle stands as the answer', async (
|
|
|
368
370
|
assert.equal(h.confirmed(), true, 'and the row is confirmed');
|
|
369
371
|
});
|
|
370
372
|
|
|
371
|
-
test('Looks right CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
|
|
373
|
+
test('Looks right, continue CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
|
|
372
374
|
const h = boot({ state: { projType: 'game' } });
|
|
373
375
|
await h.open();
|
|
374
376
|
|
|
@@ -243,7 +243,7 @@ test('a resumed draft past the code row still probes repo access before Create',
|
|
|
243
243
|
'every screen between the review and the done panel re-probes');
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
-
test('a half-typed custom domain says why
|
|
246
|
+
test('a half-typed custom domain says why Done refuses it; a taken handle silences the format cheer', () => {
|
|
247
247
|
assert.match(SCRIPT, /That doesn’t look like a domain yet \(e\.g\. moonbase\.example\.com\) — fix it, or clear the field to use the address above\./);
|
|
248
248
|
assert.match(SCRIPT, /if \(avail === 'taken' \|\| avail === 'yours' \|\| avail === 'reserved'\) \{ var hs = \$\('h-slug'\); hs\.innerHTML = ' '; hs\.className = 'hint'; \}/);
|
|
249
249
|
});
|
|
@@ -184,9 +184,12 @@ function boot({ check = (body) => ({ stop: false, soft_limits: softLimitsFor(bod
|
|
|
184
184
|
a: () => state.physics.answers,
|
|
185
185
|
pick: (key, val) => q(`[data-phys="${key}"][data-val="${val}"]`)[0].click(),
|
|
186
186
|
rowSays: (key) => [row(key).querySelector('[data-row-v]').textContent, row(key).querySelector('[data-row-s]').textContent],
|
|
187
|
-
|
|
187
|
+
// a row's own check and confirmation, as the page's one "Looks right, continue" runs it
|
|
188
|
+
// per row (task 1004550): there is no per-row Looks right any more
|
|
189
|
+
ok: (key) => vm.runInContext(`confirmRow('${key}')`, sandbox),
|
|
190
|
+
// Edit opens a row; the same control reads Done while it is open, and confirms it
|
|
188
191
|
change: (key) => row(key).querySelector('[data-row-chg]').click(),
|
|
189
|
-
|
|
192
|
+
done: (key) => row(key).querySelector('[data-row-chg]').click(),
|
|
190
193
|
isOpen: (key) => !row(key).querySelector('.opts').hidden,
|
|
191
194
|
go: () => $('reviewGo'),
|
|
192
195
|
settle: async () => { for (let i = 0; i < 4; i += 1) await tick(); },
|
|
@@ -343,9 +346,11 @@ test('Team size: one or many, a start count, and a guessed growth the founder co
|
|
|
343
346
|
assert.deepEqual(w.rowSays('team'), ['4 people now', 'About 12 by month three · people you invite.']);
|
|
344
347
|
w.state.teamShape = 'community'; w.run('paintSetup()');
|
|
345
348
|
assert.equal(w.rowSays('team')[1], 'About 12 by month three · anyone can apply.');
|
|
346
|
-
w.
|
|
349
|
+
assert.equal(w.row('team').querySelector('[data-row-chg]').textContent, 'Done', 'an open row\'s one control reads Done');
|
|
350
|
+
w.done('team');
|
|
347
351
|
assert.equal(w.state.confirmed.team, true);
|
|
348
|
-
assert.equal(w.isOpen('team'), false, '
|
|
352
|
+
assert.equal(w.isOpen('team'), false, 'Done closes the row');
|
|
353
|
+
assert.equal(w.row('team').querySelector('[data-row-chg]').textContent, 'Edit');
|
|
349
354
|
});
|
|
350
355
|
|
|
351
356
|
test('changing the team re-asks the rows it moves: rewards and extras', async () => {
|
|
@@ -372,13 +377,13 @@ test('rewards are hidden for a solo founder: the real table hides the row, and t
|
|
|
372
377
|
assert.equal(w.run("rowApplies('rewards')"), false, 'the REAL softLimitsFor hides reward_intents for one-and-one');
|
|
373
378
|
assert.equal(w.row('rewards').hidden, true, 'and the row goes with it');
|
|
374
379
|
assert.ok(!w.run('unconfirmedRows()').includes('rewards'), 'nothing to confirm for a question never asked');
|
|
375
|
-
assert.equal(w.
|
|
380
|
+
assert.equal(w.run('unconfirmedRows()').length, 6, 'six rows apply');
|
|
376
381
|
assert.equal(w.run('createDetail()').reward_intents, undefined);
|
|
377
382
|
w.change('team');
|
|
378
383
|
w.q('[data-phys-solo="many"]')[0].click();
|
|
379
384
|
await w.settle();
|
|
380
385
|
assert.equal(w.row('rewards').hidden, false, 'a team brings the question back, once the table is asked again');
|
|
381
|
-
assert.equal(w.
|
|
386
|
+
assert.equal(w.run('unconfirmedRows()').length, 7);
|
|
382
387
|
});
|
|
383
388
|
|
|
384
389
|
test('rewards: a greyed option is disabled and leaves the list, and each reason is said once', async () => {
|
|
@@ -472,30 +477,46 @@ test('a demo\'s team, rewards and run are the demo\'s own: fixed, said plainly,
|
|
|
472
477
|
});
|
|
473
478
|
|
|
474
479
|
// ── confirming, and the locked Continue ──────────────────────────────────────
|
|
475
|
-
test('
|
|
480
|
+
test('one "Looks right, continue" confirms every row that applies, each through its own check (task 1004550)', async () => {
|
|
476
481
|
const w = await onReview({ builders_at_start: 3, builders_expected: 8 });
|
|
477
|
-
assert.equal(w.go().disabled,
|
|
478
|
-
assert.equal(w.go().textContent, '
|
|
479
|
-
assert.equal(w.
|
|
480
|
-
assert.equal(w.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
assert.equal(w.state.confirmed[key], true, key);
|
|
484
|
-
assert.equal(w.row(key).querySelector('[data-row-ok]').textContent, 'Confirmed');
|
|
485
|
-
assert.equal(w.row(key).querySelector('[data-row-ok]').disabled, true);
|
|
486
|
-
assert.equal(w.go().textContent, `Confirm ${6 - i} more`);
|
|
487
|
-
}
|
|
482
|
+
assert.equal(w.go().disabled, false, 'Continue is never locked: it is the confirmation');
|
|
483
|
+
assert.equal(w.go().textContent, 'Looks right, continue');
|
|
484
|
+
assert.equal(w.row('code').querySelector('[data-row-ok]'), null, 'no row carries a Looks right of its own');
|
|
485
|
+
assert.equal(w.run('unconfirmedRows()').length, 7, 'nothing is confirmed before the founder says so');
|
|
486
|
+
assert.equal(w.run('validateStep(4)'), true, 'one Continue confirms them all');
|
|
487
|
+
for (const key of ['code', 'address', 'app', 'extras', 'team', 'rewards', 'run']) assert.equal(w.state.confirmed[key], true, key);
|
|
488
488
|
assert.equal(w.state.modules && plain(w.state.modules).join(), 'discord', 'confirming the extras is the answer the create sends');
|
|
489
|
-
w.ok('run');
|
|
490
|
-
assert.equal(w.go().disabled, false);
|
|
491
|
-
assert.equal(w.go().textContent, 'Continue');
|
|
492
|
-
assert.equal(w.run('validateStep(4)'), true);
|
|
493
489
|
w.change('team');
|
|
494
|
-
assert.equal(w.state.confirmed.team, undefined, '
|
|
490
|
+
assert.equal(w.state.confirmed.team, undefined, 'Edit takes a row back');
|
|
495
491
|
assert.equal(w.isOpen('team'), true);
|
|
496
|
-
assert.
|
|
492
|
+
assert.deepEqual(plain(w.run('unconfirmedRows()')), ['team']);
|
|
493
|
+
w.done('team');
|
|
494
|
+
assert.equal(w.isOpen('team'), false, 'Done closes it again');
|
|
495
|
+
assert.equal(w.state.confirmed.team, true, 'and Done is the answer');
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test('Continue stops on the first row that fails its check, opened with its reason, and confirms nothing past it', async () => {
|
|
499
|
+
const w = await onReview({ builders_at_start: 3, builders_expected: 8 });
|
|
500
|
+
w.state.physics.answers.builders_at_start = 4; w.state.physics.answers.builders_expected = 2; // fewer later than now
|
|
501
|
+
assert.equal(w.run('validateStep(4)'), false, 'Continue refuses');
|
|
502
|
+
assert.equal(w.isOpen('team'), true, 'the failing row opens');
|
|
503
|
+
assert.equal(w.state.confirmed.team, undefined);
|
|
504
|
+
assert.equal(w.state.confirmed.rewards, undefined, 'nothing after it is confirmed on the founder\'s behalf');
|
|
505
|
+
assert.equal(w.state.confirmed.code, true, 'the rows before it held, and stay confirmed');
|
|
506
|
+
assert.match(w.$('h-review').textContent, /Fix the row that’s open/);
|
|
507
|
+
assert.equal(w.$('h-review').className, 'hint err');
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
test('opening a row closes another open one whose answer holds, and leaves one that fails open', async () => {
|
|
511
|
+
const w = await onReview({ builders_at_start: 3, builders_expected: 8 });
|
|
512
|
+
w.change('code');
|
|
497
513
|
w.change('team');
|
|
498
|
-
assert.equal(w.isOpen('
|
|
514
|
+
assert.equal(w.isOpen('code'), false, 'one row open at a time');
|
|
515
|
+
assert.equal(w.state.confirmed.code, true, 'closing it that way is the same as Done');
|
|
516
|
+
w.state.physics.answers.builders_expected = 1; // fewer later than now: the team row fails
|
|
517
|
+
w.change('run');
|
|
518
|
+
assert.equal(w.isOpen('team'), true, 'a row that fails its check stays open');
|
|
519
|
+
assert.equal(w.isOpen('run'), true);
|
|
499
520
|
});
|
|
500
521
|
|
|
501
522
|
test('the prefills never overwrite a founder\'s own answer', () => {
|
|
@@ -578,7 +599,7 @@ test('a new owner walks from the quick checks through a fully confirmed review,
|
|
|
578
599
|
w.ok('code'); w.ok('address'); w.ok('app'); w.ok('extras');
|
|
579
600
|
w.change('team');
|
|
580
601
|
w.q('[data-phys-solo="many"]')[0].click();
|
|
581
|
-
w.
|
|
602
|
+
w.done('team');
|
|
582
603
|
await w.settle();
|
|
583
604
|
assert.equal(w.state.confirmed.extras, undefined, 'the team moved the extras\' preselect, so that row is asked again');
|
|
584
605
|
w.ok('extras');
|
|
@@ -586,16 +607,15 @@ test('a new owner walks from the quick checks through a fully confirmed review,
|
|
|
586
607
|
w.change('rewards');
|
|
587
608
|
w.pick('reward_intents', 'cash');
|
|
588
609
|
w.pick('reward_intents', 'equity');
|
|
589
|
-
w.
|
|
610
|
+
w.done('rewards');
|
|
590
611
|
w.change('run');
|
|
591
612
|
w.pick('framing', 'open-source');
|
|
592
613
|
await w.settle();
|
|
593
|
-
w.
|
|
614
|
+
w.done('run');
|
|
594
615
|
assert.equal(w.state.confirmed.app, undefined, 'the framing moved the App hosting row');
|
|
595
616
|
assert.equal(w.rowSays('app')[0], 'Runs on your own machines');
|
|
596
|
-
w.
|
|
597
|
-
assert.equal(w.
|
|
598
|
-
assert.equal(w.run('validateStep(4)'), true);
|
|
617
|
+
assert.equal(w.run('validateStep(4)'), true, 'one Continue confirms the rest');
|
|
618
|
+
assert.equal(w.state.confirmed.app, true);
|
|
599
619
|
const d = plain(w.run('createDetail()'));
|
|
600
620
|
assert.deepEqual({ voice: d.voice, framing: d.framing, services: d.services, team: [d.builders_at_start, d.builders_expected], shape: d.team_shape, rewards: d.reward_intents },
|
|
601
621
|
{ voice: 'professional', framing: 'open-source', services: undefined, team: [2, 5], shape: 'small-team', rewards: ['cash', 'equity'] });
|
|
@@ -611,10 +631,14 @@ test('no path reaches Look without the founder confirming the address: a prefill
|
|
|
611
631
|
assert.equal(w.row('address').querySelector('[data-row-chg]').hidden, false, `${label}: it can always be changed`);
|
|
612
632
|
for (const key of ['code', 'app', 'extras', 'team', 'rewards', 'run']) if (w.run(`rowApplies('${key}')`)) w.ok(key);
|
|
613
633
|
assert.deepEqual(plain(w.run('unconfirmedRows()')), ['address'], `${label}: every other row confirmed, the address still asked`);
|
|
614
|
-
|
|
615
|
-
|
|
634
|
+
// the address is on the page the founder confirms (task 1004550): only their own
|
|
635
|
+
// "Looks right, continue" — or Done on the row — confirms it, and its check still runs
|
|
636
|
+
w.run('validateAddress = function () { return false; }');
|
|
637
|
+
assert.equal(w.run('validateStep(4)'), false, `${label}: an address that fails its check refuses Continue`);
|
|
616
638
|
assert.equal(w.isOpen('address'), true, `${label}: and opens the address row`);
|
|
617
|
-
w.
|
|
639
|
+
assert.equal(w.state.confirmed.address, undefined, `${label}: still unconfirmed`);
|
|
640
|
+
w.run('validateAddress = function () { return true; }');
|
|
641
|
+
w.done('address');
|
|
618
642
|
assert.equal(w.state.confirmed.address, true, `${label}: choosing it is the answer`);
|
|
619
643
|
assert.equal(w.run('validateStep(4)'), true, `${label}: then Continue goes on`);
|
|
620
644
|
}
|
|
@@ -55,7 +55,7 @@ test('each screen opens with the mockup\'s own words', () => {
|
|
|
55
55
|
['panel-fork', 'You’re about to start a digital planet.', 'Try a small demo first, or start your real project.'],
|
|
56
56
|
['panel-what', 'What are you making?', null],
|
|
57
57
|
['panel-checks', 'A few quick checks.', 'These decide which legal and payout options we can offer.'],
|
|
58
|
-
['panel-review', 'Check what we set up.', 'We filled these in from your answers.
|
|
58
|
+
['panel-review', 'Check what we set up.', 'We filled these in from your answers. Change anything that’s wrong.'],
|
|
59
59
|
['panel-look', 'Give it a look.', 'So builders always know which project they’re in.'],
|
|
60
60
|
];
|
|
61
61
|
for (const [id, h, lede] of pairs) {
|
|
@@ -81,19 +81,21 @@ test('planet words survive only as the light touch: the fork and the alive momen
|
|
|
81
81
|
assert.match(SCRIPT, /if \(inst\.last_verified_up\) \{\n\s*\/\*[^*]*\*\/\n\s*\$\('done-h'\)\.textContent =/, 'said only once the project has answered (Live is a verified claim)');
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
-
test('the review cannot be skipped: seven rows,
|
|
84
|
+
test('the review cannot be skipped: seven rows, one Edit each, and one Continue that confirms them all (task 1004550)', () => {
|
|
85
85
|
const p = panel('panel-review');
|
|
86
86
|
const rows = [...p.matchAll(/<div class="setRow" data-row="([a-z]+)">/g)].map((m) => m[1]);
|
|
87
87
|
assert.deepEqual(rows, ['code', 'address', 'app', 'extras', 'team', 'rewards', 'run']);
|
|
88
88
|
for (const key of rows) {
|
|
89
89
|
const r = (p.match(new RegExp(`<div class="setRow" data-row="${key}">[\\s\\S]*?<span class="acts">[^\\n]*\\n`)) || [''])[0];
|
|
90
|
-
assert.match(r, /<
|
|
91
|
-
assert.match(r, /<div class="opts" hidden
|
|
90
|
+
assert.match(r, /<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Edit<\/button><\/span>/, `${key}: one Edit, nothing else`);
|
|
91
|
+
assert.match(r, /<div class="opts" hidden>/, `${key}: Edit opens the row's own controls`);
|
|
92
92
|
}
|
|
93
|
-
assert.
|
|
94
|
-
assert.match(
|
|
95
|
-
assert.match(SCRIPT, /if \(step === 4\)
|
|
96
|
-
assert.match(SCRIPT, /if \(
|
|
93
|
+
assert.doesNotMatch(p, /data-row-ok|data-row-use|Use this/, 'no per-row Looks right and no Use this are left');
|
|
94
|
+
assert.match(p, /<button type="submit" class="btn" id="reviewGo">Looks right, continue<\/button>/, 'Continue is the one confirmation, never locked');
|
|
95
|
+
assert.match(SCRIPT, /if \(step === 4\) return confirmAllRows\(\);/, 'Continue confirms every row through its own check');
|
|
96
|
+
assert.match(SCRIPT, /if \(!confirmRow\(rows\[i\]\)\) \{/, 'and stops on the first that fails');
|
|
97
|
+
assert.match(SCRIPT, /if \(unconfirmedRows\(\)\.length\) \{ reopenReview\(\); return; \}/, 'and Create refuses until every row is confirmed');
|
|
98
|
+
assert.match(SCRIPT, /chg\.textContent = 'Done';/, 'an open row\'s control reads Done');
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
test('nothing the old steps could do is lost: their controls are moved whole into the rows', () => {
|