@bongos/core 1.21.4 → 1.21.5
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 +118 -63
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +2 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/adr/0362-the-governor-docket-is-collected-from-contributed-sources.md +34 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +35 -1
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +389 -518
- package/docs/copy-registry.json +880 -2143
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +17 -25
- package/docs/page-readings.json +1008 -1246
- package/modules/autonomy/governor-docket.js +101 -0
- package/modules/autonomy/routes/autonomy.js +5 -0
- package/modules/discord/board-broadcast.js +3 -1
- package/modules/discord/craft-broadcast.js +107 -0
- package/modules/discord/routes/discord.js +10 -0
- package/modules/discord/ship-broadcast.js +30 -2
- package/modules/economy/reward.js +3 -0
- package/modules/government/board.js +20 -5
- package/modules/government/docket-vocab.js +82 -0
- package/modules/government/docket.js +458 -0
- package/modules/government/routes/government.js +34 -0
- package/modules/hall-ui/public/genesis-home.js +9 -10
- package/modules/ideas/governor-docket.js +93 -0
- package/modules/ideas/routes/blockers.js +5 -0
- package/modules/lifecycle/governor-docket.js +192 -0
- package/modules/lifecycle/routes/gate-approvals.js +78 -33
- package/modules/lifecycle/routes/lifecycle.js +3 -0
- package/modules/onboarding/routes/access-requests.js +68 -27
- package/modules/public-landing/public/assets/cosmos.css +6 -50
- package/modules/public-landing/public/projects.html +1170 -1659
- package/modules/public-landing/public/projects.probes.json +18 -18
- package/modules/public-landing/public/projects.states.json +23 -31
- package/modules/security/routes/reports.js +39 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/src/bongos/module-rechecks.js +77 -0
- package/src/bongos/route-rank-check.js +3 -0
- package/src/bongos/routes/core-update.js +41 -0
- package/src/bongos/routes/modules.js +17 -0
- package/src/module-api.js +1 -1
- package/tests/discord_craft_broadcast.mjs +162 -0
- package/tests/genesis_home.mjs +1 -1
- package/tests/government_routes.mjs +3 -1
- package/tests/governor_docket.mjs +552 -0
- package/tests/onboard.mjs +10 -4
- package/tests/platform_boot.mjs +1 -1
- package/tests/projects_hub.mjs +196 -89
- package/tests/projects_hub_app_status.mjs +3 -1
- package/tests/projects_hub_app_step.mjs +72 -48
- package/tests/projects_hub_dns_ready.mjs +10 -2
- package/tests/projects_hub_look.mjs +32 -22
- package/tests/projects_hub_module_picker.mjs +112 -62
- package/tests/projects_hub_pre_uat.mjs +48 -41
- package/tests/public_landing_projects.mjs +12 -6
- package/tests/wizard_demo.mjs +37 -11
- package/tests/wizard_draft_resume.mjs +34 -22
- package/tests/wizard_front_door.mjs +173 -103
- package/tests/wizard_intent_resume.mjs +28 -25
- package/tests/wizard_physics.mjs +94 -76
- package/tests/wizard_physics_more.mjs +409 -277
- package/tests/wizard_six_screens.mjs +126 -0
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
// ruled out the task's other option (drop the record at a terminal state).
|
|
16
16
|
//
|
|
17
17
|
// The reset fires only when there is a SECOND DRAFT to lose, and the draft's own step is
|
|
18
|
-
// what says so. A landed create clears DRAFT_KEY — but show(
|
|
18
|
+
// what says so. A landed create clears DRAFT_KEY — but show(6) then persists the done
|
|
19
19
|
// panel's position straight back into it, so a plain "is there a draft?" test is always
|
|
20
20
|
// yes and would skip the reset forever, leaving the finished project's name sitting in
|
|
21
21
|
// the fresh form. (That is exactly what the first cut of this change shipped into a
|
|
22
22
|
// render: "still watching Mercury" above a name field still reading "Mercury".) Only
|
|
23
|
-
// steps 1-
|
|
23
|
+
// steps 1-5 are wizard steps since task 1004504's six blended screens (1-11 before them, 1-10
|
|
24
|
+
// before task 1004421's Look step, 1-8 before task 1004417's front door took 1 and 2);
|
|
24
25
|
// anything else is the done panel's own bookkeeping.
|
|
25
26
|
//
|
|
26
27
|
// Harness: there is no jsdom in this repo, so this follows tests/wizard_draft_resume.mjs
|
|
@@ -58,8 +59,8 @@ test('both slices are still findable (a rename must fail loudly, not silently pa
|
|
|
58
59
|
assert.ok(offerSlice, 'paintResumeOffer() was not found in projects.html');
|
|
59
60
|
assert.match(enterSlice, /if \(focus && !resumeIntent\) \{/, 'the intent branch is part of the slice');
|
|
60
61
|
assert.match(enterSlice, /startProgress\(created\.id\);/, 'so is the resume branch it guards');
|
|
61
|
-
assert.match(enterSlice, /draft\.step >= 1 && draft\.step <=
|
|
62
|
-
'the STORED draft\'s step decides, not state.step — state.step is
|
|
62
|
+
assert.match(enterSlice, /draft\.step >= 1 && draft\.step <= 5 \?/,
|
|
63
|
+
'the STORED draft\'s step decides, not state.step — state.step is 6 for the whole life of a finished standup');
|
|
63
64
|
assert.match(SCRIPT, /if \(created && created\.id\) \{ enterCreated\(created, !!focus\); return; \}/,
|
|
64
65
|
'enter() still hands the created record straight to it');
|
|
65
66
|
});
|
|
@@ -73,7 +74,7 @@ function runEnter({ focus, resumeIntent = false, draft = null, record }) {
|
|
|
73
74
|
const calls = { show: [], resets: 0, offer: [], done: 0, progress: [], polls: 0, focused: 0 };
|
|
74
75
|
// state.step is the DONE step for the whole life of a finished standup — the value
|
|
75
76
|
// a draft-blind reading would have trusted
|
|
76
|
-
const state = { step: draft ? draft.step :
|
|
77
|
+
const state = { step: draft ? draft.step : 6, name: 'Mercury', slug: 'mercury', domain: '', mode: 'greenfield', noAddress: false, modules: null };
|
|
77
78
|
const sandbox = {
|
|
78
79
|
record, focus, resumeIntent, state,
|
|
79
80
|
DRAFT_KEY: 'draft',
|
|
@@ -99,7 +100,7 @@ const REC = { id: 'inst-1', name: 'Acme', slug: 'acme', domain: 'acme.example',
|
|
|
99
100
|
|
|
100
101
|
test('a reload / Back / identity settle (focus false) still resumes the done panel, watch and all', () => {
|
|
101
102
|
const { calls, state } = runEnter({ focus: false, record: REC });
|
|
102
|
-
assert.deepEqual(calls.show, [[
|
|
103
|
+
assert.deepEqual(calls.show, [[6, false]], 'the done panel, and no focus steal on a non-deliberate arrival');
|
|
103
104
|
assert.equal(calls.resets, 0, 'nothing is reset — this is the same standup continuing');
|
|
104
105
|
assert.equal(calls.done, 1, 'the checklist is re-rendered');
|
|
105
106
|
assert.deepEqual(calls.progress, ['inst-1'], 'the progress watch is restarted from the record');
|
|
@@ -114,7 +115,7 @@ test('a deliberate "New project" click after a finished standup opens a FRESH st
|
|
|
114
115
|
assert.equal(calls.progress.length, 0, 'and its watch is not restarted');
|
|
115
116
|
assert.equal(calls.resets, 1, 'with no second draft to lose, the wizard forgets the finished project');
|
|
116
117
|
assert.deepEqual(calls.show, [[1, false]], 'step 1, and step 1 does not take the heading focus');
|
|
117
|
-
assert.equal(calls.focused, 1, 'its
|
|
118
|
+
assert.equal(calls.focused, 1, 'its first control, the fork\'s terms tick, does — the deliberate-nav rule enter() already applies');
|
|
118
119
|
assert.equal(state.name, '', 'the old name is gone from the form');
|
|
119
120
|
});
|
|
120
121
|
|
|
@@ -127,7 +128,7 @@ test('…and the standup stays one click away, named, on the resume link', () =>
|
|
|
127
128
|
|
|
128
129
|
test('the resume link gets the done panel back — the record was never cleared', () => {
|
|
129
130
|
const { calls, sandbox } = runEnter({ focus: true, resumeIntent: true, record: REC });
|
|
130
|
-
assert.deepEqual(calls.show, [[
|
|
131
|
+
assert.deepEqual(calls.show, [[6, true]], 'a deliberate resume moves focus to the done panel');
|
|
131
132
|
assert.deepEqual(calls.progress, ['inst-1'], 'the watch comes back with it');
|
|
132
133
|
assert.equal(sandbox.resumeIntent, false, 'the flag is ONE-SHOT — the next plain nav click must mean "new" again');
|
|
133
134
|
assert.deepEqual(calls.offer, [null], 'and the offer comes down behind it');
|
|
@@ -137,29 +138,30 @@ test('a second draft already under way is NOT wiped by navigating back to the wi
|
|
|
137
138
|
// The regression this guards: with the reset unconditional, typing a name, stepping
|
|
138
139
|
// away to "Your projects" and clicking "New project" again would blank the draft —
|
|
139
140
|
// trading the reported bug for a worse one.
|
|
140
|
-
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step:
|
|
141
|
+
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step: 3 } });
|
|
141
142
|
assert.equal(calls.resets, 0, 'a live draft is never reset');
|
|
142
|
-
assert.deepEqual(calls.show, [[
|
|
143
|
-
assert.equal(state.step,
|
|
143
|
+
assert.deepEqual(calls.show, [[3, true]], 'the second draft resumes where it was left, heading focused');
|
|
144
|
+
assert.equal(state.step, 3);
|
|
144
145
|
assert.deepEqual(calls.offer, ['Acme'], 'the offer stays up beside it');
|
|
145
146
|
});
|
|
146
147
|
|
|
147
148
|
test('the done panel\'s own saved step is NOT a second draft — it must not block the reset', () => {
|
|
148
|
-
// Caught in a render, not in a unit test: show(
|
|
149
|
+
// Caught in a render, not in a unit test: show(6) calls saveDraft(), so DRAFT_KEY is
|
|
149
150
|
// non-empty again the moment a create lands. Reading "is there a draft?" left the
|
|
150
151
|
// finished project's name in the fresh form, under a line offering to resume it.
|
|
151
|
-
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step:
|
|
152
|
+
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step: 6, name: 'Mercury' } });
|
|
152
153
|
assert.equal(calls.resets, 1, 'the done step is bookkeeping, not a project being typed');
|
|
153
154
|
assert.deepEqual(calls.show, [[1, false]], 'out-of-range steps land on step 1, the front door');
|
|
154
155
|
assert.equal(state.name, '', 'and the finished project\'s name does not survive into it');
|
|
155
156
|
assert.equal(calls.done, 0, 'never back onto the done panel');
|
|
156
157
|
});
|
|
157
158
|
|
|
158
|
-
test('a draft on the
|
|
159
|
-
//
|
|
160
|
-
// Look step (task 1004421)
|
|
161
|
-
//
|
|
162
|
-
|
|
159
|
+
test('a draft on the checks, the review or the look is live too — the last three screens (tasks 1004351, 1004504)', () => {
|
|
160
|
+
// Every renumbering has moved the review: 7 to 8 for the app step, 10 for the front door
|
|
161
|
+
// (task 1004417), 11 for the Look step (task 1004421), 4 for the six blended screens
|
|
162
|
+
// (task 1004504). A guard left behind would reset a second project sitting on its
|
|
163
|
+
// review, silently wiping it.
|
|
164
|
+
for (const step of [3, 4, 5]) {
|
|
163
165
|
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step } });
|
|
164
166
|
assert.equal(calls.resets, 0, `step ${step} is a live draft`);
|
|
165
167
|
assert.deepEqual(calls.show, [[step, true]]);
|
|
@@ -167,7 +169,7 @@ test('a draft on the app step, the Look step or the review is live too — the l
|
|
|
167
169
|
}
|
|
168
170
|
});
|
|
169
171
|
|
|
170
|
-
test('a draft on the
|
|
172
|
+
test('a draft on the fork or the first question is live too — a founder part-way through keeps their place (tasks 1004417, 1004504)', () => {
|
|
171
173
|
for (const step of [1, 2]) {
|
|
172
174
|
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step } });
|
|
173
175
|
assert.equal(calls.resets, 0, `step ${step} is a live draft`);
|
|
@@ -175,8 +177,8 @@ test('a draft on the front door is live too — a founder part-way through it ke
|
|
|
175
177
|
}
|
|
176
178
|
});
|
|
177
179
|
|
|
178
|
-
test('a draft whose step is missing or junk resets too — only 1-
|
|
179
|
-
for (const draft of [{ step: 0 }, { step:
|
|
180
|
+
test('a draft whose step is missing or junk resets too — only 1-5 preserves anything', () => {
|
|
181
|
+
for (const draft of [{ step: 0 }, { step: 6 }, { name: 'half-typed' }]) {
|
|
180
182
|
const { calls } = runEnter({ focus: true, record: REC, draft });
|
|
181
183
|
assert.equal(calls.resets, 1, `step ${JSON.stringify(draft.step)} is not a wizard step`);
|
|
182
184
|
assert.deepEqual(calls.show, [[1, false]]);
|
|
@@ -201,13 +203,14 @@ test('paintResumeOffer names the project by textContent and hides on null (no li
|
|
|
201
203
|
});
|
|
202
204
|
|
|
203
205
|
test('the offer ships as real markup inside step 1, hidden until there is something to resume', () => {
|
|
204
|
-
// step 1 is the
|
|
205
|
-
const panel = HUB.slice(HUB.indexOf('id="panel-
|
|
206
|
+
// step 1 is the fork since task 1004504 (the terms are its tick) — the offer lives there
|
|
207
|
+
const panel = HUB.slice(HUB.indexOf('id="panel-fork"'), HUB.indexOf('id="panel-what"'));
|
|
208
|
+
assert.ok(HUB.indexOf('id="panel-fork"') > -1 && HUB.indexOf('id="panel-what"') > HUB.indexOf('id="panel-fork"'), 'step 1 is findable');
|
|
206
209
|
assert.match(panel, /<p class="resumeOffer" id="wizResume" hidden>still watching <strong id="wizResumeName">your project<\/strong> — <a href="#" id="wizResumeGo">resume it<\/a><\/p>/,
|
|
207
210
|
'baked into the panel (C7), carrying its own hidden state');
|
|
208
211
|
assert.ok(panel.indexOf('id="wizResume"') < panel.indexOf('<form data-next="2">'),
|
|
209
|
-
'it sits above the
|
|
210
|
-
assert.
|
|
212
|
+
'it sits above the fork\'s form, where a visitor reads it before anything else');
|
|
213
|
+
assert.equal((HUB.match(/id="wizResume"/g) || []).length, 1,
|
|
211
214
|
'and only once — a second copy would be a duplicate id');
|
|
212
215
|
assert.match(SCRIPT, /\$\('wizResumeGo'\)\.addEventListener\('click', function \(ev\) \{\n\s*ev\.preventDefault\(\);\n\s*resumeIntent = true;\n\s*enter\(true\);/,
|
|
213
216
|
'the link raises the one-shot intent and re-enters, rather than href-navigating');
|
package/tests/wizard_physics.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
// tests/wizard_physics.mjs — planet physics, questions 1 to 4, in the create wizard
|
|
2
|
-
// (task 1004418 / BV2.PS07, goal 1000121).
|
|
2
|
+
// (task 1004418 / BV2.PS07, goal 1000121; regrouped by task 1004504).
|
|
3
3
|
//
|
|
4
|
-
// Done-when: the
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// Done-when: the questions and their branches work end to end (geography; ages with
|
|
5
|
+
// the guardian tiers; entity, intent when new and legal type when it exists; the stop
|
|
6
|
+
// screen), answers save through the create's `detail`, and the options the soft-limit
|
|
7
|
+
// table hides or greys show their reason (spec D6). Since task 1004504 geography, ages
|
|
8
|
+
// and entity are the "few quick checks" screen and the project's state is the review's
|
|
9
|
+
// Code row; the forming planet left with the rail (the night room replaced it).
|
|
9
10
|
//
|
|
10
11
|
// Three layers, all RUN: the server's pre-create check over a mounted router; the
|
|
11
12
|
// page's vocabularies against planet-physics.js (a page cannot require a module, so
|
|
@@ -93,28 +94,41 @@ test('a builder who loops the check is slowed down, and another builder is not',
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
// ── the page's vocabularies are the server's ─────────────────────────────────
|
|
96
|
-
// questions 5 to 7 joined the same region (task 1004419, tests/wizard_physics_more.mjs)
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// questions 5 to 7 joined the same region (task 1004419, tests/wizard_physics_more.mjs);
|
|
98
|
+
// the region runs to the row checks the old steps made on Next (task 1004504)
|
|
99
|
+
const START = '/* ── planet physics (tasks 1004418 and 1004419, BV2.PS07 + PS11; regrouped by task 1004504) ──';
|
|
100
|
+
const END = ' /* ── checks the old steps made on Next, now made by their rows ── */';
|
|
101
|
+
assert.ok(HUB.indexOf(START) > -1, 'the physics region opens with its sentinel comment');
|
|
102
|
+
assert.ok(HUB.indexOf(END, HUB.indexOf(START)) > HUB.indexOf(START), 'and closes where the row checks begin');
|
|
103
|
+
const SCRIPT = HUB.slice(HUB.indexOf(START), HUB.indexOf(END, HUB.indexOf(START)));
|
|
99
104
|
|
|
100
105
|
test('the page carries the server\'s countries, regions and entity forms, exactly', () => {
|
|
101
106
|
assert.ok(SCRIPT.length > 1000, 'the physics controller was found');
|
|
107
|
+
assert.ok(SCRIPT.includes('function checksAdvance()'), 'and it carries the quick checks\' Continue');
|
|
102
108
|
const box = {};
|
|
103
109
|
vm.createContext(box);
|
|
104
|
-
vm.runInContext(SCRIPT.slice(0, SCRIPT.indexOf(' var
|
|
110
|
+
vm.runInContext(SCRIPT.slice(0, SCRIPT.indexOf(' var physLimits = [];')).replace(/\bvar /g, 'this.'), box);
|
|
105
111
|
const plain = (v) => JSON.parse(JSON.stringify(v)); // values born in the vm
|
|
106
112
|
assert.deepEqual(plain(box.PHYS_COUNTRIES), [...physics.COUNTRY_CODES]);
|
|
107
113
|
assert.deepEqual(Object.keys(box.PHYS_REGIONS).sort(), [...physics.REGION_CODES].sort());
|
|
108
114
|
assert.deepEqual(plain(box.PHYS_ENTITY_FORMS), [...physics.ENTITY_FORMS]);
|
|
109
|
-
for
|
|
115
|
+
// every entity the page has words for is an entity answer (PHYS_MATERIAL, the plates
|
|
116
|
+
// per entity, left with the forming planet in task 1004504)
|
|
117
|
+
assert.equal(box.PHYS_MATERIAL, undefined, 'the per-entity plates are gone');
|
|
118
|
+
for (const k of Object.keys(box.PHYS_SAY.entity)) {
|
|
110
119
|
assert.notEqual(physics.canonicalAnswer(physics.PLANET_PHYSICS_QUESTIONS.entity, k), undefined, `${k} is an entity answer`);
|
|
111
120
|
}
|
|
112
121
|
});
|
|
113
122
|
|
|
114
123
|
test('every choice the questions offer is an answer the server accepts', () => {
|
|
115
|
-
|
|
124
|
+
// the quick checks, and the rows of the review that host the rest (task 1004504)
|
|
125
|
+
const panel = HUB.slice(HUB.indexOf('<section id="view-new"'), HUB.indexOf('<!-- 5 · a look, then Create'));
|
|
126
|
+
const checks = HUB.slice(HUB.indexOf('id="panel-checks"'), HUB.indexOf('<!-- 4 · check what we set up'));
|
|
127
|
+
assert.deepEqual([...new Set([...checks.matchAll(/data-phys="([a-z_]+)"/g)].map((m) => m[1]))], ['builder_geography', 'minors_on_board', 'entity'],
|
|
128
|
+
'the quick checks ask where builders will be, about under-18s and who is behind it');
|
|
129
|
+
assert.match(checks, /<select id="physCountry">/, 'and where you are');
|
|
116
130
|
const seen = [...panel.matchAll(/data-phys="([a-z_]+)" data-val="([^"]+)"/g)];
|
|
117
|
-
assert.ok(seen.length >= 20, 'the
|
|
131
|
+
assert.ok(seen.length >= 20, 'the questions\' choices');
|
|
118
132
|
for (const [, key, raw] of seen) {
|
|
119
133
|
const one = raw === 'true' ? true : raw === 'false' ? false : raw;
|
|
120
134
|
// a list question's choice is one member of the list it sends (task 1004419)
|
|
@@ -128,9 +142,10 @@ test('every choice the questions offer is an answer the server accepts', () => {
|
|
|
128
142
|
});
|
|
129
143
|
|
|
130
144
|
test('the age tiers are described, never claimed as enforced (spec D6)', () => {
|
|
131
|
-
const ages = HUB.slice(HUB.indexOf('id="physAges"'), HUB.indexOf('
|
|
145
|
+
const ages = HUB.slice(HUB.indexOf('id="physAges"'), HUB.indexOf('id="physIntent"'));
|
|
146
|
+
assert.ok(ages.length > 100, 'the age tiers sit between the under-18 question and who is behind it');
|
|
132
147
|
assert.match(ages, /13 to 15<\/dt><dd>Non-monetary tracks only/);
|
|
133
|
-
assert.match(ages, /
|
|
148
|
+
assert.match(ages, /[Tt]he platform does not check ages for you yet/);
|
|
134
149
|
assert.doesNotMatch(ages, /apply automatically|enforced/i);
|
|
135
150
|
});
|
|
136
151
|
|
|
@@ -157,11 +172,13 @@ function boot({ checkAnswer = { stop: false, soft_limits: { limits: [] } } } = {
|
|
|
157
172
|
const $ = (id) => (els[id] = els[id] || new El(id));
|
|
158
173
|
const shown = [];
|
|
159
174
|
const fetched = [];
|
|
160
|
-
const state = { description: '', teamShape: null, physics: null };
|
|
175
|
+
const state = { description: '', teamShape: null, physics: null, mode: 'greenfield', confirmed: {} };
|
|
161
176
|
const sandbox = {
|
|
162
177
|
$, state, API: '/api/bongos', document: { createElement: () => new El('opt') },
|
|
163
178
|
root: { querySelectorAll: () => [], querySelector: () => null },
|
|
164
179
|
saveDraft() {}, show(n) { shown.push(n); }, Intl,
|
|
180
|
+
// the review's machinery lives outside this region (tests/wizard_physics_more.mjs runs it)
|
|
181
|
+
paintSetup() {}, unconfirm() {}, setMode(m) { state.mode = m; }, authKnown: true,
|
|
165
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 : []) }; },
|
|
166
183
|
};
|
|
167
184
|
vm.createContext(sandbox);
|
|
@@ -170,51 +187,56 @@ function boot({ checkAnswer = { stop: false, soft_limits: { limits: [] } } } = {
|
|
|
170
187
|
}
|
|
171
188
|
const tick = () => new Promise((r) => setTimeout(r, 0));
|
|
172
189
|
|
|
173
|
-
test('Continue
|
|
174
|
-
const w = boot();
|
|
190
|
+
test('Continue on the quick checks asks for each answer, then the server, and only then moves on', async () => {
|
|
191
|
+
const w = boot({ checkAnswer: { stop: false, soft_limits: { limits: [{ option: 'equity', effect: 'note', reason: 'r' }] } } });
|
|
175
192
|
const a = () => w.state.physics.answers;
|
|
176
|
-
assert.equal(w.run('
|
|
177
|
-
assert.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
assert.match(w.$('h-phys').textContent, /
|
|
181
|
-
a().
|
|
182
|
-
w.run('
|
|
183
|
-
assert.match(w.$('h-phys').textContent, /
|
|
184
|
-
a().existing_work = ['code', 'digital-files']; a().main_work = 'code';
|
|
185
|
-
w.state.description = 'A co-op farming game.';
|
|
186
|
-
w.run('physAdvance()');
|
|
187
|
-
await tick(); await tick();
|
|
188
|
-
assert.equal(w.state.physics.q, 2, 'the screen passed it, on to geography');
|
|
189
|
-
assert.deepEqual(w.fetched.at(-1).body, { description: 'A co-op farming game.', detail: { project_state: 'existing', existing_work: ['code', 'digital-files'], main_work: 'code' } });
|
|
190
|
-
w.run('physAdvance()');
|
|
191
|
-
assert.equal(w.state.physics.q, 2, 'no country: it stays');
|
|
192
|
-
a().home_country = 'US'; a().home_region = 'US-CA'; a().builder_geography = 'us-eu';
|
|
193
|
-
w.run('physAdvance()');
|
|
194
|
-
assert.equal(w.state.physics.q, 3);
|
|
193
|
+
assert.equal(w.run('checksAdvance()'), false, 'the screen never says yes itself');
|
|
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/);
|
|
195
201
|
a().minors_on_board = true;
|
|
196
|
-
w.run('
|
|
197
|
-
assert.
|
|
198
|
-
w.
|
|
199
|
-
assert.deepEqual(w.shown, [], 'no entity: still here');
|
|
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');
|
|
200
205
|
a().entity = 'for-profit';
|
|
201
|
-
w.
|
|
206
|
+
w.state.description = 'A co-op farming game.';
|
|
207
|
+
w.run('checksAdvance()');
|
|
208
|
+
w.run('checksAdvance()');
|
|
202
209
|
await tick(); await tick();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
assert.deepEqual(w.
|
|
210
|
+
assert.equal(w.fetched.length, 1, 'a second Continue while the first is out asks nothing more');
|
|
211
|
+
assert.equal(w.fetched[0].url, '/api/bongos/provisioning/physics-check');
|
|
212
|
+
// the review's prefills are written on the way: a new project, one person, all managed
|
|
213
|
+
assert.deepEqual(w.fetched[0].body, { description: 'A co-op farming game.', detail: {
|
|
214
|
+
project_state: 'new', home_country: 'US', home_region: 'US-CA', builder_geography: 'us-eu', minors_on_board: true, entity: 'for-profit',
|
|
215
|
+
builders_at_start: 1, builders_expected: 1, framing: 'managed', voice: 'professional' } });
|
|
216
|
+
assert.equal(w.state.teamShape, 'solo', 'one person is a solo team shape');
|
|
217
|
+
assert.equal(w.$('h-phys').textContent, ' ', 'the ask clears');
|
|
218
|
+
assert.deepEqual(w.shown, [4], 'the screen passed it: on to the review');
|
|
219
|
+
assert.equal(JSON.stringify(w.run('physLimits')), JSON.stringify([{ option: 'equity', effect: 'note', reason: 'r' }]), 'and its soft limits shape the review');
|
|
207
220
|
});
|
|
208
221
|
|
|
209
|
-
test('a stopped description lands on the stop screen with the server\'s words', async () => {
|
|
222
|
+
test('a stopped description lands on the stop screen with the server\'s words, and Start over clears it all', async () => {
|
|
210
223
|
const w = boot({ checkAnswer: { stop: true, message: STOP_MESSAGE, soft_limits: { limits: [] } } });
|
|
211
|
-
w.run('
|
|
212
|
-
w.state.physics.answers
|
|
224
|
+
w.run('phys()');
|
|
225
|
+
Object.assign(w.state.physics.answers, { home_country: 'NL', builder_geography: 'global', minors_on_board: false, entity: 'intent-solo' });
|
|
213
226
|
w.state.description = 'ransomware';
|
|
214
|
-
w.run('
|
|
227
|
+
w.run('checksAdvance()');
|
|
215
228
|
await tick(); await tick();
|
|
216
|
-
assert.equal(w.state.physics.q, 'stop');
|
|
217
229
|
assert.equal(w.$('physStopMsg').textContent, STOP_MESSAGE);
|
|
230
|
+
assert.equal(w.$('checksStop').hidden, false, 'the stop screen shows');
|
|
231
|
+
assert.equal(w.$('checksMain').hidden, true, 'in place of the questions');
|
|
232
|
+
assert.deepEqual(w.shown, [], 'and nothing moves on: nothing exists to undo');
|
|
233
|
+
w.state.confirmed = { code: true };
|
|
234
|
+
w.$('physStartOver').listeners.click();
|
|
235
|
+
assert.equal(w.state.description, '');
|
|
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?"');
|
|
239
|
+
assert.equal(w.$('checksMain').hidden, false, 'with the questions back in place');
|
|
218
240
|
});
|
|
219
241
|
|
|
220
242
|
test('the create sends the answers as detail: existing work only for an existing planet, the team shape beside them', () => {
|
|
@@ -223,49 +245,45 @@ test('the create sends the answers as detail: existing work only for an existing
|
|
|
223
245
|
Object.assign(w.state.physics.answers, { project_state: 'new', existing_work: ['code'], main_work: 'code', home_country: 'NL', builder_geography: 'global', minors_on_board: false, entity: 'intent-solo' });
|
|
224
246
|
w.state.teamShape = 'solo';
|
|
225
247
|
assert.deepEqual(JSON.parse(JSON.stringify(w.run('createDetail()'))), {
|
|
226
|
-
project_state: 'new', home_country: 'NL', builder_geography: 'global', minors_on_board: false, entity: 'intent-solo', voice: '
|
|
227
|
-
});
|
|
248
|
+
project_state: 'new', home_country: 'NL', builder_geography: 'global', minors_on_board: false, entity: 'intent-solo', voice: 'professional', team_shape: 'solo',
|
|
249
|
+
}, 'one plain voice since task 1004504: an individual is sent the professional voice too');
|
|
250
|
+
w.state.physics.answers.entity = 'for-profit';
|
|
251
|
+
assert.equal(w.run('createDetail()').voice, 'professional', 'and so is an organization');
|
|
252
|
+
w.state.physics.answers.project_state = 'existing';
|
|
253
|
+
assert.deepEqual(JSON.parse(JSON.stringify(w.run('createDetail()'))).existing_work, ['code'], 'an existing planet sends what exists');
|
|
254
|
+
w.state.demo = { people: 3, hours_each: 4 };
|
|
255
|
+
assert.deepEqual(JSON.parse(JSON.stringify(w.run('createDetail()'))), { team_shape: 'solo' }, 'a demo sends only its team shape');
|
|
256
|
+
w.state.demo = null;
|
|
228
257
|
w.state.physics.answers = {}; w.state.teamShape = null;
|
|
229
258
|
assert.equal(w.run('createDetail()'), undefined, 'nothing answered: the detail is omitted, as before');
|
|
230
259
|
});
|
|
231
260
|
|
|
232
|
-
test('the planet
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
const src = () => w.$('physPlanet').getAttribute('src');
|
|
236
|
-
w.state.physics.q = 1; w.state.physics.answers.project_state = 'new';
|
|
237
|
-
w.run('physPaintStage()');
|
|
238
|
-
assert.equal(src(), '/assets/world/r2-chrome-blob-one-cut.webp', 'a seed');
|
|
239
|
-
w.state.physics.answers.project_state = 'existing';
|
|
240
|
-
w.run('physPaintStage()');
|
|
241
|
-
assert.equal(src(), '/assets/world/r2-chrome-blobs-set-cut.webp', 'terrain already formed');
|
|
242
|
-
assert.equal(w.$('physGuard').hidden, true);
|
|
243
|
-
w.state.physics.q = 3; w.state.physics.answers.minors_on_board = true;
|
|
244
|
-
w.run('physPaintStage()');
|
|
245
|
-
assert.equal(w.$('physGuard').hidden, false, 'the protective ring');
|
|
246
|
-
w.state.physics.q = 4;
|
|
247
|
-
for (const [entity, plate] of [['intent-solo', 'r3-frost-blob-cut'], ['for-profit', 'r3-gold-blob-cut'], ['nonprofit', 'r3-obsidian-blob-cut']]) {
|
|
248
|
-
w.state.physics.answers.entity = entity;
|
|
249
|
-
w.run('physPaintStage()');
|
|
250
|
-
assert.equal(src(), `/assets/world/${plate}.webp`, entity);
|
|
251
|
-
}
|
|
252
|
-
for (const plate of ['r3-frost-blob-cut', 'r3-gold-blob-cut', 'r3-obsidian-blob-cut', 'r2-chrome-blob-one-cut', 'r2-chrome-blobs-set-cut']) {
|
|
253
|
-
assert.ok(fs.existsSync(path.join(LANDING, 'assets', 'world', `${plate}.webp`)), `${plate} is a world plate`);
|
|
261
|
+
test('the forming planet is gone: the night room stands behind the wizard instead (task 1004504)', () => {
|
|
262
|
+
for (const gone of ['physPaintStage', 'PHYS_MATERIAL', 'PHYS_CORE_WORD', 'PHYS_ORBIT', 'physAdvance', 'physNextQ', 'physPrevQ', 'physSummaryRows']) {
|
|
263
|
+
assert.ok(!HUB.includes(gone), `${gone} is gone`);
|
|
254
264
|
}
|
|
265
|
+
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="fork" aria-hidden="true">/, 'the room is decoration only');
|
|
267
|
+
const seed = HUB.match(/<span class="rmSeed"><img src="\/assets\/world\/([a-z0-9-]+)\.webp" alt=""><\/span>/);
|
|
268
|
+
assert.ok(seed, 'the room\'s seed is a world plate');
|
|
269
|
+
assert.ok(fs.existsSync(path.join(LANDING, 'assets', 'world', `${seed[1]}.webp`)), `${seed[1]} exists`);
|
|
255
270
|
});
|
|
256
271
|
|
|
257
272
|
test('a limit the table puts on an option shows its reason beside it, and a grey one is disabled', () => {
|
|
258
273
|
const w = boot();
|
|
259
274
|
const why = new El('why');
|
|
260
275
|
const btn = new El('btn');
|
|
276
|
+
const fieldset = new El('physExisting');
|
|
261
277
|
w.sandbox.root.querySelector = (sel) => (sel.includes('data-why="existing_work"') ? why : null);
|
|
262
|
-
w.sandbox.root.querySelectorAll = (sel) => (sel.includes('data-phys="entity"][data-val="intent-solo"') ? [btn]
|
|
278
|
+
w.sandbox.root.querySelectorAll = (sel) => (sel.includes('data-phys="entity"][data-val="intent-solo"') ? [btn]
|
|
279
|
+
: sel.includes('[data-limit="existing_work"]') ? [fieldset] : []);
|
|
263
280
|
w.run(`physLimits = [
|
|
264
281
|
{ option: 'existing_work', effect: 'hide', reason: 'Your planet is new, so there is nothing built yet to describe.' },
|
|
265
282
|
{ option: 'entity:intent-solo', effect: 'grey', reason: 'Because.' }
|
|
266
283
|
]; physApplyLimits();`);
|
|
267
284
|
assert.equal(why.hidden, false);
|
|
268
285
|
assert.match(why.textContent, /nothing built yet/);
|
|
286
|
+
assert.equal(fieldset.hidden, true, 'a question the table hides is hidden in place');
|
|
269
287
|
assert.equal(btn.disabled, true);
|
|
270
288
|
assert.equal(btn.title, 'Because.');
|
|
271
289
|
});
|