@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// tests/wizard_one_page.mjs — project creation as one page with three parts, in the night room
|
|
2
|
+
// or the day room by the viewer's clock (task 1004648, BONGOS-V2).
|
|
3
|
+
//
|
|
4
|
+
// The design of record is docs/design/mocks/day-night-rooms/index.html (the owner approved it as
|
|
5
|
+
// drawn, 2026-10-08), written up in docs/design/project-startup-direction.md "One-page flow". Sign
|
|
6
|
+
// in stays the gate in front; then ONE page: "Make something.", the demo as a small link, part 01
|
|
7
|
+
// your project, part 02 how it's run (a summary to skim — every row's Change opens today's real
|
|
8
|
+
// control in place, and no row needs confirming), the terms tick, Create with a one-line recap,
|
|
9
|
+
// then part 03 Connect GitHub once the project exists.
|
|
10
|
+
//
|
|
11
|
+
// This file pins the shape and the words statically, and EXECUTES the three small pieces of new
|
|
12
|
+
// logic (the clock, what Create waits for, how full the page is) by the vm-slice idiom. The rows'
|
|
13
|
+
// controls are executed by tests/wizard_physics_more.mjs; the walk end to end by the kit probes.
|
|
14
|
+
//
|
|
15
|
+
// Run: node --test tests/wizard_one_page.mjs
|
|
16
|
+
|
|
17
|
+
import assert from 'node:assert/strict';
|
|
18
|
+
import { test } from 'node:test';
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import vm from 'node:vm';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
+
const HUB = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
26
|
+
const STYLE = HUB.slice(HUB.indexOf('<style>'), HUB.indexOf('</style>'));
|
|
27
|
+
const SCRIPT = HUB.slice(HUB.lastIndexOf('<script>'), HUB.lastIndexOf('</script>'));
|
|
28
|
+
const VIEW = (HUB.match(/<section id="view-new" hidden>[\s\S]*?\n<\/section>/) || [''])[0];
|
|
29
|
+
const MOCK = fs.readFileSync(path.join(ROOT, 'docs', 'design', 'mocks', 'day-night-rooms', 'index.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
30
|
+
const PAGE = (VIEW.match(/<section class="panel on" id="panel-page"[\s\S]*?\n {10}<\/section>/) || [''])[0];
|
|
31
|
+
const part = (id) => (PAGE.match(new RegExp(`<div class="part[^"]*" id="${id}"[\\s\\S]*?\\n {14}</div>\\n`)) || [''])[0];
|
|
32
|
+
const row = (key) => (PAGE.match(new RegExp(`<div class="setRow" data-row="${key}">[\\s\\S]*?\\n {18}</div>\\n`)) || [''])[0];
|
|
33
|
+
const slice = (from, to) => {
|
|
34
|
+
const a = SCRIPT.indexOf(from), b = SCRIPT.indexOf(to, a);
|
|
35
|
+
assert.ok(a > 0 && b > a, `slice boundaries found: ${from} … ${to}`);
|
|
36
|
+
return SCRIPT.slice(a, b);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
test('the gate, then one page: the controller knows three places, and the draft key moved with them', () => {
|
|
40
|
+
assert.ok(VIEW && PAGE, 'the view and its page are found');
|
|
41
|
+
const panels = [...VIEW.matchAll(/<section class="panel[^"]*" id="(panel-[a-z]+)"/g)].map((m) => m[1]);
|
|
42
|
+
assert.deepEqual(panels, ['panel-gate', 'panel-page'], 'the sign-in gate and the page, nothing else');
|
|
43
|
+
assert.match(SCRIPT, /var panels = \{ 0: 'panel-gate', 1: 'panel-page', 2: 'panel-page' \};/);
|
|
44
|
+
assert.match(SCRIPT, /var SCREEN = \{ 0: 'signin', 1: 'page', 2: 'done' \};/);
|
|
45
|
+
assert.match(SCRIPT, /var DRAFT_KEY = 'cb-create-draft-v10';/, 'a renumbering bumps the draft key, so an older draft is ignored rather than replayed');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('the old screens, their doors and the confirm-every-row machinery are gone, not hidden', () => {
|
|
49
|
+
for (const id of ['panel-fork', 'panel-what', 'panel-checks', 'panel-review', 'panel-look', 'forkIntro', 'wizStart', 'wizBack', 'reviewGo', 'h-review', 'physNext', 'stepPager', 'hopWhile', 'hopSignin', 'doneStepsHead', 'h-projtype']) {
|
|
50
|
+
assert.doesNotMatch(HUB, new RegExp(`id="${id}"`), `#${id} is gone from the markup`);
|
|
51
|
+
assert.doesNotMatch(SCRIPT, new RegExp(`\\$\\('${id}'\\)`), `and nothing reaches for #${id}`);
|
|
52
|
+
}
|
|
53
|
+
for (const fn of ['confirmRow', 'confirmAllRows', 'unconfirm', 'unconfirmedRows', 'reopenReview', 'confirmedRows', 'validateStep', 'checksAdvance', 'paintDoors', 'prevStep', 'stepGo']) {
|
|
54
|
+
assert.doesNotMatch(SCRIPT, new RegExp(`function ${fn}\\(`), `${fn}() is removed`);
|
|
55
|
+
}
|
|
56
|
+
assert.doesNotMatch(SCRIPT, /state\.confirmed/, 'no row remembers being confirmed');
|
|
57
|
+
assert.doesNotMatch(VIEW, /data-next=|<form/, 'no screen-to-screen forms are left');
|
|
58
|
+
assert.doesNotMatch(STYLE, /\.doorPill|\.wizBack|\.stepPager|hopSignin/, 'nor their styles');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('the page opens with the mockup\'s own words, in the mockup\'s order', () => {
|
|
62
|
+
const words = [
|
|
63
|
+
'Make something.',
|
|
64
|
+
'Three parts, one page. Just looking?',
|
|
65
|
+
'Try a small demo instead',
|
|
66
|
+
'Your project',
|
|
67
|
+
'A few quick checks. They decide which legal and payout options we can offer.',
|
|
68
|
+
'filled in from your answers',
|
|
69
|
+
'I can sign for this project, and I accept the',
|
|
70
|
+
'Create my project',
|
|
71
|
+
'Connect GitHub',
|
|
72
|
+
'opens once your project exists',
|
|
73
|
+
'One trip to GitHub. Approve the app, pick your repo, and you land back here. We tick each step as it happens, from any tab or device.',
|
|
74
|
+
];
|
|
75
|
+
let at = 0;
|
|
76
|
+
for (const w of words) {
|
|
77
|
+
assert.ok(MOCK.includes(w.replace('How it’s run', 'How it\'s run')), `"${w}" is the mockup's own`);
|
|
78
|
+
const i = PAGE.indexOf(w, at);
|
|
79
|
+
assert.ok(i >= at, `"${w}" comes next on the page`);
|
|
80
|
+
at = i;
|
|
81
|
+
}
|
|
82
|
+
assert.match(PAGE, /<h2 id="p2-h"><span class="n">02<\/span>How it’s run <span class="state">filled in from your answers<\/span><\/h2>/);
|
|
83
|
+
assert.match(PAGE, /In a sentence <small>\(optional, it goes on the public map\)<\/small>/);
|
|
84
|
+
assert.match(VIEW, /<h1 id="gate-h">Make something\.<\/h1>[\s\S]*Continue with GitHub/, 'the gate in front is unchanged');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('part 02 is the mockup\'s rows, each with one Change that opens today\'s own control in place', () => {
|
|
88
|
+
const rows = [...part('part-run').matchAll(/<div class="setRow" data-row="([a-z]+)">/g)].map((m) => m[1]);
|
|
89
|
+
assert.deepEqual(rows, ['code', 'address', 'team', 'extras', 'rewards', 'app', 'run', 'ship', 'look']);
|
|
90
|
+
const labels = rows.map((k) => (row(k).match(/<span class="k">([^<]+)<\/span>/) || [])[1]);
|
|
91
|
+
assert.deepEqual(labels, ['Code', 'Address', 'Team', 'Extras', 'Rewards', 'Hosting', 'We run', 'Shipping', 'Look']);
|
|
92
|
+
for (const k of rows) {
|
|
93
|
+
assert.match(row(k), /<span class="acts"><button type="button" class="rowChg" data-row-chg aria-expanded="false">Change<\/button><\/span>/, `${k}: one Change, nothing else`);
|
|
94
|
+
assert.match(row(k), /<div class="opts" hidden>/, `${k}: Change opens the row's own controls`);
|
|
95
|
+
}
|
|
96
|
+
assert.match(SCRIPT, /chg\.textContent = 'Done';/, 'an open row reads Done');
|
|
97
|
+
assert.match(SCRIPT, /chg\.textContent = 'Change';/, 'and Change again once closed');
|
|
98
|
+
assert.match(SCRIPT, /var ROWS = \['code', 'address', 'team', 'extras', 'rewards', 'app', 'run', 'ship', 'look'\];/);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('nothing the live wizard does is lost: every capability in the direction table is reachable on the page', () => {
|
|
102
|
+
const has = (where, html, ids) => { for (const id of ids) assert.match(html, new RegExp(`id="${id}"`), `${where} hosts #${id}`); };
|
|
103
|
+
has('part 01', part('part-project'), ['newName', 'typeCards', 'f-desc', 'physFillOffer', 'physFromAccount', 'demoSetup', 'demoIdea', 'demoInvites', 'demoStart',
|
|
104
|
+
'physCountry', 'physRegionField', 'physAges', 'physIntent', 'physHaveEntity', 'physForms', 'checksStop', 'physStopMsg']);
|
|
105
|
+
has('the Code row', row('code'), ['physExisting', 'c-green', 'c-adopt', 'adoptNote', 'ghNoAccess', 'ghAdopt', 'ghSearch', 'ghFresh', 'manualHome', 'h-home']);
|
|
106
|
+
has('the Address row', row('address'), ['f-slug', 'addrConfirm', 'addrCustom', 'f-domain', 'f-noaddr']);
|
|
107
|
+
has('the Team row', row('team'), ['physTeam', 'physStartN', 'physExpected', 'teamCards']);
|
|
108
|
+
has('the Extras row', row('extras'), ['modQ', 'modCards', 'modWhy', 'modCoreNote']);
|
|
109
|
+
assert.match(row('rewards'), /data-phys="reward_intents" data-val="non-monetary"[\s\S]*data-val="future-payouts"/, 'Rewards: pick several');
|
|
110
|
+
has('the Hosting row', row('app'), ['appOpts', 'renderLater', 'renderSetup', 'f-renderKey', 'renderWs', 'renderSvc', 'renderNew']);
|
|
111
|
+
has('the We run row', row('run'), ['physSuite']);
|
|
112
|
+
assert.match(row('run'), /data-val="hybrid" aria-pressed="false">Some of it<\/button>/, 'We run: "Some of it" reveals the service checklist');
|
|
113
|
+
has('the Shipping row', row('ship'), ['shipOpts']);
|
|
114
|
+
has('the Look row', row('look'), ['wizLook', 'lookDot']);
|
|
115
|
+
has('the page', PAGE, ['wizResume', 'termsAck', 'doCreate', 'wizRecap', 'createWhy', 'createAlert', 'humanSteps', 'liveProgress', 'wizInvite', 'done-h']);
|
|
116
|
+
const ids = [...VIEW.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]);
|
|
117
|
+
assert.deepEqual(ids.filter((id, i) => ids.indexOf(id) !== i), [], 'moved, never copied: every id in the view is unique');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('Create waits for a name and the terms tick only, and says why in place (executed)', () => {
|
|
121
|
+
const src = slice('var createBusy = false;', '$(\'termsAck\').checked = !!state.termsAck;');
|
|
122
|
+
const els = {};
|
|
123
|
+
const el = (id) => (els[id] = els[id] || { id, disabled: false, textContent: '', attrs: {}, setAttribute(k, v) { this.attrs[k] = v; } });
|
|
124
|
+
const ctx = { state: { step: 1, name: '', termsAck: false, projType: null }, $: el, renderRecap() {}, phys: () => ({ answers: {} }) };
|
|
125
|
+
vm.createContext(ctx);
|
|
126
|
+
vm.runInContext(src + '; this.paintCreate = paintCreate; this.createWhy = createWhy;', ctx);
|
|
127
|
+
ctx.paintCreate();
|
|
128
|
+
assert.equal(el('doCreate').disabled, true);
|
|
129
|
+
assert.equal(el('createWhy').textContent, 'Give it a name first.', 'no name: says so');
|
|
130
|
+
ctx.state.name = ' Kestrel ';
|
|
131
|
+
ctx.paintCreate();
|
|
132
|
+
assert.equal(el('doCreate').disabled, true);
|
|
133
|
+
assert.equal(el('createWhy').textContent, 'Tick the terms to create your project.', 'a name, no tick: says so');
|
|
134
|
+
ctx.state.termsAck = true;
|
|
135
|
+
ctx.paintCreate();
|
|
136
|
+
assert.equal(el('doCreate').disabled, false, 'a name and the tick: Create is enabled, whatever the rows say');
|
|
137
|
+
assert.equal(el('createWhy').textContent, ' ');
|
|
138
|
+
assert.equal(el('wizRoom').attrs['data-fill'], '2', 'the seed grows as the page fills in');
|
|
139
|
+
ctx.state.step = 2;
|
|
140
|
+
ctx.paintCreate();
|
|
141
|
+
assert.equal(el('doCreate').textContent, 'Created');
|
|
142
|
+
assert.equal(el('doCreate').disabled, true, 'once created, Create stands down');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('Create runs the screening stop before anything exists, and a row that cannot be filed opens with its reason', () => {
|
|
146
|
+
const create = slice('$(\'doCreate\').addEventListener(\'click\'', '/* ── done panel');
|
|
147
|
+
const order = ['createWhy()', 'firstInvalidRow()', 'physScreen()', 'ensureFreshRepo()', "fetch(API + '/provisioning/instances'"].map((s) => create.indexOf(s));
|
|
148
|
+
assert.ok(order.every((i, k) => i > 0 && (k === 0 || i > order[k - 1])), 'name and tick, then the rows, then the screen, then the repo, then the create');
|
|
149
|
+
assert.match(create, /if \(!ok\) \{ var stop = new Error\('stopped'\); stop\.stopped = true; throw stop; \}/, 'a stop files nothing');
|
|
150
|
+
assert.match(SCRIPT, /if \(res && res\.stop\) \{ \$\('physStopMsg'\)\.textContent = res\.message \|\| ''; checksShow\('stop'\); return false; \}/, 'the stop is said in place, in the server\'s own words');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('the room follows the viewer\'s own clock: day 07:00 to 18:59, re-checked each minute (executed)', () => {
|
|
154
|
+
const src = slice('function skyAt(d)', 'function paintSky()');
|
|
155
|
+
const ctx = {};
|
|
156
|
+
vm.createContext(ctx);
|
|
157
|
+
vm.runInContext(src + '; this.skyAt = skyAt;', ctx);
|
|
158
|
+
const at = (h, m) => ctx.skyAt(new Date(2026, 9, 8, h, m));
|
|
159
|
+
assert.equal(at(6, 59), 'night');
|
|
160
|
+
assert.equal(at(7, 0), 'day');
|
|
161
|
+
assert.equal(at(12, 0), 'day');
|
|
162
|
+
assert.equal(at(18, 59), 'day');
|
|
163
|
+
assert.equal(at(19, 0), 'night');
|
|
164
|
+
assert.equal(at(0, 0), 'night');
|
|
165
|
+
assert.match(SCRIPT, /function paintSky\(\) \{ root\.setAttribute\('data-sky', skyAt\(new Date\(\)\)\); \}\n\s*paintSky\(\);\n\s*setInterval\(paintSky, 60000\);/, 'painted at once and every minute');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('the day room is the mockup\'s, drawn from the fifteen, and its motion sits inside the kill switch', () => {
|
|
169
|
+
const day = (STYLE.match(/#view-new\[data-sky="day"\] \.wizRoom\{[\s\S]*?\n\}/) || [''])[0];
|
|
170
|
+
assert.ok(day, 'the day room re-points the room\'s own tokens');
|
|
171
|
+
for (const t of ['--rm-room', '--rm-night', '--rm-moon', '--rm-tower-1', '--rm-pane', '--rm-floor', '--rm-glow', '--rm-leaf-1']) assert.match(day, new RegExp(`${t}:`), `${t} has a daylight value`);
|
|
172
|
+
const words = (STYLE.match(/#view-new\[data-sky="day"\]>\*\{[\s\S]*?\n\}/) || [''])[0];
|
|
173
|
+
assert.ok(words, 'the words get a light ground\'s tokens, on the view\'s children (never :root)');
|
|
174
|
+
for (const block of [day, words]) assert.doesNotMatch(block, /#(?!fff\b|000\b)[0-9a-f]{3,8}\b/i, 'no colour literal but black or white');
|
|
175
|
+
assert.match(words, /--ok:color-mix\(in srgb,var\(--wz-ok\) 40%,#000\);/, 'a status ink is read off the page\'s own through --wz-*, never itself');
|
|
176
|
+
assert.match(STYLE, /#view-new\[data-sky="day"\] \.rmCloud\{opacity:\.9;\}/, 'clouds by day');
|
|
177
|
+
assert.match(STYLE, /#view-new\[data-sky="day"\] \.rmSun\{opacity:\.7;\}/, 'and a patch of sun on the floor');
|
|
178
|
+
const calm = (STYLE.match(/@media \(prefers-reduced-motion:no-preference\)\{\n {2}#view-new \.rmSeed[\s\S]*?\n\}/) || [''])[0];
|
|
179
|
+
assert.match(calm, /#view-new \.rmCloud\{animation:rmDrift 60s linear infinite;\}/, 'the drift only without reduced motion');
|
|
180
|
+
assert.match(calm, /transition:background \.8s ease/, 'and the change of light too');
|
|
181
|
+
assert.doesNotMatch(STYLE.replace(calm, ''), /rmDrift 60s|animation:rmTwinkle|animation:rmBreathe/, 'no room motion outside the no-preference block');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('part 03 is the checklist, every step on show, with the state line from the server\'s own record', () => {
|
|
185
|
+
assert.match(part('panel-done') || PAGE, /id="panel-done"/);
|
|
186
|
+
assert.match(PAGE, /<div class="part waiting" id="panel-done" aria-labelledby="done-part-h">/, 'part 03 waits until the project exists');
|
|
187
|
+
assert.match(SCRIPT, /done\.classList\.toggle\('waiting', !created\);/);
|
|
188
|
+
const steps = slice('function stepsPaint()', '(function hopInput()');
|
|
189
|
+
assert.match(steps, /'connected ✓'/);
|
|
190
|
+
assert.match(steps, /'waiting on GitHub…'/);
|
|
191
|
+
assert.match(steps, /'ready'/);
|
|
192
|
+
assert.doesNotMatch(steps, /innerHTML/, 'no markup built from strings');
|
|
193
|
+
assert.doesNotMatch(STYLE, /humanSteps>li:not\(\.cur\)/, 'the checklist is no longer paged one step at a time');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('the name is typed, so the handle under it is built from nodes, never markup', () => {
|
|
197
|
+
const hint = slice('function paintNameHint()', 'if (state.name && state.slug) paintNameHint();');
|
|
198
|
+
assert.match(hint, /code\.textContent = state\.slug;/);
|
|
199
|
+
assert.doesNotMatch(hint, /innerHTML/);
|
|
200
|
+
assert.doesNotMatch(SCRIPT, /\$\('h-name'\)\.innerHTML/);
|
|
201
|
+
});
|
package/tests/wizard_physics.mjs
CHANGED
|
@@ -104,7 +104,7 @@ const SCRIPT = HUB.slice(HUB.indexOf(START), HUB.indexOf(END, HUB.indexOf(START)
|
|
|
104
104
|
|
|
105
105
|
test('the page carries the server\'s countries, regions and entity forms, exactly', () => {
|
|
106
106
|
assert.ok(SCRIPT.length > 1000, 'the physics controller was found');
|
|
107
|
-
assert.ok(SCRIPT.includes('function
|
|
107
|
+
assert.ok(SCRIPT.includes('function physScreen()'), 'and it carries the screen Create asks (task 1004648)');
|
|
108
108
|
const box = {};
|
|
109
109
|
vm.createContext(box);
|
|
110
110
|
vm.runInContext(SCRIPT.slice(0, SCRIPT.indexOf(' var physLimits = [];')).replace(/\bvar /g, 'this.'), box);
|
|
@@ -121,11 +121,11 @@ test('the page carries the server\'s countries, regions and entity forms, exactl
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
test('every choice the questions offer is an answer the server accepts', () => {
|
|
124
|
-
// the quick checks, and the rows of
|
|
125
|
-
const panel = HUB.slice(HUB.indexOf('<section id="view-new"'), HUB.indexOf('
|
|
126
|
-
const checks = HUB.slice(HUB.indexOf('id="
|
|
127
|
-
assert.deepEqual([...new Set([...checks.matchAll(/data-phys="([a-z_]+)"/g)].map((m) => m[1]))], ['
|
|
128
|
-
'the quick checks ask where builders will be
|
|
124
|
+
// the quick checks in part 01, and the rows of part 02 that host the rest (task 1004648)
|
|
125
|
+
const panel = HUB.slice(HUB.indexOf('<section id="view-new"'), HUB.indexOf('id="termsAck"'));
|
|
126
|
+
const checks = HUB.slice(HUB.indexOf('id="checksMain"'), HUB.indexOf('id="checksStop"'));
|
|
127
|
+
assert.deepEqual([...new Set([...checks.matchAll(/data-phys="([a-z_]+)"/g)].map((m) => m[1]))], ['minors_on_board', 'builder_geography', 'entity'],
|
|
128
|
+
'the quick checks ask about under-18s, where builders will be and who is behind it in the mockup’s order');
|
|
129
129
|
assert.match(checks, /<select id="physCountry">/, 'and where you are');
|
|
130
130
|
const seen = [...panel.matchAll(/data-phys="([a-z_]+)" data-val="([^"]+)"/g)];
|
|
131
131
|
assert.ok(seen.length >= 20, 'the questions\' choices');
|
|
@@ -172,13 +172,13 @@ function boot({ checkAnswer = { stop: false, soft_limits: { limits: [] } } } = {
|
|
|
172
172
|
const $ = (id) => (els[id] = els[id] || new El(id));
|
|
173
173
|
const shown = [];
|
|
174
174
|
const fetched = [];
|
|
175
|
-
const state = { description: '', teamShape: null, physics: null, mode: 'greenfield'
|
|
175
|
+
const state = { description: '', teamShape: null, physics: null, mode: 'greenfield' };
|
|
176
176
|
const sandbox = {
|
|
177
177
|
$, state, API: '/api/bongos', document: { createElement: () => new El('opt') },
|
|
178
178
|
root: { querySelectorAll: () => [], querySelector: () => null },
|
|
179
179
|
saveDraft() {}, show(n) { shown.push(n); }, Intl,
|
|
180
|
-
//
|
|
181
|
-
paintSetup() {},
|
|
180
|
+
// part 02's machinery lives outside this region (tests/wizard_physics_more.mjs runs it)
|
|
181
|
+
paintSetup() {}, paintCreate() {}, setMode(m) { state.mode = m; }, authKnown: true,
|
|
182
182
|
fetch: async (url, opts) => { fetched.push({ url, body: opts && opts.body && JSON.parse(opts.body) }); return { ok: true, json: async () => (url.includes('physics-check') ? checkAnswer : []) }; },
|
|
183
183
|
};
|
|
184
184
|
vm.createContext(sandbox);
|
|
@@ -187,56 +187,61 @@ function boot({ checkAnswer = { stop: false, soft_limits: { limits: [] } } } = {
|
|
|
187
187
|
}
|
|
188
188
|
const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
189
189
|
|
|
190
|
-
test('
|
|
190
|
+
test('Create asks the server\'s screen with the answers given so far, prefills included, and its soft limits shape part 02 (task 1004648)', async () => {
|
|
191
191
|
const w = boot({ checkAnswer: { stop: false, soft_limits: { limits: [{ option: 'equity', effect: 'note', reason: 'r' }] } } });
|
|
192
|
+
w.run('phys(); physDefaults();');
|
|
192
193
|
const a = () => w.state.physics.answers;
|
|
193
|
-
|
|
194
|
-
assert.match(w.$('h-phys').textContent, /Pick your country/);
|
|
195
|
-
a().home_country = 'US'; a().home_region = 'US-CA';
|
|
196
|
-
w.run('checksAdvance()');
|
|
197
|
-
assert.match(w.$('h-phys').textContent, /where builders will be/);
|
|
198
|
-
a().builder_geography = 'us-eu';
|
|
199
|
-
w.run('checksAdvance()');
|
|
200
|
-
assert.match(w.$('h-phys').textContent, /under 18/);
|
|
201
|
-
a().minors_on_board = true;
|
|
202
|
-
w.run('checksAdvance()');
|
|
203
|
-
assert.match(w.$('h-phys').textContent, /who’s behind it/);
|
|
204
|
-
assert.equal(w.fetched.length, 0, 'nothing is asked of the server until every answer is given');
|
|
205
|
-
a().entity = 'for-profit';
|
|
194
|
+
a().home_country = 'US'; a().home_region = 'US-CA'; a().builder_geography = 'us-eu'; a().minors_on_board = true; a().entity = 'for-profit';
|
|
206
195
|
w.state.description = 'A co-op farming game.';
|
|
207
|
-
|
|
208
|
-
w.
|
|
209
|
-
|
|
210
|
-
|
|
196
|
+
let ok = null;
|
|
197
|
+
w.sandbox.done = (v) => { ok = v; };
|
|
198
|
+
w.run('physScreen().then(done)');
|
|
199
|
+
await tick(); await tick(); await tick();
|
|
200
|
+
assert.equal(ok, true, 'no stop: Create goes on');
|
|
201
|
+
assert.equal(w.fetched.length, 1);
|
|
211
202
|
assert.equal(w.fetched[0].url, '/api/bongos/provisioning/physics-check');
|
|
212
|
-
//
|
|
203
|
+
// part 02's prefills ride along: a new project, one person, all managed
|
|
213
204
|
assert.deepEqual(w.fetched[0].body, { description: 'A co-op farming game.', detail: {
|
|
214
205
|
project_state: 'new', home_country: 'US', home_region: 'US-CA', builder_geography: 'us-eu', minors_on_board: true, entity: 'for-profit',
|
|
215
206
|
builders_at_start: 1, builders_expected: 1, framing: 'managed', voice: 'professional' } });
|
|
216
207
|
assert.equal(w.state.teamShape, 'solo', 'one person is a solo team shape');
|
|
217
|
-
assert.equal(w
|
|
218
|
-
assert.deepEqual(w.shown, [
|
|
219
|
-
|
|
208
|
+
assert.equal(JSON.stringify(w.run('physLimits')), JSON.stringify([{ option: 'equity', effect: 'note', reason: 'r' }]), 'and its soft limits shape the rows');
|
|
209
|
+
assert.deepEqual(w.shown, [], 'the screen moves nothing on itself: Create does');
|
|
210
|
+
const unanswered = boot();
|
|
211
|
+
unanswered.sandbox.done = (v) => { ok = v; };
|
|
212
|
+
ok = null;
|
|
213
|
+
unanswered.run('physScreen().then(done)');
|
|
214
|
+
await tick(); await tick(); await tick();
|
|
215
|
+
assert.equal(ok, true, 'an unanswered check is not a stop — the create runs the same screen and is the authority');
|
|
216
|
+
const demo = boot();
|
|
217
|
+
demo.state.demo = { people: 3 };
|
|
218
|
+
demo.sandbox.done = (v) => { ok = v; };
|
|
219
|
+
ok = null;
|
|
220
|
+
demo.run('physScreen().then(done)');
|
|
221
|
+
await tick();
|
|
222
|
+
assert.equal(ok, true);
|
|
223
|
+
assert.equal(demo.fetched.length, 0, 'a demo\'s answers are its own: nothing to screen');
|
|
220
224
|
});
|
|
221
225
|
|
|
222
|
-
test('a stopped
|
|
226
|
+
test('a stopped answer lands on the stop in place of the checks, with the server\'s words, and the answers survive the way back', async () => {
|
|
223
227
|
const w = boot({ checkAnswer: { stop: true, message: STOP_MESSAGE, soft_limits: { limits: [] } } });
|
|
224
228
|
w.run('phys()');
|
|
225
229
|
Object.assign(w.state.physics.answers, { home_country: 'NL', builder_geography: 'global', minors_on_board: false, entity: 'intent-solo' });
|
|
226
230
|
w.state.description = 'ransomware';
|
|
227
|
-
|
|
228
|
-
|
|
231
|
+
let ok = null;
|
|
232
|
+
w.sandbox.done = (v) => { ok = v; };
|
|
233
|
+
w.run('physScreen().then(done)');
|
|
234
|
+
await tick(); await tick(); await tick();
|
|
235
|
+
assert.equal(ok, false, 'Create files nothing');
|
|
229
236
|
assert.equal(w.$('physStopMsg').textContent, STOP_MESSAGE);
|
|
230
|
-
assert.equal(w.$('checksStop').hidden, false, 'the stop
|
|
237
|
+
assert.equal(w.$('checksStop').hidden, false, 'the stop shows');
|
|
231
238
|
assert.equal(w.$('checksMain').hidden, true, 'in place of the questions');
|
|
232
239
|
assert.deepEqual(w.shown, [], 'and nothing moves on: nothing exists to undo');
|
|
233
|
-
w.state.confirmed = { code: true };
|
|
234
240
|
w.$('physStartOver').listeners.click();
|
|
235
|
-
assert.equal(w.
|
|
236
|
-
assert.equal(JSON.stringify(w.state.physics.answers), '{}', 'every answer goes');
|
|
237
|
-
assert.equal(JSON.stringify(w.state.confirmed), '{}', 'and every confirmed row');
|
|
238
|
-
assert.deepEqual(w.shown, [2], 'back to "What are you making?"');
|
|
241
|
+
assert.equal(w.$('checksStop').hidden, true, 'Change my answers takes the stop down');
|
|
239
242
|
assert.equal(w.$('checksMain').hidden, false, 'with the questions back in place');
|
|
243
|
+
assert.equal(w.state.description, 'ransomware', 'and every answer kept, to change the one that stopped it');
|
|
244
|
+
assert.equal(w.state.physics.answers.home_country, 'NL');
|
|
240
245
|
});
|
|
241
246
|
|
|
242
247
|
test('the create sends the answers as detail: existing work only for an existing planet, the team shape beside them', () => {
|
|
@@ -263,7 +268,7 @@ test('the forming planet is gone: the night room stands behind the wizard instea
|
|
|
263
268
|
assert.ok(!HUB.includes(gone), `${gone} is gone`);
|
|
264
269
|
}
|
|
265
270
|
assert.doesNotMatch(HUB, /id="(physPlanet|physGuard|physStage|panel-physics|physSummary)"/, 'and so is its markup');
|
|
266
|
-
assert.match(HUB, /<div class="wizRoom" id="wizRoom" data-screen="
|
|
271
|
+
assert.match(HUB, /<div class="wizRoom" id="wizRoom" data-screen="page" data-fill="0" aria-hidden="true">/, 'the room is decoration only');
|
|
267
272
|
const seed = HUB.match(/<span class="rmSeed"><img src="\/assets\/world\/([a-z0-9-]+)\.webp" alt=""><\/span>/);
|
|
268
273
|
assert.ok(seed, 'the room\'s seed is a world plate');
|
|
269
274
|
assert.ok(fs.existsSync(path.join(LANDING, 'assets', 'world', `${seed[1]}.webp`)), `${seed[1]} exists`);
|