@bongos/core 1.21.104 → 1.21.106
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 +57 -47
- package/docs/copy-inventory.md +368 -366
- package/docs/copy-registry.json +475 -457
- package/docs/design/project-startup-direction.md +14 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +10 -3
- package/docs/page-readings.json +1023 -977
- package/modules/lifecycle/check-rollup.js +11 -1
- package/modules/lifecycle/github-push.js +3 -7
- package/modules/public-landing/public/projects-hall-live.states.json +3 -2
- package/modules/public-landing/public/projects-setup.states.json +91 -0
- package/modules/public-landing/public/projects.html +535 -804
- package/modules/public-landing/public/projects.probes.json +3 -3
- package/modules/public-landing/public/projects.states.json +1 -1
- package/modules/ui-design/kit/fixtures/provisioning-instances-hall-live.json +22 -2
- package/modules/ui-design/kit/fixtures/provisioning-instances-setup.json +195 -0
- package/modules/ui-design/kit/serve.js +22 -4
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/ship-land.js +2 -2
- package/src/module-api.js +1 -1
- package/tests/cosmos_token_contrast.mjs +10 -18
- package/tests/github_handback_any_device.mjs +4 -3
- package/tests/github_one_trip.mjs +45 -47
- package/tests/github_org_account.mjs +2 -2
- package/tests/github_pick_up.mjs +40 -29
- package/tests/github_push_merge_driver.mjs +46 -0
- package/tests/onboard.mjs +13 -7
- package/tests/project_invite_ui.mjs +6 -3
- package/tests/project_modules_ui.mjs +3 -3
- package/tests/projects_hub.mjs +8 -9
- package/tests/projects_hub_cloud_hop.mjs +77 -125
- package/tests/projects_hub_dns_ready.mjs +42 -51
- package/tests/projects_hub_pre_uat.mjs +68 -69
- package/tests/public_landing_projects.mjs +45 -102
- package/tests/setup_status_facts.mjs +400 -0
- package/tests/wizard_github_feedback.mjs +90 -87
- package/tests/wizard_one_page.mjs +11 -9
- package/tests/wizard_auto_open.mjs +0 -94
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
// tests/projects_hub_cloud_hop.mjs
|
|
2
2
|
//
|
|
3
|
-
// The
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
3
|
+
// The cloud hop (task 1004578, from idea 1001205): a little game with one cloud per
|
|
4
|
+
// PUBLIC project on the platform, played while a project is being set up. Since task
|
|
5
|
+
// 1004683 (the design of record docs/design/mocks/day-night-rooms/setup-status.html) it
|
|
6
|
+
// waits behind a "Play cloud hop while you wait" link and opens in place only when
|
|
7
|
+
// asked: the wait never depends on it, and nothing on a timer stands in for a fact. The
|
|
8
|
+
// owner set the game's rules on a played prototype, and the nausea ones are the ones a
|
|
9
|
+
// refactor would quietly lose: the clouds stand still, the sky stands still, the view
|
|
10
|
+
// does not follow a jump.
|
|
9
11
|
//
|
|
10
|
-
// EXECUTED by the vm-slice idiom:
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// primitives or through JSON.
|
|
12
|
+
// EXECUTED by the vm-slice idiom: the link (hopOpen), the arrival (hopArrive), the
|
|
13
|
+
// physics (hopStep) and the score line (hopScore) are sliced function by function and
|
|
14
|
+
// run under narrow stubs. Every slice is asserted to exist, so a rename fails loudly
|
|
15
|
+
// instead of testing nothing. vm-born values are compared as primitives or through JSON.
|
|
16
|
+
// Part 03's facts themselves are tests/setup_status_facts.mjs.
|
|
16
17
|
//
|
|
17
18
|
// Run: node --test tests/projects_hub_cloud_hop.mjs
|
|
18
|
-
|
|
19
19
|
import { test } from 'node:test';
|
|
20
20
|
import { strict as assert } from 'node:assert';
|
|
21
21
|
import fs from 'node:fs';
|
|
@@ -47,69 +47,71 @@ const HOP_CONSTS = (SCRIPT.match(/\n {4}var HOP_GAP = [^\n]*\n/) || [''])[0];
|
|
|
47
47
|
assert.ok(HOP_CONSTS.includes('HOP_JUMP'), 'the hop constants line must be findable');
|
|
48
48
|
const HOP_REGION = between('/* ══ THE CLOUD HOP (task 1004578)', '/* ── the hub\'s three doors ── */');
|
|
49
49
|
|
|
50
|
-
// ── the moment ──────────────────────────────────────────────────────────────────
|
|
51
|
-
const PROGRESS = between('var PROG_FLOWS = {', 'function startProgress(');
|
|
52
|
-
const OVERLAY = between('function verifiedAt(inst)', 'function chipMarkup(');
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
// ── behind its link (task 1004683) ──────────────────────────────────────────────
|
|
52
|
+
// The wait never depends on the game: it opens in place only when asked, and puts
|
|
53
|
+
// itself away (after the curtain, if it was running) once every required fact is done.
|
|
54
|
+
function bootLink({ reduced = false, running = false, since = 0 } = {}) {
|
|
55
|
+
const box = { hidden: true, classList: { remove() {}, add() {} } };
|
|
56
|
+
const link = { textContent: 'Play cloud hop while you wait', attrs: {}, setAttribute(k, v) { this.attrs[k] = v; } };
|
|
57
|
+
const calls = { start: 0, stop: 0, curtain: 0, raf: 0, done: null };
|
|
57
58
|
const sandbox = {
|
|
58
|
-
$: (id) => (
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
$: (id) => ({ hop: box, hopLink: link })[id] || null, REDUCED: reduced,
|
|
60
|
+
performance: { now: () => 10000 },
|
|
61
|
+
hopStart: () => { calls.start += 1; }, hopStop: () => { calls.stop += 1; },
|
|
62
|
+
hopCurtain: (done) => { calls.curtain += 1; calls.done = done; }, hopFrame() {},
|
|
63
|
+
requestAnimationFrame: () => { calls.raf += 1; return 1; }, cancelAnimationFrame() {},
|
|
61
64
|
};
|
|
62
65
|
vm.createContext(sandbox);
|
|
63
|
-
vm.runInContext(
|
|
64
|
-
return { sandbox,
|
|
66
|
+
vm.runInContext(`var hop = { target: '', raf: ${running ? 1 : 0}, since: ${since}, tr: 0 };\n${fn('hopOpen')}\n${fn('hopArrive')}`, sandbox);
|
|
67
|
+
return { sandbox, box, link, calls };
|
|
65
68
|
}
|
|
66
|
-
const NOW = () => new Date().toISOString();
|
|
67
|
-
const live = { status: 'active', domain: 'mercury.cloudbongos.com', dns_resolves: true, last_verified_up: true, last_verified_at: NOW(), open_intent: null };
|
|
68
|
-
|
|
69
|
-
test('the moment follows the same branches renderProgress speaks', () => {
|
|
70
|
-
const { sandbox } = bootMoment();
|
|
71
|
-
const m = (inst) => sandbox.momentFor(inst);
|
|
72
|
-
assert.equal(m({ status: 'requested' }), 'wait', 'filed: the hop');
|
|
73
|
-
assert.equal(m({ status: 'provisioning' }), 'wait', 'standing up: the hop');
|
|
74
|
-
assert.equal(m({ ...live, dns_resolves: false, last_verified_up: null }), 'wait', 'address still being set up: keep playing');
|
|
75
|
-
assert.equal(m({ ...live, last_verified_up: null }), 'wait', 'standing, not checked yet: keep playing');
|
|
76
|
-
assert.equal(m({ ...live, last_verified_up: false }), 'wait', 'not answering right now: keep playing');
|
|
77
|
-
assert.equal(m(live), 'arrive', 'answering on a resolved address: arrive');
|
|
78
|
-
assert.equal(m({ ...live, open_intent: { action: 'standup', state: 'running' } }), 'arrive',
|
|
79
|
-
'an intent renderProgress does not speak for (a running standup) falls through to the status');
|
|
80
|
-
assert.equal(m({ ...live, open_intent: { action: 'provision' } }), 'wait', 'a queued standup is still a wait');
|
|
81
|
-
assert.equal(m({ status: 'torn_down', open_intent: { action: 'provision' } }), 'wait', 'a queued revive is a wait');
|
|
82
|
-
});
|
|
83
69
|
|
|
84
|
-
test('
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
assert.equal(
|
|
88
|
-
assert.equal(
|
|
89
|
-
assert.equal(
|
|
90
|
-
assert.equal(
|
|
91
|
-
|
|
92
|
-
assert.equal(
|
|
70
|
+
test('the game opens in place only when asked, and its link says so', () => {
|
|
71
|
+
const h = bootLink();
|
|
72
|
+
h.sandbox.hopOpen(true);
|
|
73
|
+
assert.equal(h.box.hidden, false);
|
|
74
|
+
assert.equal(h.link.attrs['aria-expanded'], 'true');
|
|
75
|
+
assert.equal(h.sandbox.hop.target, 'wait', 'only an opened game may run (hopRunning reads it)');
|
|
76
|
+
assert.equal(h.calls.start, 1);
|
|
77
|
+
h.sandbox.hopOpen(false);
|
|
78
|
+
assert.equal(h.box.hidden, true);
|
|
79
|
+
assert.equal(h.link.attrs['aria-expanded'], 'false');
|
|
80
|
+
assert.equal(h.link.textContent, 'Play cloud hop while you wait');
|
|
81
|
+
assert.equal(h.sandbox.hop.target, '');
|
|
82
|
+
assert.equal(h.calls.stop, 1);
|
|
83
|
+
assert.match(HUB, /<button type="button" class="hopLink" id="hopLink" aria-expanded="false" aria-controls="hop">Play cloud hop while you wait<\/button>\s*<div class="hop" id="hop" hidden>/,
|
|
84
|
+
'baked shut, behind the mockup\'s link');
|
|
85
|
+
assert.match(SCRIPT, /\$\('hopLink'\)\.addEventListener\('click', function \(\) \{ hopOpen\(\$\('hop'\)\.hidden\); \}\);/, 'the link is the only way in');
|
|
86
|
+
assert.match(fn('hopStart'), /if \(!hop \|\| !hopEl \|\| hopEl\.hidden\) return;/, 'a closed game never starts, not on a resize nor a tab coming back');
|
|
93
87
|
});
|
|
94
88
|
|
|
95
|
-
test('
|
|
96
|
-
const {
|
|
97
|
-
|
|
98
|
-
sandbox
|
|
99
|
-
|
|
100
|
-
assert.equal(
|
|
101
|
-
|
|
102
|
-
assert.equal(
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
assert.
|
|
89
|
+
test('every required fact done: a running game gets the curtain, then puts itself away; a closed one stays away', () => {
|
|
90
|
+
const run = bootLink({ running: true, since: 1000 });
|
|
91
|
+
run.box.hidden = false;
|
|
92
|
+
run.sandbox.hopArrive();
|
|
93
|
+
assert.equal(run.calls.curtain, 1, 'the curtain plays from a running game');
|
|
94
|
+
assert.equal(run.box.hidden, false, 'and the game stays until the curtain lands');
|
|
95
|
+
run.calls.done();
|
|
96
|
+
assert.equal(run.box.hidden, true, 'then it is put away');
|
|
97
|
+
const shut = bootLink();
|
|
98
|
+
shut.sandbox.hopArrive();
|
|
99
|
+
assert.equal(shut.calls.curtain, 0, 'a game nobody opened plays nothing');
|
|
100
|
+
assert.equal(shut.box.hidden, true);
|
|
101
|
+
const still = bootLink({ reduced: true, running: true, since: 1000 });
|
|
102
|
+
still.box.hidden = false;
|
|
103
|
+
still.sandbox.hopArrive();
|
|
104
|
+
assert.equal(still.calls.curtain, 0, 'reduced motion: no curtain');
|
|
105
|
+
assert.equal(still.box.hidden, true);
|
|
106
|
+
assert.match(fn('paintAlive'), /if \(on\) hopArrive\(\);/, 'the facts decide the arrival, never the game');
|
|
106
107
|
});
|
|
107
108
|
|
|
108
|
-
test('
|
|
109
|
-
|
|
110
|
-
assert.
|
|
111
|
-
assert.
|
|
109
|
+
test('nothing on a timer stands in for a fact: no tick counter, no moment, no bar', () => {
|
|
110
|
+
assert.doesNotMatch(SCRIPT, /ticks > 400|ticks \+= 1/, 'the ~33-minute tick counter is gone');
|
|
111
|
+
assert.doesNotMatch(SCRIPT, /setMoment|momentFor|data-moment|hopMoment|hopSync/, 'no moment switch');
|
|
112
|
+
assert.doesNotMatch(HUB, /hopBar|id="hopStatus"/, 'no bar and no second status line');
|
|
112
113
|
assert.match(fn('resetForNewProject'), /hopReset\(\);/, 'a second project starts with a fresh hop');
|
|
114
|
+
assert.match(fn('renderDone'), /hopOpen\(false\);/, 'a fresh part 03 opens with the game put away');
|
|
113
115
|
});
|
|
114
116
|
|
|
115
117
|
// ── the physics — the owner's rules ─────────────────────────────────────────────
|
|
@@ -199,7 +201,7 @@ test('the loop never runs under reduced motion; the rest frame is a full still s
|
|
|
199
201
|
'the start, the frame (playing and curtain) and the curtain\'s kick-off — no other loop');
|
|
200
202
|
assert.match(fn('hopFrame'), /if \(!hopRunning\(\)\) \{ hopDraw\(now\); return; \}/,
|
|
201
203
|
'every frame re-asks the gate, so turning reduced motion on stops it');
|
|
202
|
-
assert.match(fn('
|
|
204
|
+
assert.match(fn('hopArrive'), /!REDUCED && hop\.raf/, 'the curtain plays only from a running game');
|
|
203
205
|
assert.match(STYLE, /\.hop\.isStill \.hopRow\{display:none;\}/, 'reduced motion has nothing to steer');
|
|
204
206
|
});
|
|
205
207
|
|
|
@@ -213,67 +215,17 @@ test('the game reads its colours off the page tokens — no literal but white or
|
|
|
213
215
|
assert.match(fn('hopColors'), /hopRead\('var\(--' \+ nm \+ '\)'\)/, 'every colour resolves from a token');
|
|
214
216
|
});
|
|
215
217
|
|
|
216
|
-
// ── the
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const attrs = {};
|
|
220
|
-
return {
|
|
221
|
-
classList: { contains: (c) => set.has(c), toggle: (c, on) => (on ? set.add(c) : set.delete(c)), add: (c) => set.add(c), remove: (c) => set.delete(c) },
|
|
222
|
-
setAttribute: (k, v) => { attrs[k] = v; }, getAttribute: (k) => attrs[k], attrs, hidden: false, textContent: '', style: {},
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
test('the status line and the bar read what the strip says', () => {
|
|
227
|
-
const st = { textContent: '' }, fill = { style: {} };
|
|
228
|
-
const sandbox = { $: (id) => ({ hopStatus: st, hopBarFill: fill })[id] || null, Math };
|
|
229
|
-
vm.createContext(sandbox);
|
|
230
|
-
vm.runInContext(fn('hopSync'), sandbox);
|
|
231
|
-
const strip = {
|
|
232
|
-
querySelector: (q) => (q === '.pnote' ? { textContent: 'Raising the server, database, and address — nothing needed from you.' } : q === '.stages li.now' ? {} : null),
|
|
233
|
-
querySelectorAll: (q) => ({ length: q === '.stages li' ? 3 : 1 }),
|
|
234
|
-
};
|
|
235
|
-
sandbox.hopSync(strip);
|
|
236
|
-
assert.equal(st.textContent, 'Raising the server, database, and address — nothing needed from you.');
|
|
237
|
-
assert.equal(fill.style.width, '50%', 'one done and one under way of three');
|
|
238
|
-
assert.match(fn('setProgress'), /hopSync\(el\)/, 'every strip write syncs the line');
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// ── part 03's checklist (task 1004648): every step on show, the state line from it ──
|
|
242
|
-
function bootSteps(items, { step = 2, inFlight = false } = {}) {
|
|
243
|
-
const list = { children: items };
|
|
244
|
-
const ghState = { textContent: '' };
|
|
245
|
-
const sandbox = { $: (id) => ({ humanSteps: list, ghState })[id] || null, Array, state: { step }, manifestInFlight: () => inFlight };
|
|
246
|
-
vm.createContext(sandbox);
|
|
247
|
-
vm.runInContext(fn('stepsPaint'), sandbox);
|
|
248
|
-
sandbox.stepsPaint();
|
|
249
|
-
return ghState.textContent;
|
|
250
|
-
}
|
|
251
|
-
function li(key, cls = []) { const e = el(cls); e.key = key; return e; }
|
|
252
|
-
|
|
253
|
-
test('part 03\'s state line reads the checklist: ready, how many done, waiting on GitHub, connected', () => {
|
|
254
|
-
assert.equal(bootSteps([li('github-repo'), li('oauth-app')], { step: 1 }), 'opens once your project exists', 'before Create');
|
|
255
|
-
assert.equal(bootSteps([li('github-repo'), li('oauth-app')]), 'ready');
|
|
256
|
-
assert.equal(bootSteps([li('github-repo', ['sdone']), li('oauth-app'), li('enter-hall', ['slocked'])]), '1 of 3 done');
|
|
257
|
-
assert.equal(bootSteps([li('github-repo', ['sdone']), li('oauth-app')], { inFlight: true }), 'waiting on GitHub…', 'a GitHub leg in flight says so');
|
|
258
|
-
assert.equal(bootSteps([li('github-repo', ['sdone']), li('oauth-app', ['sdone'])]), 'connected ✓');
|
|
259
|
-
assert.doesNotMatch(fn('stepsPaint'), /innerHTML|\.cur|data-n/, 'no paging, and no markup from strings');
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
// ── the markup and the sheet ────────────────────────────────────────────────────
|
|
263
|
-
test('the wait stands the strip and the later lines down, the checklist stays on show, and nothing is paged', () => {
|
|
264
|
-
for (const id of ['hop', 'hopStatus', 'hopBarFill', 'hopCanvas', 'hopLeft', 'hopJump', 'hopRight', 'hopScore', 'humanSteps', 'ghState']) {
|
|
218
|
+
// ── the markup ──────────────────────────────────────────────────────────────────
|
|
219
|
+
test('the hop is baked into part 03 below the facts and above "is alive"; the old wait is gone', () => {
|
|
220
|
+
for (const id of ['hop', 'hopLink', 'hopCanvas', 'hopLeft', 'hopJump', 'hopRight', 'hopScore']) {
|
|
265
221
|
assert.ok(HUB.includes(`id="${id}"`), `#${id} is baked into part 03`);
|
|
266
222
|
}
|
|
267
|
-
for (const id of ['hopWhile', 'hopSignin', 'stepPager', 'stepCount', 'stepBack', 'stepNext']) {
|
|
268
|
-
assert.ok(!HUB.includes(`id="${id}"`), `#${id} is gone
|
|
223
|
+
for (const id of ['hopWhile', 'hopSignin', 'stepPager', 'stepCount', 'stepBack', 'stepNext', 'hopStatus', 'hopBarFill', 'humanSteps', 'liveProgress']) {
|
|
224
|
+
assert.ok(!HUB.includes(`id="${id}"`), `#${id} is gone (tasks 1004648, 1004683)`);
|
|
269
225
|
}
|
|
270
226
|
const panel = HUB.slice(HUB.indexOf('id="panel-done"'), HUB.indexOf('</section>', HUB.indexOf('id="panel-done"')));
|
|
271
|
-
assert.ok(panel.indexOf('id="
|
|
272
|
-
'the
|
|
273
|
-
assert.
|
|
274
|
-
assert.match(STYLE,
|
|
275
|
-
assert.doesNotMatch(STYLE, /#panel-done\[data-moment="wait"\][^{]*#humanSteps\{display:none;\}/, 'the wait no longer hides the checklist');
|
|
276
|
-
assert.doesNotMatch(STYLE, /humanSteps>li:not\(\.cur\)/, 'and the arrival does not page it');
|
|
277
|
-
assert.match(STYLE, /#panel-done\[data-moment="arrive"\] #liveProgress \.stages\{display:none;\}/, 'the arrival keeps the note with the address, not the dots');
|
|
278
|
-
assert.match(STYLE, /#panel-done \.humanSteps>li\.sdone::before\{content:"✓";/, 'a verified step wears the tick');
|
|
227
|
+
assert.ok(panel.indexOf('id="ghFacts"') < panel.indexOf('id="hopLink"') && panel.indexOf('id="hop"') < panel.indexOf('id="doneAlive"'),
|
|
228
|
+
'the facts first, then the game behind its link, then "is alive"');
|
|
229
|
+
assert.doesNotMatch(panel, /id="hopScore"[^>]*aria-live/, 'the score is not a live region: part 03 speaks through one');
|
|
230
|
+
assert.match(STYLE, /\.hop\[hidden\]\{display:none;\}/, 'the game stays out of the layout until asked');
|
|
279
231
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// carries `dns_resolves` (the server's sighting — modules/provisioning/dns-resolves.js)
|
|
8
8
|
// or a verified up probe. The status alone, 'active' included, is never enough.
|
|
9
9
|
//
|
|
10
|
-
// EXECUTED by the vm-slice idiom:
|
|
10
|
+
// EXECUTED by the vm-slice idiom: part 03's Its home facts (homeFacts, task 1004683), the card's
|
|
11
11
|
// addressRow and the row's orbFor are sliced verbatim out of projects.html and run
|
|
12
12
|
// under a narrow stub, and the markup they write is read back. Every slice boundary is
|
|
13
13
|
// asserted, so a rename fails loudly instead of testing nothing. vm-born values are
|
|
@@ -36,27 +36,23 @@ function slice(from, to) {
|
|
|
36
36
|
return HUB.slice(a, b);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
// part 03's Its home (task 1004683): the three facts read off the owner's instance read
|
|
40
|
+
function wizFn(name) {
|
|
41
|
+
const m = HUB.match(new RegExp(`\\n {4}function ${name}\\([^)]*\\) \\{[\\s\\S]*?\\n {4}\\}`));
|
|
42
|
+
assert.ok(m, `function ${name}() must be findable in projects.html`);
|
|
43
|
+
return m[0];
|
|
44
|
+
}
|
|
45
|
+
const FACTS = ['hostOk', 'whenSaid', 'homeFacts'].map(wizFn).join('\n');
|
|
40
46
|
const OVERLAY = slice('function verifiedAt(inst)', 'function chipMarkup(');
|
|
41
47
|
const ADDRESS = slice('var ADDRESS_SETTING_UP', 'function publishRow(');
|
|
42
48
|
const ORB = slice('function orbFor(', '/* ── progress strips');
|
|
43
|
-
for (const [name, region] of [['
|
|
49
|
+
for (const [name, region] of [['homeFacts', FACTS], ['addressReady', OVERLAY], ['addressRow', ADDRESS], ['orbFor', ORB]]) {
|
|
44
50
|
assert.ok(region.includes(`function ${name}(`), `the slice must carry ${name}`);
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
function boot() {
|
|
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' };
|
|
51
|
-
// and the line under it (task 1004543): renderDone's "setting up" until the project answers
|
|
52
|
-
const SETTING_UP = 'We’re setting up <strong>Lantern</strong>: its server, database and address.';
|
|
53
|
-
const doneLede = { innerHTML: SETTING_UP };
|
|
54
|
-
// the invite step's gate (task 1004503): renderProgress opens it only on a verified answer
|
|
55
|
-
const gates = [];
|
|
56
54
|
const sandbox = {
|
|
57
|
-
|
|
58
|
-
state: { name: 'Lantern', slug: 'lantern' },
|
|
59
|
-
paintInviteGate: (live) => gates.push(live),
|
|
55
|
+
live: { readAt: Date.now() },
|
|
60
56
|
esc: (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => (
|
|
61
57
|
{ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])),
|
|
62
58
|
projectPageLink: (label) => '<a href="?view=manage&id=1">' + label + '</a>',
|
|
@@ -66,54 +62,47 @@ function boot() {
|
|
|
66
62
|
planetAttrs: () => '',
|
|
67
63
|
FRESH_MS: 12 * 60 * 1000,
|
|
68
64
|
LIVE_MOMENT_MS: 10 * 60 * 1000,
|
|
69
|
-
lastProgHtml: '',
|
|
70
65
|
};
|
|
71
66
|
vm.createContext(sandbox);
|
|
72
|
-
vm.runInContext(
|
|
73
|
-
|
|
67
|
+
vm.runInContext(OVERLAY + '\n' + ADDRESS + '\n' + ORB + '\n' + FACTS, sandbox);
|
|
68
|
+
// Its home as the founder sees it: each fact's state and every word it says
|
|
69
|
+
const home = (inst) => JSON.parse(JSON.stringify(sandbox.homeFacts(inst, Date.now())));
|
|
70
|
+
const words = (rows) => rows.map((r) => [r.title, [].concat(r.sub || []).map((p) => (typeof p === 'string' ? p : p.t)).join(''), r.right || ''].join(' ')).join('\n');
|
|
71
|
+
return { sandbox, home, words };
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
const NOW = () => new Date().toISOString();
|
|
77
|
-
const base = { id: 1, slug: 'lantern', status: 'active', domain: 'lantern.cloudbongos.com', last_verified_at: null, last_verified_up: null, dns_note: null };
|
|
78
75
|
const ADDRESS_LINK = /<a [^>]*href="https:\/\/lantern\.cloudbongos\.com/;
|
|
76
|
+
const base = { id: 1, slug: 'lantern', status: 'active', domain: 'lantern.cloudbongos.com', created_at: NOW(), provisioned_at: NOW(), last_verified_at: null, last_verified_up: null, dns_note: null, open_intent: null };
|
|
79
77
|
|
|
80
|
-
test('active but not yet resolved: the
|
|
78
|
+
test('active but not yet resolved: part 03 says the address is on its way, never names it, and keeps it a fact under way', () => {
|
|
81
79
|
const h = boot();
|
|
82
|
-
const
|
|
83
|
-
assert.
|
|
84
|
-
assert.
|
|
85
|
-
assert.doesNotMatch(h.
|
|
86
|
-
assert.match(h.progress(), /your address is being set up/);
|
|
80
|
+
const rows = h.home({ ...base, dns_resolves: false });
|
|
81
|
+
assert.deepEqual(rows.map((r) => r.state), ['done', 'done', 'now'], 'kept, running, not answering yet');
|
|
82
|
+
assert.equal(rows[2].sub, 'New addresses can take a few minutes to reach everyone.');
|
|
83
|
+
assert.doesNotMatch(h.words(rows), /lantern\.cloudbongos\.com/, 'a name typed before it resolves is cached as not found: not even named');
|
|
87
84
|
});
|
|
88
85
|
|
|
89
|
-
test('a down probe on a never-resolved address is still
|
|
86
|
+
test('a down probe on a never-resolved address is still on its way, never named', () => {
|
|
90
87
|
const h = boot();
|
|
91
|
-
h.
|
|
92
|
-
assert.
|
|
93
|
-
assert.
|
|
88
|
+
const rows = h.home({ ...base, dns_resolves: false, last_verified_up: false, last_verified_at: NOW() });
|
|
89
|
+
assert.equal(rows[2].state, 'now');
|
|
90
|
+
assert.doesNotMatch(h.words(rows), /lantern\.cloudbongos\.com/);
|
|
94
91
|
});
|
|
95
92
|
|
|
96
|
-
test('once the lookup answers, the
|
|
93
|
+
test('once the lookup answers, part 03 names the address; only a verified answer ticks "Address answers"', () => {
|
|
97
94
|
const h = boot();
|
|
98
|
-
const
|
|
99
|
-
assert.equal(
|
|
100
|
-
assert.
|
|
101
|
-
assert.equal(
|
|
102
|
-
|
|
103
|
-
assert.equal(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
assert.
|
|
107
|
-
|
|
108
|
-
assert.
|
|
109
|
-
assert.match(live.lede(), /^Its home is ready\. Watch the rest here, or any time on <a href="\?view=mine">your projects page<\/a>\.$/,
|
|
110
|
-
'and the line under it stops saying "setting up" (task 1004543)');
|
|
111
|
-
assert.doesNotMatch(live.lede(), /setting up/i);
|
|
112
|
-
const down = boot();
|
|
113
|
-
down.sandbox.renderProgress({ ...base, dns_resolves: true, last_verified_up: false, last_verified_at: NOW() });
|
|
114
|
-
assert.equal(down.heading(), 'Setting up Lantern', 'a down probe is never "alive"');
|
|
115
|
-
assert.deepEqual(down.gates, [], 'nor opens the invite box');
|
|
116
|
-
assert.equal(down.lede(), down.SETTING_UP, 'nor says its home is ready');
|
|
95
|
+
const resolved = h.home({ ...base, dns_resolves: true });
|
|
96
|
+
assert.equal(resolved[0].sub, 'lantern.cloudbongos.com', 'named once it resolves');
|
|
97
|
+
assert.equal(resolved[2].state, 'now', 'resolving is not yet answering');
|
|
98
|
+
assert.equal(resolved[2].sub, 'Checking that it answers.');
|
|
99
|
+
const up = h.home({ ...base, dns_resolves: true, last_verified_up: true, last_verified_at: NOW() });
|
|
100
|
+
assert.equal(up[2].state, 'done');
|
|
101
|
+
assert.match(up[2].right, /^checked \d+s ago$/, 'when the platform last saw it answer');
|
|
102
|
+
const down = h.home({ ...base, dns_resolves: true, last_verified_up: false, last_verified_at: NOW() });
|
|
103
|
+
assert.equal(down[2].state, 'now', 'a down probe is never "answers"');
|
|
104
|
+
// "is alive" and the Enter Genesis door ask the same two gates
|
|
105
|
+
assert.match(wizFn('paintAlive'), /var on = !!\(ready && inst && hostOk\(inst\.domain\) && addressReady\(inst\) && inst\.last_verified_up === true\);/);
|
|
117
106
|
});
|
|
118
107
|
|
|
119
108
|
test('the card and the manage page: no link and no name before the lookup answers, in every pre-live state', () => {
|
|
@@ -165,9 +154,11 @@ test('every door to the address on the page asks addressReady', () => {
|
|
|
165
154
|
const SCRIPT = HUB;
|
|
166
155
|
assert.match(SCRIPT, /inst\.status === 'active' && addressReady\(inst\)\) acts\.push\('<a class="btn small ghost" href="https:\/\/' \+ esc\(inst\.domain\) \+ '" target="_blank" rel="noopener">View<\/a>'\)/, 'the row\'s View');
|
|
167
156
|
assert.match(SCRIPT, /inst\.status === 'active' && addressReady\(inst\)\) acts\.push\('<a class="btn small ghost" href="https:\/\/' \+ esc\(inst\.domain\) \+ '" target="_blank" rel="noopener">Open site →<\/a>'\)/, 'the manage page\'s Open site');
|
|
168
|
-
// task
|
|
169
|
-
|
|
170
|
-
assert.match(SCRIPT, /live\.addrReady
|
|
157
|
+
// part 03 (task 1004683): "Builders can sign in" names the address only once it resolves,
|
|
158
|
+
// and the Enter Genesis door opens only on a resolved, answering address
|
|
159
|
+
assert.match(SCRIPT, /signIn\.sub = dom && live\.addrReady \? 'Tested on ' \+ dom : '';/, 'the sign-in fact waits for the address');
|
|
160
|
+
assert.match(SCRIPT, /if \(inst\) \{ live\.instStatus = inst\.status; live\.addrReady = addressReady\(inst\); \}\n\s*paintFacts\(\);/, 'and the read redraws the facts on the tick the lookup answers');
|
|
161
|
+
assert.match(SCRIPT, /if \(on\) \$\('enterGenesis'\)\.setAttribute\('href', 'https:\/\/' \+ inst\.domain\);/, 'the Genesis door, behind paintAlive\'s gate');
|
|
171
162
|
assert.equal((SCRIPT.match(/inst\.status !== 'active' \|\| !addressReady\(inst\)\) \{ (vis|join)Runs\(''\); return; \}/g) || []).length, 2,
|
|
172
163
|
'the browser\'s own manifest reads wait for the name too');
|
|
173
164
|
// the only raw address hrefs left are behind one of those gates
|