@bongos/core 1.21.3 → 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 +128 -68
- 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 +4 -0
- package/docs/page-inventory.json +17 -25
- package/docs/page-readings.json +1008 -1246
- package/migrations/core_272_rename_cn_criterion_slugs.sql +40 -0
- 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/criterion-ref.js +11 -0
- package/modules/lifecycle/db-tasks.js +7 -0
- package/modules/lifecycle/governor-docket.js +192 -0
- package/modules/lifecycle/routes/done-when.js +6 -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 +20 -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/criterion_goal_attach.mjs +12 -4
- package/tests/criterion_ref_parse.mjs +41 -0
- 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
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// tests/projects_hub_app_step.mjs — the create wizard's "where your app runs"
|
|
2
|
-
// EXECUTED (task 1004351, ADR 0345), and its Render setup (task 1004352, ADR 0348).
|
|
1
|
+
// tests/projects_hub_app_step.mjs — the create wizard's "where your app runs" question,
|
|
2
|
+
// EXECUTED (task 1004351, ADR 0345), and its Render setup (task 1004352, ADR 0348). Since
|
|
3
|
+
// task 1004504 the old App step is the review's "App hosting" row: its controls moved into
|
|
4
|
+
// the row whole, and "Use this" runs the check its Next used to.
|
|
3
5
|
//
|
|
4
6
|
// A static regex cannot tell a guard that works from one that is never consulted, so —
|
|
5
7
|
// as tests/projects_hub_module_picker.mjs does for the picker — this slices the block out
|
|
6
|
-
// of projects.html by its
|
|
8
|
+
// of projects.html by the code at its edges and runs it in `vm` under a small DOM stub.
|
|
7
9
|
//
|
|
8
10
|
// The load-bearing assertions for Render, because the key reaches the owner's WHOLE
|
|
9
11
|
// Render account:
|
|
@@ -13,7 +15,7 @@
|
|
|
13
15
|
// must choose, and Next refuses until they do
|
|
14
16
|
// • a refused key says so at once, next to the field
|
|
15
17
|
// • a service building another repository cannot be linked
|
|
16
|
-
// • the
|
|
18
|
+
// • the App hosting row only ever names what the owner actually chose
|
|
17
19
|
//
|
|
18
20
|
// Run: node --test --test-reporter=tap tests/projects_hub_app_step.mjs
|
|
19
21
|
|
|
@@ -29,13 +31,20 @@ const require = createRequire(import.meta.url);
|
|
|
29
31
|
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
30
32
|
const HUB = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
// Two regions since task 1004504: the row's change listener and the Render wiring (from
|
|
35
|
+
// the #appOpts listener to the Look step), and the choice + the row's words (from
|
|
36
|
+
// appPlatformChoice to the review's ROWS list). The row's value comes from rowText(), and
|
|
37
|
+
// whether the row is fixed from appSkipped(); both come along, real.
|
|
38
|
+
function slice(start, end, what) {
|
|
39
|
+
const a = HUB.indexOf(start), b = HUB.indexOf(end, a);
|
|
40
|
+
assert.ok(a > -1, what + ' opens where it should');
|
|
41
|
+
assert.ok(b > a, what + ' ends where it should');
|
|
42
|
+
return HUB.slice(a, b);
|
|
43
|
+
}
|
|
44
|
+
const BLOCK = slice(" var appOptsEl = root.querySelector('#appOpts');", ' /* ── the Look step (task 1004421', 'the app wiring');
|
|
45
|
+
const CHOICE = slice(' function appPlatformChoice() {', ' var ROWS = [', 'the app choice and its row detail');
|
|
46
|
+
const ROWTEXT = slice(' function rowText(key) {', ' /* paint every row from the draft', "the review rows' words");
|
|
47
|
+
const SKIPPED = slice(' function appSkipped() {', ' /* What the create sends as `detail`.', 'appSkipped()');
|
|
39
48
|
// The Render flow itself is shared with an existing project's page (task 1004184), so it
|
|
40
49
|
// lives outside the wizard; its fence and the page's own node helper come along.
|
|
41
50
|
const CSTART = ' /* ── connecting Render (task 1004352';
|
|
@@ -46,7 +55,7 @@ const CONNECT = HUB.slice(cfrom, cto);
|
|
|
46
55
|
const APPEL = (HUB.match(/function appEl\(tag, cls, text\) \{[\s\S]*?\n \}/) || [''])[0];
|
|
47
56
|
assert.ok(APPEL, 'the page\'s own appEl() is lifted');
|
|
48
57
|
|
|
49
|
-
const PANEL = (HUB.match(/<
|
|
58
|
+
const PANEL = (HUB.match(/<div class="setRow" data-row="app">[\s\S]*?<span class="acts">/) || [''])[0];
|
|
50
59
|
const ESC = (HUB.match(/function esc\(s\) \{[\s\S]*?\n \}/) || [''])[0];
|
|
51
60
|
assert.ok(ESC, 'the page\'s own esc() is lifted, not re-implemented');
|
|
52
61
|
|
|
@@ -90,6 +99,7 @@ function load({ draft = {}, answers = {} } = {}) {
|
|
|
90
99
|
const els = {};
|
|
91
100
|
const $ = (id) => (els[id] = els[id] || el(id));
|
|
92
101
|
let changeHandler = null;
|
|
102
|
+
let painted = 0;
|
|
93
103
|
const saves = [];
|
|
94
104
|
const calls = [];
|
|
95
105
|
const root = {
|
|
@@ -115,14 +125,18 @@ function load({ draft = {}, answers = {} } = {}) {
|
|
|
115
125
|
const ctx = { root, state, $, document, Object, API: '/api/bongos', fetch, JSON, String, Number, encodeURIComponent, Array, saveDraft: () => saves.push(JSON.stringify(state)),
|
|
116
126
|
// the shared status watch (task 1004353) lives outside this block; record that it is started
|
|
117
127
|
watchApp: (id, box) => { watches.push({ id, box }); return () => {}; },
|
|
118
|
-
// the
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
// the review repaints its rows on a change (task 1004504); count it
|
|
129
|
+
paintSetup: () => { painted += 1; },
|
|
130
|
+
// the planet-physics answers live outside these regions; rowText reads them through phys()
|
|
131
|
+
phys: () => { state.physics = state.physics || { answers: {} }; return state.physics; } };
|
|
121
132
|
vm.createContext(ctx);
|
|
122
|
-
vm.runInContext(ESC + '\n' + APPEL + '\n' + CONNECT + '\n' +
|
|
133
|
+
vm.runInContext(ESC + '\n' + APPEL + '\n' + CONNECT + '\n' + CHOICE + '\n' + SKIPPED + '\n' + BLOCK + '\n' + ROWTEXT +
|
|
134
|
+
'\nthis.api = { appPlatformChoice, syncAppStep, appRowDetail, rowText, syncRenderSetup, validateRender, renderRequestBody, sendRenderSetup, planLabel };', ctx);
|
|
123
135
|
const api = ctx.api;
|
|
124
136
|
return {
|
|
125
|
-
...api, inputs, state, saves, calls, $, watches,
|
|
137
|
+
...api, inputs, state, saves, calls, $, watches, painted: () => painted,
|
|
138
|
+
// the row as the founder reads it: its value, then the line under it
|
|
139
|
+
appRow: () => { const t = api.rowText('app'); return t.v + ' | ' + t.s; },
|
|
126
140
|
change: (value) => changeHandler({ target: inputs.find((i) => i.value === value) }),
|
|
127
141
|
typeKey: (v) => { $('f-renderKey').value = v; $('f-renderKey').fire('input', { target: $('f-renderKey') }); },
|
|
128
142
|
check: async () => { $('renderCheck').fire('click', { target: $('renderCheck') }); await flush(); },
|
|
@@ -139,32 +153,33 @@ const SERVICES = { services: [
|
|
|
139
153
|
] };
|
|
140
154
|
const ANSWERS = { [LOOKUP]: TWO, [LOOKUP + '#' + TEA_A]: SERVICES };
|
|
141
155
|
|
|
142
|
-
// ── the
|
|
156
|
+
// ── the question itself (task 1004351), as the review's App hosting row (task 1004504) ──
|
|
143
157
|
|
|
144
|
-
test('the
|
|
145
|
-
assert.ok(PANEL, '
|
|
158
|
+
test('the row is real markup: Render and Decide later, Decide later the default', () => {
|
|
159
|
+
assert.ok(PANEL, 'the review carries a data-row="app" row');
|
|
160
|
+
assert.match(PANEL, /<span class="k">App hosting<\/span>/, 'the row is named for the app alone');
|
|
146
161
|
assert.match(PANEL, /<input type="radio" name="appPlatform" value="render" aria-describedby="appRenderWhat">/, 'Render can be picked (task 1004352)');
|
|
147
162
|
assert.doesNotMatch(PANEL, /Coming soon/, 'and is no longer marked soon');
|
|
148
163
|
assert.match(PANEL, /<input type="radio" name="appPlatform" value="later" checked>/, 'Decide later is the default');
|
|
149
|
-
assert.
|
|
150
|
-
assert.match(PANEL, /<
|
|
151
|
-
assert.
|
|
164
|
+
assert.ok(PANEL.indexOf('value="later"') < PANEL.indexOf('value="render"'), 'Decide later comes first');
|
|
165
|
+
assert.match(PANEL, /<button type="button" class="btn small rowUse" data-row-use>Use this<\/button>/, 'Use this confirms the row');
|
|
166
|
+
assert.doesNotMatch(HUB, /id="panel-app"/, 'the old App panel is gone');
|
|
152
167
|
});
|
|
153
168
|
|
|
154
|
-
test(
|
|
155
|
-
assert.match(HUB, /
|
|
156
|
-
assert.match(HUB,
|
|
157
|
-
assert.
|
|
158
|
-
assert.match(HUB,
|
|
159
|
-
assert.match(HUB, /
|
|
160
|
-
assert.match(HUB, /if \(
|
|
161
|
-
assert.match(HUB, /if \(step === 10\) return validateRender\(\);/, 'Next on the step runs the Render check');
|
|
169
|
+
test("the row sits in the review, between Address and Extras, and its checks are the old step's (task 1004504)", () => {
|
|
170
|
+
assert.match(HUB, /var panels = \{ 0: 'panel-gate', 1: 'panel-fork', 2: 'panel-what', 3: 'panel-checks', 4: 'panel-review', 5: 'panel-look', 6: 'panel-done' \};/);
|
|
171
|
+
assert.match(HUB, /var ROWS = \['code', 'address', 'app', 'extras', 'team', 'rewards', 'run'\];/);
|
|
172
|
+
assert.doesNotMatch(HUB, /<li[^>]*data-step="\d+"/, 'no rail beat names the step any more');
|
|
173
|
+
assert.match(HUB, /if \(step === 4\) \{[^}]*syncAppStep\(\); syncRenderSetup\(\);\r?\n\s*paintSetup\(\);\r?\n\s*\}/, 'entering the review repaints the row, Render setup included');
|
|
174
|
+
assert.match(HUB, /if \(key === 'app'\) \{ syncAppStep\(\); syncRenderSetup\(\); \}/, 'opening the row repaints it too');
|
|
175
|
+
assert.match(HUB, /if \(key === 'app'\) return rowFixed\('app'\) \|\| validateRender\(\);/, 'confirming the row runs the Render check');
|
|
162
176
|
});
|
|
163
177
|
|
|
164
178
|
test('a fresh draft reads "decide later", and the Render setup stays hidden', () => {
|
|
165
179
|
const w = load();
|
|
166
180
|
assert.equal(w.appPlatformChoice(), 'later');
|
|
167
|
-
|
|
181
|
+
// the row says the hall is ours to host (ADR 0345 D1), in the page's one spelling of it
|
|
182
|
+
assert.equal(w.appRow(), 'Decide later | We host your builders’ hall. There’s no app to host until your builders write one.');
|
|
168
183
|
w.syncRenderSetup();
|
|
169
184
|
assert.equal(w.$('renderSetup').hidden, true);
|
|
170
185
|
assert.equal(w.validateRender(), true, 'decide later never blocks Next');
|
|
@@ -176,6 +191,7 @@ test('choosing Render saves the choice and opens its setup', () => {
|
|
|
176
191
|
assert.equal(w.state.appPlatform, 'render');
|
|
177
192
|
assert.equal(JSON.parse(w.saves[0]).appPlatform, 'render');
|
|
178
193
|
assert.equal(w.$('renderSetup').hidden, false);
|
|
194
|
+
assert.equal(w.painted(), 1, 'and the review repaints its rows');
|
|
179
195
|
});
|
|
180
196
|
|
|
181
197
|
test('an unknown or missing value reads "decide later"', () => {
|
|
@@ -184,19 +200,25 @@ test('an unknown or missing value reads "decide later"', () => {
|
|
|
184
200
|
}
|
|
185
201
|
});
|
|
186
202
|
|
|
187
|
-
test('the review
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
assert.
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
test('the review names the app alone: no hall row, no Shape row (task 1004504)', () => {
|
|
204
|
+
assert.doesNotMatch(HUB, /function renderReview\(\)|id="reviewList"/, 'the old ledger is gone');
|
|
205
|
+
assert.doesNotMatch(HUB, /data-row="(hall|shape)"/, 'the review has no hall or Shape row');
|
|
206
|
+
assert.doesNotMatch(HUB, /Hosted by us, on the platform’s current release/, 'the hall line left with the ledger');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('a local or open-source answer makes the row fixed: it says so and asks for nothing', () => {
|
|
210
|
+
const w = load({ draft: { physics: { answers: { framing: 'local' } } } });
|
|
211
|
+
assert.equal(w.appRow(), 'Runs on your own machines | As your answer says. Choose a platform later from the project’s page.');
|
|
212
|
+
w.state.physics.answers.framing = 'open-source';
|
|
213
|
+
assert.match(w.appRow(), /^Runs on your own machines/);
|
|
214
|
+
w.state.demo = { people: 2, hours_each: 3 };
|
|
215
|
+
assert.match(w.appRow(), /^Decide later/, 'a demo is all managed, whatever framing a walk left behind');
|
|
193
216
|
});
|
|
194
217
|
|
|
195
|
-
test('the rail grid
|
|
218
|
+
test('the rail grid left with the rail (task 1004504), so adding a step has no column count to keep in step', () => {
|
|
196
219
|
const css = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'assets', 'cosmos.css'), 'utf8');
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
assert.equal(cols, beats, 'the #view-new rail grid carries exactly one column per rail step');
|
|
220
|
+
assert.doesNotMatch(css, /#view-new \.steps/, 'no #view-new rail grid is left in cosmos.css');
|
|
221
|
+
assert.equal([...HUB.matchAll(/<li[^>]*data-step="\d+"/g)].length, 0, 'and no rail step is left on the page');
|
|
200
222
|
});
|
|
201
223
|
|
|
202
224
|
test('the hall is spelled one way on this page', () => {
|
|
@@ -377,7 +399,7 @@ test('Free is on the plan list with its catch spelled out, and Starter stays the
|
|
|
377
399
|
const dbOpts = w.$('f-renderDbPlan').children.map((o) => o.value);
|
|
378
400
|
assert.equal(dbOpts.includes('free'), false, 'the database stays paid-only');
|
|
379
401
|
const r = load({ draft: { appPlatform: 'render', renderOwnerName: 'Mine', renderMode: 'greenfield', renderRuntime: 'node', renderPlan: 'free' } });
|
|
380
|
-
assert.equal(r.appRow(), 'Render
|
|
402
|
+
assert.equal(r.appRow(), 'Render | Workspace Mine — a new app (Node.js, Free, $0, but it sleeps when idle and takes about a minute to wake up), no database. Render bills you directly.');
|
|
381
403
|
});
|
|
382
404
|
|
|
383
405
|
test('what Create sends: greenfield carries the build and the plans, a link only the service', async () => {
|
|
@@ -430,7 +452,7 @@ test('a brand-new repo choosing Render asks for no key, says when it happens, an
|
|
|
430
452
|
assert.equal(w.$('renderLater').hidden, false, 'the note shows');
|
|
431
453
|
assert.equal(w.$('renderSetup').hidden, true, 'no key, workspace or plan questions');
|
|
432
454
|
assert.equal(w.validateRender(), true, 'Next is not blocked on a key nobody needs yet');
|
|
433
|
-
assert.
|
|
455
|
+
assert.equal(w.appRow(), 'Render | Set up from your project’s page once your app has code. Nothing is created on Render now.');
|
|
434
456
|
w.sendRenderSetup(42);
|
|
435
457
|
await flush();
|
|
436
458
|
assert.equal(w.calls.filter((c) => c.url.endsWith('/render')).length, 0, 'nothing is sent to Render');
|
|
@@ -484,13 +506,15 @@ test('a resumed draft asks for the key again, and says why', () => {
|
|
|
484
506
|
assert.equal(w.validateRender(), false);
|
|
485
507
|
});
|
|
486
508
|
|
|
487
|
-
test('the
|
|
509
|
+
test('the row names exactly what was chosen, prices included', () => {
|
|
488
510
|
const g = load({ draft: { appPlatform: 'render', renderOwnerName: 'Mine', renderMode: 'greenfield', renderRuntime: 'node', renderPlan: 'starter', renderDb: true, renderDbPlan: 'basic_256mb' } });
|
|
489
|
-
assert.equal(g.appRow(), 'Render
|
|
511
|
+
assert.equal(g.appRow(), 'Render | Workspace Mine — a new app (Node.js, Starter, about $7/month), with a database (Basic 256 MB, about $6/month). Render bills you directly.');
|
|
490
512
|
const a = load({ draft: { appPlatform: 'render', renderOwnerName: 'Mine', renderMode: 'adopt', renderServiceName: '<shop>' } });
|
|
491
|
-
assert.equal(a.appRow(), 'Render
|
|
513
|
+
assert.equal(a.appRow(), 'Render | Workspace Mine — link <shop>, changing nothing on it.', 'a name from Render stays plain text');
|
|
514
|
+
// ...because the row is painted as TEXT, never parsed as markup
|
|
515
|
+
assert.match(HUB, /row\.querySelector\('\[data-row-v\]'\)\.textContent = t\.v;\r?\n\s*row\.querySelector\('\[data-row-s\]'\)\.textContent = t\.s;/);
|
|
492
516
|
});
|
|
493
517
|
|
|
494
518
|
test('Create hands the app to Render straight after the project is filed', () => {
|
|
495
|
-
assert.match(HUB, /show\(
|
|
519
|
+
assert.match(HUB, /show\(6, true\);\r?\n\s*sendRenderSetup\(data\.instance && data\.instance\.id\);/);
|
|
496
520
|
});
|
|
@@ -46,8 +46,11 @@ for (const [name, region] of [['renderProgress', PROGRESS], ['addressReady', OVE
|
|
|
46
46
|
|
|
47
47
|
function boot() {
|
|
48
48
|
const liveProgress = { innerHTML: '' };
|
|
49
|
+
// the done panel's heading (task 1004504): "Setting up <name>" until the project answers
|
|
50
|
+
const doneH = { textContent: 'Setting up Lantern' };
|
|
49
51
|
const sandbox = {
|
|
50
|
-
$: (id) => (id === 'liveProgress' ? liveProgress : null),
|
|
52
|
+
$: (id) => (id === 'liveProgress' ? liveProgress : id === 'done-h' ? doneH : null),
|
|
53
|
+
state: { name: 'Lantern', slug: 'lantern' },
|
|
51
54
|
esc: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
|
52
55
|
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])),
|
|
53
56
|
projectPageLink: (label) => '<a href="?view=manage&id=1">' + label + '</a>',
|
|
@@ -61,7 +64,7 @@ function boot() {
|
|
|
61
64
|
};
|
|
62
65
|
vm.createContext(sandbox);
|
|
63
66
|
vm.runInContext(PROGRESS + '\n' + OVERLAY + '\n' + ADDRESS + '\n' + ORB, sandbox);
|
|
64
|
-
return { sandbox, progress: () => liveProgress.innerHTML };
|
|
67
|
+
return { sandbox, progress: () => liveProgress.innerHTML, heading: () => doneH.textContent };
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
const NOW = () => new Date().toISOString();
|
|
@@ -89,9 +92,14 @@ test('once the lookup answers, the done panel links the address', () => {
|
|
|
89
92
|
const terminal = h.sandbox.renderProgress({ ...base, dns_resolves: true });
|
|
90
93
|
assert.equal(terminal, false, 'resolving is not yet answering — the panel still waits for the probe');
|
|
91
94
|
assert.match(h.progress(), ADDRESS_LINK);
|
|
95
|
+
assert.equal(h.heading(), 'Setting up Lantern', 'and the heading claims nothing yet (task 1004504)');
|
|
92
96
|
const live = boot();
|
|
93
97
|
assert.equal(live.sandbox.renderProgress({ ...base, dns_resolves: true, last_verified_up: true, last_verified_at: NOW() }), true);
|
|
94
98
|
assert.match(live.progress(), ADDRESS_LINK);
|
|
99
|
+
assert.equal(live.heading(), 'Lantern is alive.', 'the alive moment, only on a verified answer');
|
|
100
|
+
const down = boot();
|
|
101
|
+
down.sandbox.renderProgress({ ...base, dns_resolves: true, last_verified_up: false, last_verified_at: NOW() });
|
|
102
|
+
assert.equal(down.heading(), 'Setting up Lantern', 'a down probe is never "alive"');
|
|
95
103
|
});
|
|
96
104
|
|
|
97
105
|
test('the card and the manage page: no link and no name before the lookup answers, in every pre-live state', () => {
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
// • the previews paint the REAL looks: every colour lookKit carries equals its
|
|
6
6
|
// pack.json, and the night shares equal src/night-tint.js — a preview that drifted
|
|
7
7
|
// from the hall would promise a look the hall does not wear;
|
|
8
|
-
// • the
|
|
9
|
-
//
|
|
8
|
+
// • the screen sits after the review and holds Create (review 4, look 5, done 6,
|
|
9
|
+
// draft v9 since task 1004504; no rail), and every kit walk files only from it;
|
|
10
10
|
// • EXECUTED by the vm-slice idiom: the picker offers the four looks and "make your
|
|
11
11
|
// own", shows the accent only for your own, shows the ADJUSTED colour in the
|
|
12
12
|
// preview and says so, paints a black night when the tint is off, refuses an SVG
|
|
13
13
|
// or oversized logo before reading it, and sends the look then the logo;
|
|
14
|
-
// • Create sends the look right after the project is filed,
|
|
14
|
+
// • Create sends the look right after the project is filed, from the screen it was chosen on;
|
|
15
15
|
// • the manage page reads the look once per project, never on the minute tick.
|
|
16
16
|
//
|
|
17
17
|
// Run: node --test tests/projects_hub_look.mjs
|
|
@@ -105,33 +105,39 @@ test('every colour the previews paint is its pack\'s, and the night shares are t
|
|
|
105
105
|
assert.equal(kit.NIGHT.card, '#101012');
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
-
test('the
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
assert.match(HUB,
|
|
108
|
+
test('the screen sits after the review and holds Create: review 4, look 5, done 6, draft v9, no rail (task 1004504)', () => {
|
|
109
|
+
// task 1004504 blended the wizard into six screens; the look moved AFTER the review and
|
|
110
|
+
// became the screen Create stands on, so the look is the last thing chosen
|
|
111
|
+
assert.match(HUB, /4: 'panel-review', 5: 'panel-look', 6: 'panel-done' \};/);
|
|
112
|
+
assert.match(HUB, /var DRAFT_KEY = 'cb-create-draft-v9';/);
|
|
113
|
+
assert.doesNotMatch(HUB, /<li[^>]*data-step="\d+"/, 'no rail beat is left on the page');
|
|
114
|
+
assert.doesNotMatch(HUB, /id="panel-app"/, 'the App step is the review\'s App hosting row now');
|
|
112
115
|
const panel = (HUB.match(/<section class="panel" id="panel-look"[\s\S]*?<\/section>/) || [''])[0];
|
|
113
|
-
assert.match(panel, /<h1 id="look-h">Give it a look
|
|
114
|
-
assert.
|
|
115
|
-
assert.match(panel, /data-back="10"/);
|
|
116
|
+
assert.match(panel, /<h1 id="look-h">Give it a look\.<\/h1>/);
|
|
117
|
+
assert.doesNotMatch(panel, /<form|data-next=|data-back=/, 'no form and no per-panel Back: the one door is Create, the one Back is the wizard\'s');
|
|
116
118
|
assert.match(panel, /<div class="lookPick" id="wizLook"><\/div>/);
|
|
117
|
-
assert.
|
|
118
|
-
assert.match(
|
|
119
|
-
assert.
|
|
119
|
+
assert.match(panel, /<p class="wizRecap" id="wizRecap"><\/p>/, 'the one-line recap of what Create makes');
|
|
120
|
+
assert.match(panel, /<button type="button" class="btn big" id="doCreate">Create my project<\/button>/, 'Create stands on this screen');
|
|
121
|
+
assert.ok(HUB.indexOf('id="panel-review"') < HUB.indexOf('id="panel-look"') && HUB.indexOf('id="panel-look"') < HUB.indexOf('id="panel-done"'), 'in document order too');
|
|
122
|
+
assert.match(HUB, /if \(step === 5\) \{ renderLook\(\); renderRecap\(\); \}/);
|
|
123
|
+
assert.doesNotMatch(fs.readFileSync(path.join(LANDING, 'assets', 'cosmos.css'), 'utf8'), /#view-new \.steps/, 'and no rail grid in cosmos.css');
|
|
120
124
|
});
|
|
121
125
|
|
|
122
|
-
test('every kit walk
|
|
126
|
+
test('every kit walk that files a project passes the review and the Look screen first, and the kit has a state on it', () => {
|
|
123
127
|
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
|
|
124
128
|
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
125
129
|
const walks = [...Object.entries(states).map(([k, v]) => [k, v.actions]), ...probes.map((p) => [p.name, p.steps])];
|
|
126
130
|
let past = 0;
|
|
127
131
|
for (const [name, steps] of walks) {
|
|
128
|
-
const at = (steps || []).findIndex((a) => a[1] === '#
|
|
129
|
-
if (at < 0
|
|
132
|
+
const at = (steps || []).findIndex((a) => a[1] === '#reviewGo');
|
|
133
|
+
if (at < 0) continue;
|
|
130
134
|
past++;
|
|
131
|
-
|
|
132
|
-
assert.ok(look
|
|
135
|
+
// the retired App and Look submits must not linger in a walk (they would stop it dead)
|
|
136
|
+
assert.ok(!steps.some((a) => a[1] === '#panel-app button[type=submit]' || a[1] === '#panel-look button[type=submit]'), `${name}: walks no retired step`);
|
|
137
|
+
const create = steps.findIndex((a) => a[1] === '#doCreate');
|
|
138
|
+
if (create >= 0) assert.ok(create > at, `${name}: files only after the review's Continue lands it on the Look screen`);
|
|
133
139
|
}
|
|
134
|
-
assert.ok(past >=
|
|
140
|
+
assert.ok(past >= 4, `sanity: the walks that leave the review were found (${past})`);
|
|
135
141
|
assert.deepEqual(states['in-new-look'].expect.visible.slice(0, 2), ['#panel-look', '#wizLook']);
|
|
136
142
|
assert.ok(states['in-new-look-custom'].actions.some((a) => a[1] === '#wizLook-custom'), 'a state pictures "make your own"');
|
|
137
143
|
});
|
|
@@ -233,13 +239,17 @@ test('executed: sendLook sends the look, then the logo — and a removed logo is
|
|
|
233
239
|
assert.deepEqual(calls.map((c) => [c.method, c.url.split('/').pop(), c.body]), [['PATCH', 'look', { look: 'grove', night_tint: 'on' }], ['DELETE', 'logo', null]]);
|
|
234
240
|
});
|
|
235
241
|
|
|
236
|
-
test('Create sends the look right after the project is filed, and the
|
|
242
|
+
test('Create sends the look right after the project is filed, and the look is chosen on the screen Create stands on', () => {
|
|
237
243
|
assert.match(HUB, /sendRenderSetup\(data\.instance && data\.instance\.id\);[^\n]*\n\s*sendLookSetup\(data\.instance && data\.instance\.id\);/);
|
|
238
244
|
assert.match(HUB, /lookKit\.sendLook\(instanceId, state\.look \|\| defaultLook\(\)\)/);
|
|
239
245
|
assert.match(HUB, /function defaultLook\(\) \{ return \{ look: 'chrome-world', nightTint: true, accent: '#b48cf2', logo: null \}; \}/, 'the default is the platform\'s look with the tint ON (owner D2)');
|
|
240
|
-
|
|
246
|
+
// the ledger review and its Look row are gone (task 1004504): the picker and Create share
|
|
247
|
+
// one screen, so the look being filed is the one on screen beside the button
|
|
248
|
+
assert.doesNotMatch(HUB, /function lookRow\(|\['Look', lookRow\(\)\]/, 'no review row restates the look');
|
|
249
|
+
const panel = (HUB.match(/<section class="panel" id="panel-look"[\s\S]*?<\/section>/) || [''])[0];
|
|
250
|
+
assert.ok(panel.indexOf('id="wizLook"') > -1 && panel.indexOf('id="wizLook"') < panel.indexOf('id="doCreate"'), 'the picker sits above Create on the same screen');
|
|
241
251
|
assert.match(HUB, /<p class="hint" id="doneLook" role="status" hidden><\/p>/, 'a refused look is said on the done panel');
|
|
242
|
-
assert.match(
|
|
252
|
+
assert.equal((HUB.match(/look: null, termsAck: false, confirmed: \{\} \};/g) || []).length, 2, 'the draft carries the look (and its reset clears it)');
|
|
243
253
|
});
|
|
244
254
|
|
|
245
255
|
test('the manage page has "Its look", read once per project — never on the minute tick', () => {
|