@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
|
@@ -46,13 +46,12 @@ const SCRIPT = slice('/* ── planet physics (tasks 1004418 and 1004419, BV2.P
|
|
|
46
46
|
const TEAM = slice(' function teamLabel(t) {', " $('f-desc').value = state.description || '';", 'the team pills');
|
|
47
47
|
const APP = slice(' function appPlatformChoice() {', ' var ROWS = [', 'the app choice');
|
|
48
48
|
const REVIEW = slice(' var ROWS = [', ' function renderRecap() {', 'the review rows');
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
['rowValid', REVIEW], ['rowFixed', REVIEW], ['paintSetup', REVIEW], ['validateStep', VALIDATE], ['paintTeam', TEAM]]) {
|
|
49
|
+
for (const [name, region] of [['physScreen', SCRIPT], ['paintChecks', SCRIPT], ['physDefaults', SCRIPT], ['firstInvalidRow', REVIEW],
|
|
50
|
+
['rowValid', REVIEW], ['rowFixed', REVIEW], ['paintSetup', REVIEW], ['paintTeam', TEAM]]) {
|
|
52
51
|
assert.ok(region.includes(`function ${name}(`), `the slice carries ${name}()`);
|
|
53
52
|
}
|
|
54
|
-
//
|
|
55
|
-
const PANEL = slice('<
|
|
53
|
+
// what the controller paints: part 01 (the quick checks) and part 02 (the rows), task 1004648
|
|
54
|
+
const PANEL = slice('<div class="part" id="part-project"', '<!-- the terms (tasks', 'parts 01 and 02');
|
|
56
55
|
const plain = (v) => JSON.parse(JSON.stringify(v)); // values born in the vm
|
|
57
56
|
|
|
58
57
|
// ── a small DOM: the screens' own elements, as a tree, matched by selector ────
|
|
@@ -60,7 +59,7 @@ class El {
|
|
|
60
59
|
constructor(tag, attrs = {}) {
|
|
61
60
|
this.tagName = tag; this.attrs = { ...attrs }; this.hidden = 'hidden' in attrs; this.disabled = 'disabled' in attrs;
|
|
62
61
|
this.value = attrs.value || ''; this.textContent = ''; this.title = ''; this.className = attrs.class || '';
|
|
63
|
-
this.listeners = {}; this.children = []; this.options = [{}]; this.parent = null; this.checked = 'checked' in attrs;
|
|
62
|
+
this.listeners = {}; this.children = []; this.options = [{}]; this.parent = null; this.checked = 'checked' in attrs; this.style = {};
|
|
64
63
|
const self = this;
|
|
65
64
|
this.classes = new Set(this.className.split(/\s+/).filter(Boolean));
|
|
66
65
|
this.classList = { toggle(c, on) { if (on) self.classes.add(c); else self.classes.delete(c); }, contains: (c) => self.classes.has(c),
|
|
@@ -154,12 +153,15 @@ function boot({ check = (body) => ({ stop: false, soft_limits: softLimitsFor(bod
|
|
|
154
153
|
const $ = (id) => (els[id] = els[id] || new El('div', { id }));
|
|
155
154
|
const shown = [];
|
|
156
155
|
const fetched = [];
|
|
157
|
-
const state = Object.assign({ step:
|
|
158
|
-
appPlatform: 'later', demo: null, modules: null,
|
|
156
|
+
const state = Object.assign({ step: 1, name: 'Kestrel', description: '', teamShape: null, physics: null, projType: 'game', mode: 'greenfield',
|
|
157
|
+
appPlatform: 'later', demo: null, modules: null, termsAck: true }, draft);
|
|
159
158
|
const sandbox = {
|
|
160
159
|
$, state, API: '/api/bongos', root: dom.root, Intl,
|
|
161
160
|
document: { createElement: (t) => new El(t) },
|
|
162
|
-
saveDraft() {}, show(n) { shown.push(n); state.step = n; },
|
|
161
|
+
saveDraft() {}, show(n) { shown.push(n); state.step = n; }, paintCreate() {}, renderLook() {},
|
|
162
|
+
// the Look row's words come from the shared picker's catalogue (tests/projects_hub_look.mjs)
|
|
163
|
+
lookKit: { LOOKS: { 'chrome-world': { title: 'The chrome world', colors: { accent: '#fb9569' } } } },
|
|
164
|
+
defaultLook: () => ({ look: 'chrome-world', nightTint: true, logo: null }),
|
|
163
165
|
setMode(m) { state.mode = m; }, authKnown: true,
|
|
164
166
|
// the code, address and Render checks are their own files' to run (projects_hub_app_step,
|
|
165
167
|
// public_landing_projects); here they answer as told
|
|
@@ -175,7 +177,7 @@ function boot({ check = (body) => ({ stop: false, soft_limits: softLimitsFor(bod
|
|
|
175
177
|
},
|
|
176
178
|
};
|
|
177
179
|
vm.createContext(sandbox);
|
|
178
|
-
vm.runInContext([TEAM, SCRIPT, APP, REVIEW
|
|
180
|
+
vm.runInContext([TEAM, SCRIPT, APP, REVIEW].join('\n'), sandbox, { filename: 'projects.html#physics' });
|
|
179
181
|
const q = (sel) => dom.root.querySelectorAll(sel);
|
|
180
182
|
const row = (key) => q(`#reviewRows .setRow[data-row="${key}"]`)[0];
|
|
181
183
|
return {
|
|
@@ -184,21 +186,17 @@ function boot({ check = (body) => ({ stop: false, soft_limits: softLimitsFor(bod
|
|
|
184
186
|
a: () => state.physics.answers,
|
|
185
187
|
pick: (key, val) => q(`[data-phys="${key}"][data-val="${val}"]`)[0].click(),
|
|
186
188
|
rowSays: (key) => [row(key).querySelector('[data-row-v]').textContent, row(key).querySelector('[data-row-s]').textContent],
|
|
187
|
-
// a row
|
|
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
|
|
189
|
+
// Change opens a row; the same control reads Done while it is open, and closes it (task 1004648)
|
|
191
190
|
change: (key) => row(key).querySelector('[data-row-chg]').click(),
|
|
192
191
|
done: (key) => row(key).querySelector('[data-row-chg]').click(),
|
|
193
192
|
isOpen: (key) => !row(key).querySelector('.opts').hidden,
|
|
194
|
-
go: () => $('reviewGo'),
|
|
195
193
|
settle: async () => { for (let i = 0; i < 4; i += 1) await tick(); },
|
|
196
194
|
};
|
|
197
195
|
}
|
|
198
196
|
const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
199
197
|
// the quick checks, answered (the Kestrel sample of the canvas)
|
|
200
198
|
const CHECKS = { home_country: 'US', home_region: 'US-CA', builder_geography: 'us-eu', minors_on_board: false, entity: 'intent-solo' };
|
|
201
|
-
// on the
|
|
199
|
+
// on the page, with the checks answered, the prefills written and the table asked
|
|
202
200
|
async function onReview(answers = {}, opts = {}) {
|
|
203
201
|
const w = boot(opts);
|
|
204
202
|
w.run('phys()');
|
|
@@ -245,9 +243,9 @@ test('the kit stub\'s physics hide rules are the table\'s own, word for word', (
|
|
|
245
243
|
}
|
|
246
244
|
});
|
|
247
245
|
|
|
248
|
-
// ──
|
|
246
|
+
// ── part 01, the quick checks ────────────────────────────────────────────────
|
|
249
247
|
test('the quick checks reveal what an answer opens: the age tiers, the legal forms, a state or province', () => {
|
|
250
|
-
const w = boot(
|
|
248
|
+
const w = boot();
|
|
251
249
|
w.run('paintChecks()');
|
|
252
250
|
assert.equal(w.$('physAges').hidden, true, 'no tiers before a yes');
|
|
253
251
|
assert.equal(w.$('physForms').hidden, true, 'the plain intents first');
|
|
@@ -277,11 +275,11 @@ test('the quick checks reveal what an answer opens: the age tiers, the legal for
|
|
|
277
275
|
});
|
|
278
276
|
|
|
279
277
|
test('the quick checks fill in from the account\'s last project, never its state, and offer nothing without one', async () => {
|
|
280
|
-
const none = boot(
|
|
278
|
+
const none = boot();
|
|
281
279
|
none.run('physLoadAccountOnce()');
|
|
282
280
|
await none.settle();
|
|
283
281
|
assert.equal(none.$('physFillOffer').hidden, true, 'no earlier answers: no offer');
|
|
284
|
-
const w = boot({
|
|
282
|
+
const w = boot({ instances: { instances: [
|
|
285
283
|
{ created_at: '2026-01-01', detail: { home_country: 'GB', entity: 'intent-unsure' } },
|
|
286
284
|
{ created_at: '2026-06-01', detail: { home_country: 'NL', builder_geography: 'global', minors_on_board: false, entity: 'for-profit', project_state: 'existing', framing: 'local' } },
|
|
287
285
|
] } });
|
|
@@ -293,28 +291,31 @@ test('the quick checks fill in from the account\'s last project, never its state
|
|
|
293
291
|
assert.equal(w.$('physCountry').value, 'NL', 'painted where the founder can see it');
|
|
294
292
|
});
|
|
295
293
|
|
|
296
|
-
test('
|
|
297
|
-
const w = boot({ draft: {
|
|
294
|
+
test('the page writes part 02\'s prefills, and Create\'s screen asks the real table with them', async () => {
|
|
295
|
+
const w = boot({ draft: { description: 'A field guide app for birdwatchers' } });
|
|
298
296
|
w.run('phys()');
|
|
299
297
|
Object.assign(w.a(), CHECKS);
|
|
300
|
-
|
|
298
|
+
w.run('physDefaults()');
|
|
299
|
+
w.run('var screened = null; physScreen().then(function (ok) { screened = ok; });');
|
|
301
300
|
await w.settle();
|
|
302
|
-
assert.
|
|
301
|
+
assert.equal(w.run('screened'), true, 'no stop: Create may go on');
|
|
302
|
+
assert.deepEqual(w.shown, [], 'the screen moves nothing itself');
|
|
303
303
|
const sent = w.fetched.find((f) => f.url.endsWith('/provisioning/physics-check')).body;
|
|
304
304
|
assert.equal(sent.description, 'A field guide app for birdwatchers');
|
|
305
305
|
assert.deepEqual({ s: sent.detail.project_state, f: sent.detail.framing, n: [sent.detail.builders_at_start, sent.detail.builders_expected] },
|
|
306
306
|
{ s: 'new', f: 'managed', n: [1, 1] }, 'a new project, all managed, one person');
|
|
307
307
|
assert.equal(w.state.teamShape, 'solo');
|
|
308
|
-
assert.ok(w.run('physLimits').some((l) => l.option === 'reward_intents' && l.effect === 'hide'), 'the table\'s verdicts shape the
|
|
308
|
+
assert.ok(w.run('physLimits').some((l) => l.option === 'reward_intents' && l.effect === 'hide'), 'the table\'s verdicts shape the rows');
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
test('a stop from the server ends
|
|
312
|
-
const w = boot({ draft: {
|
|
311
|
+
test('a stop from the server ends in place of the checks, before anything exists', async () => {
|
|
312
|
+
const w = boot({ draft: { description: 'ransomware' }, check: () => ({ stop: true, message: 'We cannot host this.' }) });
|
|
313
313
|
w.run('phys()');
|
|
314
314
|
Object.assign(w.a(), CHECKS);
|
|
315
|
-
w.run('
|
|
315
|
+
w.run('var screened = null; physScreen().then(function (ok) { screened = ok; });');
|
|
316
316
|
await w.settle();
|
|
317
|
-
assert.
|
|
317
|
+
assert.equal(w.run('screened'), false, 'Create files nothing');
|
|
318
|
+
assert.deepEqual(w.shown, [], 'nothing moves on');
|
|
318
319
|
assert.equal(w.$('physStopMsg').textContent, 'We cannot host this.');
|
|
319
320
|
assert.equal(w.$('checksStop').hidden, false);
|
|
320
321
|
assert.equal(w.$('checksMain').hidden, true);
|
|
@@ -323,7 +324,7 @@ test('a stop from the server ends on the stop screen, before anything exists', a
|
|
|
323
324
|
// ── the Team size row (question 5) ───────────────────────────────────────────
|
|
324
325
|
test('Team size: one or many, a start count, and a guessed growth the founder corrects', async () => {
|
|
325
326
|
const w = await onReview();
|
|
326
|
-
assert.deepEqual(w.rowSays('team'), ['
|
|
327
|
+
assert.deepEqual(w.rowSays('team'), ['Just you to start', 'Open to invites.'], 'prefilled: one person');
|
|
327
328
|
assert.equal(w.q('[data-phys-solo="one"]')[0].getAttribute('aria-pressed'), 'true');
|
|
328
329
|
assert.equal(w.$('physTeam').hidden, true);
|
|
329
330
|
w.change('team');
|
|
@@ -348,27 +349,19 @@ test('Team size: one or many, a start count, and a guessed growth the founder co
|
|
|
348
349
|
assert.equal(w.rowSays('team')[1], 'About 12 by month three · anyone can apply.');
|
|
349
350
|
assert.equal(w.row('team').querySelector('[data-row-chg]').textContent, 'Done', 'an open row\'s one control reads Done');
|
|
350
351
|
w.done('team');
|
|
351
|
-
assert.equal(w.state.confirmed.team, true);
|
|
352
352
|
assert.equal(w.isOpen('team'), false, 'Done closes the row');
|
|
353
|
-
assert.equal(w.row('team').querySelector('[data-row-chg]').textContent, '
|
|
353
|
+
assert.equal(w.row('team').querySelector('[data-row-chg]').textContent, 'Change');
|
|
354
|
+
assert.equal(w.state.confirmed, undefined, 'and nothing is recorded as confirmed: the summary wins');
|
|
354
355
|
});
|
|
355
356
|
|
|
356
|
-
test('
|
|
357
|
-
const w = await onReview({ builders_at_start: 3, builders_expected: 8 });
|
|
358
|
-
w.state.confirmed = { rewards: true, extras: true, code: true };
|
|
359
|
-
w.q('[data-phys-solo="one"]')[0].click();
|
|
360
|
-
assert.deepEqual(Object.keys(w.state.confirmed), ['code'], 'rewards and extras come back to be confirmed; the repo does not');
|
|
361
|
-
assert.equal(w.state.teamShape, 'solo');
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
test('a team row with no count cannot be confirmed: it opens instead', async () => {
|
|
357
|
+
test('a team row that cannot be filed keeps Create from filing it, and Done leaves it open with its controls', async () => {
|
|
365
358
|
const w = await onReview();
|
|
359
|
+
w.change('team');
|
|
366
360
|
w.state.physics.answers.builders_at_start = 4; w.state.physics.answers.builders_expected = 2; // fewer later than now
|
|
367
|
-
assert.equal(w.run(
|
|
368
|
-
|
|
369
|
-
assert.equal(w.
|
|
361
|
+
assert.equal(w.run('firstInvalidRow()'), 'team', 'Create\'s check names it');
|
|
362
|
+
w.done('team');
|
|
363
|
+
assert.equal(w.isOpen('team'), true, 'Done does not close a row whose answer fails');
|
|
370
364
|
assert.equal(w.row('team').querySelector('[data-row-chg]').getAttribute('aria-expanded'), 'true');
|
|
371
|
-
assert.equal(w.state.confirmed.team, undefined);
|
|
372
365
|
});
|
|
373
366
|
|
|
374
367
|
// ── the Rewards row (question 6) ─────────────────────────────────────────────
|
|
@@ -376,14 +369,12 @@ test('rewards are hidden for a solo founder: the real table hides the row, and t
|
|
|
376
369
|
const w = await onReview({ reward_intents: ['cash'] });
|
|
377
370
|
assert.equal(w.run("rowApplies('rewards')"), false, 'the REAL softLimitsFor hides reward_intents for one-and-one');
|
|
378
371
|
assert.equal(w.row('rewards').hidden, true, 'and the row goes with it');
|
|
379
|
-
assert.ok(!w.run('unconfirmedRows()').includes('rewards'), 'nothing to confirm for a question never asked');
|
|
380
|
-
assert.equal(w.run('unconfirmedRows()').length, 7, 'seven rows apply');
|
|
381
372
|
assert.equal(w.run('createDetail()').reward_intents, undefined);
|
|
382
373
|
w.change('team');
|
|
383
374
|
w.q('[data-phys-solo="many"]')[0].click();
|
|
384
375
|
await w.settle();
|
|
385
376
|
assert.equal(w.row('rewards').hidden, false, 'a team brings the question back, once the table is asked again');
|
|
386
|
-
assert.equal(w.run(
|
|
377
|
+
assert.equal(w.run("rowApplies('rewards')"), true);
|
|
387
378
|
});
|
|
388
379
|
|
|
389
380
|
test('rewards: a greyed option is disabled and leaves the list, and each reason is said once', async () => {
|
|
@@ -412,11 +403,9 @@ test('rewards: a greyed option is disabled and leaves the list, and each reason
|
|
|
412
403
|
test('We run: hybrid and local open the suite pre-picked; managed and open source hide it with the table\'s reason', async () => {
|
|
413
404
|
const w = await onReview({ builders_at_start: 3, builders_expected: 8, reward_intents: ['non-monetary'] });
|
|
414
405
|
assert.deepEqual(w.rowSays('run'), ['Everything, for you', 'Hosting, payouts and the starter kit, managed.'], 'prefilled: all managed');
|
|
415
|
-
w.state.confirmed.app = true;
|
|
416
406
|
w.change('run');
|
|
417
407
|
w.pick('framing', 'hybrid');
|
|
418
408
|
await w.settle();
|
|
419
|
-
assert.equal(w.state.confirmed.app, undefined, 'a new framing re-asks the App hosting row');
|
|
420
409
|
assert.deepEqual(plain(w.a().services), ['hosting', 'starter-kit', 'marketplace-buy', 'guild-access'], 'the canvas\'s hybrid suite');
|
|
421
410
|
assert.equal(w.$('physSuite').hidden, false);
|
|
422
411
|
assert.equal(w.$('physSuiteLbl').textContent, 'Your hybrid suite');
|
|
@@ -439,7 +428,7 @@ test('We run: hybrid and local open the suite pre-picked; managed and open sourc
|
|
|
439
428
|
assert.equal(w.run('createDetail()').services, undefined, 'all managed sends no suite');
|
|
440
429
|
w.pick('framing', 'managed');
|
|
441
430
|
assert.equal(w.a().framing, null, 'a second click un-says it');
|
|
442
|
-
assert.equal(w.run("rowValid('run')"), false, 'and a run row with no framing cannot be
|
|
431
|
+
assert.equal(w.run("rowValid('run')"), false, 'and a run row with no framing cannot be filed');
|
|
443
432
|
});
|
|
444
433
|
|
|
445
434
|
test('a company already formed hides company creation from the suite', async () => {
|
|
@@ -450,14 +439,14 @@ test('a company already formed hides company creation from the suite', async ()
|
|
|
450
439
|
});
|
|
451
440
|
|
|
452
441
|
// ── fixed rows: the App hosting row on your own machines, and a demo's own answers ──
|
|
453
|
-
test('a planet on its own machines fixes the
|
|
442
|
+
test('a planet on its own machines fixes the Hosting row: it says so, files without the Render check, and never opens', async () => {
|
|
454
443
|
for (const [framing, fixed] of [['managed', false], ['hybrid', false], ['local', true], ['open-source', true]]) {
|
|
455
444
|
const w = await onReview({ framing }, { renderOk: false });
|
|
456
445
|
assert.equal(w.run("rowFixed('app')"), fixed, framing);
|
|
457
446
|
assert.equal(w.row('app').querySelector('[data-row-chg]').hidden, fixed, `${framing}: Change is ${fixed ? 'gone' : 'offered'}`);
|
|
458
|
-
assert.equal(w.run("
|
|
447
|
+
assert.equal(w.run("rowValid('app')"), fixed, `${framing}: ${fixed ? 'filed without' : 'held by'} the Render check`);
|
|
459
448
|
if (fixed) assert.equal(w.rowSays('app')[0], 'Runs on your own machines');
|
|
460
|
-
else assert.equal(w.
|
|
449
|
+
else assert.equal(w.run('firstInvalidRow()'), 'app', 'a failed check is what Create opens');
|
|
461
450
|
}
|
|
462
451
|
const demo = await onReview({ framing: 'local' }, { renderOk: false, draft: { demo: { people: 3, hours_each: 4 } } });
|
|
463
452
|
assert.equal(demo.run("rowFixed('app')"), false, 'a demo is all managed, whatever framing a walk left behind');
|
|
@@ -476,35 +465,26 @@ test('a demo\'s team, rewards and run are the demo\'s own: fixed, said plainly,
|
|
|
476
465
|
assert.deepEqual(w.rowSays('run'), ['Everything, for you', 'A demo is all managed and private to your team.']);
|
|
477
466
|
});
|
|
478
467
|
|
|
479
|
-
// ──
|
|
480
|
-
test('
|
|
468
|
+
// ── nothing to confirm (task 1004648: the summary wins) ─────────────────────
|
|
469
|
+
test('no row needs confirming: the prefilled page is fileable as it stands, and the extras stay the server\'s suggestion', async () => {
|
|
481
470
|
const w = await onReview({ builders_at_start: 3, builders_expected: 8 });
|
|
482
|
-
assert.equal(w.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
assert.equal(w.
|
|
489
|
-
w.
|
|
490
|
-
assert.equal(w.
|
|
491
|
-
assert.equal(w.isOpen('team'), true);
|
|
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');
|
|
471
|
+
assert.equal(w.run('firstInvalidRow()'), null, 'every row that applies can be filed as it stands');
|
|
472
|
+
for (const key of ['code', 'address', 'team', 'extras', 'rewards', 'app', 'run', 'ship', 'look']) {
|
|
473
|
+
assert.equal(w.row(key).querySelector('[data-row-chg]').textContent, 'Change', `${key}: one Change`);
|
|
474
|
+
assert.equal(w.isOpen(key), false, `${key}: closed`);
|
|
475
|
+
}
|
|
476
|
+
assert.equal(w.state.modules, null, 'the extras are never turned into an answer nobody gave: the server applies the same bundle');
|
|
477
|
+
assert.equal(w.q('#reviewGo').length, 0, 'there is no Continue to press');
|
|
478
|
+
assert.deepEqual(w.rowSays('look'), ['Chrome world', 'Add a logo later from the project page.'], 'the Look row says the look');
|
|
479
|
+
assert.equal(w.$('lookDot').style.background, '#fb9569', 'and wears its accent');
|
|
496
480
|
});
|
|
497
481
|
|
|
498
|
-
test('
|
|
482
|
+
test('Create\'s check finds the first row that cannot be filed, in the page\'s order', async () => {
|
|
499
483
|
const w = await onReview({ builders_at_start: 3, builders_expected: 8 });
|
|
500
484
|
w.state.physics.answers.builders_at_start = 4; w.state.physics.answers.builders_expected = 2; // fewer later than now
|
|
501
|
-
assert.equal(w.run('
|
|
502
|
-
|
|
503
|
-
assert.equal(w.
|
|
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');
|
|
485
|
+
assert.equal(w.run('firstInvalidRow()'), 'team');
|
|
486
|
+
w.run('validateCode = function () { return false; }');
|
|
487
|
+
assert.equal(w.run('firstInvalidRow()'), 'code', 'the earliest row first');
|
|
508
488
|
});
|
|
509
489
|
|
|
510
490
|
test('opening a row closes another open one whose answer holds, and leaves one that fails open', async () => {
|
|
@@ -512,7 +492,7 @@ test('opening a row closes another open one whose answer holds, and leaves one t
|
|
|
512
492
|
w.change('code');
|
|
513
493
|
w.change('team');
|
|
514
494
|
assert.equal(w.isOpen('code'), false, 'one row open at a time');
|
|
515
|
-
assert.equal(w.
|
|
495
|
+
assert.equal(w.row('code').querySelector('[data-row-chg]').textContent, 'Change', 'closed that way, it reads Change again');
|
|
516
496
|
w.state.physics.answers.builders_expected = 1; // fewer later than now: the team row fails
|
|
517
497
|
w.change('run');
|
|
518
498
|
assert.equal(w.isOpen('team'), true, 'a row that fails its check stays open');
|
|
@@ -528,9 +508,9 @@ test('the prefills never overwrite a founder\'s own answer', () => {
|
|
|
528
508
|
assert.deepEqual({ s: w.a().project_state, f: w.a().framing, n: [w.a().builders_at_start, w.a().builders_expected], t: w.state.teamShape },
|
|
529
509
|
{ s: 'existing', f: 'hybrid', n: [3, 9], t: 'community' });
|
|
530
510
|
assert.equal(w.state.mode, 'adopt', 'a project that already has code adopts its repo');
|
|
531
|
-
w.state.mode = 'greenfield'; w.state.
|
|
511
|
+
w.state.mode = 'greenfield'; w.state.modeChosen = true;
|
|
532
512
|
w.run('physDefaults()');
|
|
533
|
-
assert.equal(w.state.mode, 'greenfield', 'but never over a
|
|
513
|
+
assert.equal(w.state.mode, 'greenfield', 'but never over a repo choice the founder made');
|
|
534
514
|
for (const gone of ['physPrefill', 'typePrefill', 'homePrefill', 'physSummary', 'data-phys-goto', 'physStepSkipped', 'physPastSkips']) {
|
|
535
515
|
assert.ok(!HUB.includes(gone), `${gone} is gone: the review shows every prefill as a row instead`);
|
|
536
516
|
}
|
|
@@ -548,7 +528,7 @@ test('one plain voice for everyone: professional is stored for an organization a
|
|
|
548
528
|
assert.doesNotMatch(HUB, /id="(physCoords|physCallout|physCapLbl)"/, 'the profile caption left with the forming planet');
|
|
549
529
|
});
|
|
550
530
|
|
|
551
|
-
test('the one voice carries no planet words on
|
|
531
|
+
test('the one voice carries no planet words on parts 01 and 02', () => {
|
|
552
532
|
const words = PANEL.replace(/<!--[\s\S]*?-->/g, '').replace(/<[^>]*>/g, ' ');
|
|
553
533
|
assert.doesNotMatch(words, /\bplanet|\bmoons?\b|\borbit/i, 'the plain copy names no planet, moon or orbit');
|
|
554
534
|
});
|
|
@@ -582,27 +562,22 @@ test('the forming planet is gone, and the room behind the wizard moves on CSS al
|
|
|
582
562
|
});
|
|
583
563
|
|
|
584
564
|
// ── the done-when, end to end ────────────────────────────────────────────────
|
|
585
|
-
test('a new owner walks from the quick checks through
|
|
586
|
-
const w = boot({ draft: {
|
|
587
|
-
w.run('paintChecks()');
|
|
565
|
+
test('a new owner walks the page from the quick checks through the rows they change, the rest prefilled', async () => {
|
|
566
|
+
const w = boot({ draft: { description: 'An inventory tool our team started last year.' } });
|
|
567
|
+
w.run('physDefaults(); paintChecks(); paintSetup();');
|
|
588
568
|
w.$('physCountry').value = 'US'; w.$('physCountry').listeners.change();
|
|
589
569
|
w.$('physRegion').value = 'US-NY'; w.$('physRegion').listeners.change();
|
|
590
570
|
w.pick('builder_geography', 'global');
|
|
591
571
|
w.pick('minors_on_board', 'false');
|
|
592
572
|
w.$('physHaveEntity').click();
|
|
593
573
|
w.pick('entity', 'for-profit'); // a company already formed, so equity is on the table
|
|
594
|
-
w.run('checksAdvance()');
|
|
595
574
|
await w.settle();
|
|
596
|
-
assert.deepEqual(w.shown, [4], 'the review');
|
|
597
575
|
w.run('paintSetup()');
|
|
598
576
|
assert.equal(w.row('rewards').hidden, true, 'one person: no rewards row');
|
|
599
|
-
w.ok('code'); w.ok('address'); w.ok('app'); w.ok('extras');
|
|
600
577
|
w.change('team');
|
|
601
578
|
w.q('[data-phys-solo="many"]')[0].click();
|
|
602
579
|
w.done('team');
|
|
603
580
|
await w.settle();
|
|
604
|
-
assert.equal(w.state.confirmed.extras, undefined, 'the team moved the extras\' preselect, so that row is asked again');
|
|
605
|
-
w.ok('extras');
|
|
606
581
|
assert.equal(w.row('rewards').hidden, false);
|
|
607
582
|
w.change('rewards');
|
|
608
583
|
w.pick('reward_intents', 'cash');
|
|
@@ -612,34 +587,24 @@ test('a new owner walks from the quick checks through a fully confirmed review,
|
|
|
612
587
|
w.pick('framing', 'open-source');
|
|
613
588
|
await w.settle();
|
|
614
589
|
w.done('run');
|
|
615
|
-
assert.equal(w.
|
|
616
|
-
assert.equal(w.
|
|
617
|
-
assert.equal(w.run('validateStep(4)'), true, 'one Continue confirms the rest');
|
|
618
|
-
assert.equal(w.state.confirmed.app, true);
|
|
590
|
+
assert.equal(w.rowSays('app')[0], 'Runs on your own machines', 'the framing moved the Hosting row');
|
|
591
|
+
assert.equal(w.run('firstInvalidRow()'), null, 'and the page can be filed as it stands');
|
|
619
592
|
const d = plain(w.run('createDetail()'));
|
|
620
593
|
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 },
|
|
621
594
|
{ voice: 'professional', framing: 'open-source', services: undefined, team: [2, 5], shape: 'small-team', rewards: ['cash', 'equity'] });
|
|
622
595
|
});
|
|
623
596
|
|
|
624
597
|
// ── the address is the founder's own choice, on every path (task 1004507, BV2.PS19) ──
|
|
625
|
-
test('
|
|
598
|
+
test('the address is on every path, the demo\'s too: never a fixed row, always changeable, and an address that cannot be filed holds Create', async () => {
|
|
626
599
|
for (const draft of [{}, { demo: { people: 3, hours_each: 4, idea: '', invites: [] } }]) {
|
|
627
600
|
const label = draft.demo ? 'demo' : 'real project';
|
|
628
601
|
const w = await onReview({}, { draft });
|
|
629
|
-
assert.equal(w.
|
|
602
|
+
assert.equal(w.row('address').hidden, false, `${label}: the address row shows`);
|
|
630
603
|
assert.equal(w.run("rowFixed('address')"), false, `${label}: the address is never a fixed row`);
|
|
631
604
|
assert.equal(w.row('address').querySelector('[data-row-chg]').hidden, false, `${label}: it can always be changed`);
|
|
632
|
-
for (const key of ['code', 'app', 'extras', 'team', 'rewards', 'run', 'ship']) if (w.run(`rowApplies('${key}')`)) w.ok(key);
|
|
633
|
-
assert.deepEqual(plain(w.run('unconfirmedRows()')), ['address'], `${label}: every other row confirmed, the address still asked`);
|
|
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
605
|
w.run('validateAddress = function () { return false; }');
|
|
637
|
-
assert.equal(w.run('
|
|
638
|
-
assert.equal(w.isOpen('address'), true, `${label}: and opens the address row`);
|
|
639
|
-
assert.equal(w.state.confirmed.address, undefined, `${label}: still unconfirmed`);
|
|
606
|
+
assert.equal(w.run('firstInvalidRow()'), 'address', `${label}: an address that fails its check is what Create opens`);
|
|
640
607
|
w.run('validateAddress = function () { return true; }');
|
|
641
|
-
w.
|
|
642
|
-
assert.equal(w.state.confirmed.address, true, `${label}: choosing it is the answer`);
|
|
643
|
-
assert.equal(w.run('validateStep(4)'), true, `${label}: then Continue goes on`);
|
|
608
|
+
assert.equal(w.run('firstInvalidRow()'), null, `${label}: then Create goes on`);
|
|
644
609
|
}
|
|
645
610
|
});
|
|
@@ -40,7 +40,7 @@ function slice(start, end, what) {
|
|
|
40
40
|
// ── 1. the wizard ──────────────────────────────────────────────────────────
|
|
41
41
|
|
|
42
42
|
test('the review asks how finished work ships: Auto (recommended) first and checked, Manual second', () => {
|
|
43
|
-
const row = slice('<div class="setRow" data-row="ship">', '<div class="
|
|
43
|
+
const row = slice('<div class="setRow" data-row="ship">', '<div class="setRow" data-row="look">', 'the ship row');
|
|
44
44
|
const auto = row.indexOf('value="auto"'), manual = row.indexOf('value="manual"');
|
|
45
45
|
assert.ok(auto > 0 && manual > auto, 'Auto comes first');
|
|
46
46
|
assert.match(row, /<input type="radio" name="shipMode" value="auto" checked>/, 'and is the pick until the founder says otherwise');
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// tests/wizard_six_screens.mjs — the blended create wizard (task 1004504, BV2.PS16; goal 1000121).
|
|
2
|
-
//
|
|
3
|
-
// The design of record is docs/design/mocks/combined-onboarding/index.html (the owner approved it
|
|
4
|
-
// as drawn, 2026-10-01): six screens and the done panel, in this order — sign in, demo or real
|
|
5
|
-
// (the terms as its one tick), what are you making, a few quick checks, check what we set up,
|
|
6
|
-
// give it a look → Create, done — with the mockup's own words, no progress rail, and a review
|
|
7
|
-
// that cannot be skipped: every prefilled answer is a row the founder confirms or changes, and
|
|
8
|
-
// Continue stays locked until all of them are confirmed.
|
|
9
|
-
//
|
|
10
|
-
// This file pins the shape and the words statically. The rows' behaviour is executed by
|
|
11
|
-
// tests/wizard_physics_more.mjs (the controller under a DOM stub) and walked end to end by the
|
|
12
|
-
// kit probes in projects.probes.json; the night room's tokens and motion by tests/projects_hub.mjs.
|
|
13
|
-
//
|
|
14
|
-
// Run: node --test tests/wizard_six_screens.mjs
|
|
15
|
-
|
|
16
|
-
import assert from 'node:assert/strict';
|
|
17
|
-
import { test } from 'node:test';
|
|
18
|
-
import fs from 'node:fs';
|
|
19
|
-
import path from 'node:path';
|
|
20
|
-
import { fileURLToPath } from 'node:url';
|
|
21
|
-
|
|
22
|
-
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
|
-
const HUB = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
24
|
-
const SCRIPT = HUB.slice(HUB.lastIndexOf('<script>'), HUB.lastIndexOf('</script>'));
|
|
25
|
-
const VIEW = (HUB.match(/<section id="view-new" hidden>[\s\S]*?\n<\/section>/) || [''])[0];
|
|
26
|
-
const MOCK = fs.readFileSync(path.join(ROOT, 'docs', 'design', 'mocks', 'combined-onboarding', 'index.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
27
|
-
const panel = (id) => (VIEW.match(new RegExp(`<section class="panel[^"]*" id="${id}"[\\s\\S]*?\\n {10}</section>`)) || [''])[0];
|
|
28
|
-
|
|
29
|
-
const ORDER = ['panel-gate', 'panel-fork', 'panel-what', 'panel-checks', 'panel-review', 'panel-look', 'panel-done'];
|
|
30
|
-
|
|
31
|
-
test('six screens and the done panel, in the design of record\'s order, in the markup and in the controller\'s map', () => {
|
|
32
|
-
assert.ok(VIEW, 'the ?view=new section is found');
|
|
33
|
-
const inMarkup = [...VIEW.matchAll(/<section class="panel[^"]*" id="(panel-[a-z]+)"/g)].map((m) => m[1]);
|
|
34
|
-
assert.deepEqual(inMarkup, ORDER, 'the panels appear in this order and there are no others');
|
|
35
|
-
const map = (SCRIPT.match(/var panels = \{([^}]*)\}/) || [, ''])[1];
|
|
36
|
-
const fromMap = [...map.matchAll(/(\d+):\s*'(panel-[a-z]+)'/g)].map((m) => [Number(m[1]), m[2]]);
|
|
37
|
-
assert.deepEqual(fromMap, ORDER.map((id, i) => [i, id]), 'the controller numbers them 0–6 in the same order');
|
|
38
|
-
assert.match(SCRIPT, /var DRAFT_KEY = 'cb-create-draft-v9';/, 'a renumbering bumps the draft key, so an older draft is ignored rather than replayed onto the wrong screen');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test('the old screens and the progress rails are gone', () => {
|
|
42
|
-
for (const id of ['panel-terms', 'panel-physics', 'panel-name', 'panel-type', 'panel-detail', 'panel-modules', 'panel-home', 'panel-address', 'panel-app']) {
|
|
43
|
-
assert.doesNotMatch(HUB, new RegExp(`id="${id}"`), `${id} folded into the six screens`);
|
|
44
|
-
}
|
|
45
|
-
assert.doesNotMatch(VIEW, /class="steps"|id="physRail"|data-step=/, 'no progress rail, outer or inner');
|
|
46
|
-
assert.doesNotMatch(SCRIPT, /function paintRail\(|function physNextQ\(|function physPaintStage\(/, 'nor the code that painted them');
|
|
47
|
-
assert.doesNotMatch(VIEW, /id="physStage"|id="forkStage"|class="bongoCell"|class="bongos"/, 'the forming planet, the plate and the drums give way to the night room');
|
|
48
|
-
assert.match(VIEW, /<div class="wizTop">\s*<span class="wizEyebrow" id="wizEyebrow">New project<\/span>\s*<button type="button" class="wizBack" id="wizBack" hidden>← Back<\/button>/,
|
|
49
|
-
'one eyebrow names the screen and one Back walks backwards');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('each screen opens with the mockup\'s own words', () => {
|
|
53
|
-
const pairs = [
|
|
54
|
-
['panel-gate', 'Make something.', 'Sign in with GitHub to start your project.'],
|
|
55
|
-
['panel-fork', 'You’re about to start a digital planet.', 'Try a small demo first, or start your real project.'],
|
|
56
|
-
['panel-what', 'What are you making?', null],
|
|
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. Change anything that’s wrong.'],
|
|
59
|
-
['panel-look', 'Give it a look.', 'So builders always know which project they’re in.'],
|
|
60
|
-
];
|
|
61
|
-
for (const [id, h, lede] of pairs) {
|
|
62
|
-
const p = panel(id);
|
|
63
|
-
assert.ok(p, `${id} is found`);
|
|
64
|
-
const mockLine = h.replace(/’/g, '(’|\'|’)');
|
|
65
|
-
assert.match(MOCK, new RegExp(mockLine.replace(/[.?]/g, '\\$&')), `"${h}" is the mockup's own headline`);
|
|
66
|
-
assert.match(p, new RegExp(`<h1 id="[a-z-]+h[0-9]?"?[^>]*>${h.replace(/[.?]/g, '\\$&')}`), `${id} opens "${h}"`);
|
|
67
|
-
if (lede) assert.match(p, new RegExp(`<p class="lede">${lede.replace(/[.?]/g, '\\$&')}</p>`), `${id}'s one line`);
|
|
68
|
-
}
|
|
69
|
-
assert.match(panel('panel-gate'), /Continue with GitHub/, 'the gate\'s door');
|
|
70
|
-
assert.match(panel('panel-fork'), /<button type="submit" class="btn big" id="wizStart" disabled>Start my project<\/button>/);
|
|
71
|
-
assert.match(panel('panel-fork'), /id="wizDemo"[^>]*disabled>Try the demo<\/button>/, 'both doors wait for the terms tick');
|
|
72
|
-
assert.match(panel('panel-fork'), /I can sign for this project, and I accept the <a class="termsRead" href="\/terms"/, 'the terms are one tick on the fork, with the terms one click away');
|
|
73
|
-
assert.match(panel('panel-what'), /<span>Name<\/span>[\s\S]*<span id="typeLbl">Kind<\/span>[\s\S]*In a sentence <small>\(optional — puts it on the public map\)<\/small>/, 'name, kind, one optional sentence');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('planet words survive only as the light touch: the fork and the alive moment', () => {
|
|
77
|
-
const words = VIEW.replace(/<!--[\s\S]*?-->/g, '').replace(/<[^>]+>/g, ' ');
|
|
78
|
-
const planets = words.match(/planet/gi) || [];
|
|
79
|
-
assert.equal(planets.length, 1, 'one planet in the whole view: the fork\'s headline');
|
|
80
|
-
assert.match(SCRIPT, /\(state\.name \|\| state\.slug \|\| 'Your project'\) \+ ' is alive\.'/, 'the alive moment');
|
|
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
|
-
});
|
|
83
|
-
|
|
84
|
-
test('the review cannot be skipped: eight rows, one Edit each, and one Continue that confirms them all (task 1004550)', () => {
|
|
85
|
-
const p = panel('panel-review');
|
|
86
|
-
const rows = [...p.matchAll(/<div class="setRow" data-row="([a-z]+)">/g)].map((m) => m[1]);
|
|
87
|
-
assert.deepEqual(rows, ['code', 'address', 'app', 'extras', 'team', 'rewards', 'run', 'ship']);
|
|
88
|
-
for (const key of rows) {
|
|
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, /<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
|
-
}
|
|
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');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test('nothing the old steps could do is lost: their controls are moved whole into the rows', () => {
|
|
102
|
-
const p = panel('panel-review');
|
|
103
|
-
const has = (row, ids) => {
|
|
104
|
-
const r = (p.match(new RegExp(`<div class="setRow" data-row="${row}">[\\s\\S]*?\\n {16}</div>\\n`)) || [''])[0];
|
|
105
|
-
for (const id of ids) assert.match(r, new RegExp(`id="${id}"`), `${row} row hosts #${id}`);
|
|
106
|
-
};
|
|
107
|
-
has('code', ['physExisting', 'c-green', 'c-adopt', 'adoptNote', 'ghNoAccess', 'ghAdopt', 'ghFresh', 'manualHome', 'h-home']);
|
|
108
|
-
has('address', ['f-slug', 'addrConfirm', 'addrCustom', 'f-domain', 'f-noaddr']);
|
|
109
|
-
has('app', ['appOpts', 'renderLater', 'renderSetup', 'f-renderKey', 'renderWs', 'renderSvc', 'renderNew']);
|
|
110
|
-
has('extras', ['modQ', 'modCards', 'modWhy', 'modCoreNote']);
|
|
111
|
-
has('team', ['physTeam', 'physStartN', 'physExpected', 'teamCards']);
|
|
112
|
-
has('run', ['physSuite']);
|
|
113
|
-
has('ship', ['shipOpts']);
|
|
114
|
-
for (const id of ['physCountry', 'physRegion', 'physAges', 'physIntent', 'physForms', 'physHaveEntity', 'checksStop', 'physStopMsg', 'physFromAccount']) {
|
|
115
|
-
assert.match(panel('panel-checks'), new RegExp(`id="${id}"`), `the quick checks host #${id}`);
|
|
116
|
-
}
|
|
117
|
-
for (const id of ['demoSetup', 'demoIdea', 'demoInvites', 'demoStart', 'wizResume']) {
|
|
118
|
-
assert.match(panel('panel-fork'), new RegExp(`id="${id}"`), `the fork hosts #${id}`);
|
|
119
|
-
}
|
|
120
|
-
for (const id of ['wizLook', 'wizRecap', 'createAlert', 'doCreate']) {
|
|
121
|
-
assert.match(panel('panel-look'), new RegExp(`id="${id}"`), `the look screen hosts #${id}`);
|
|
122
|
-
}
|
|
123
|
-
/* two answers moved across screens, so two old guarantees are re-pinned where they live now */
|
|
124
|
-
assert.match(SCRIPT, /function renderNow\(\) \{ return !appSkipped\(\) && /, 'a framing on the founder\'s own machines rules Render out at Create, whatever was picked before');
|
|
125
|
-
assert.match(SCRIPT, /function renderLater\(\) \{ return !appSkipped\(\) && /);
|
|
126
|
-
assert.match(SCRIPT, /if \(key === 'extras' && starterBundles\) state\.modules = pickedModules\(\);/, 'confirming the extras before the catalog lands files no answer, never an empty list');
|
|
127
|
-
const ids = [...VIEW.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]);
|
|
128
|
-
assert.deepEqual(ids.filter((id, i) => ids.indexOf(id) !== i), [], 'moved, never copied: every id in the view is unique');
|
|
129
|
-
});
|