@bongos/core 1.21.105 → 1.21.107
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 +88 -58
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +6 -1
- package/clients/bongos-client/index.mjs +4 -0
- package/docs/api/openapi.json +91 -2
- package/docs/api-reference.md +5 -3
- 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/migrations/core_275_claims_auto_pickup.sql +24 -0
- package/modules/government/decision-rules.js +1 -0
- package/modules/lifecycle/autopickup.js +94 -0
- package/modules/lifecycle/db-claims.js +18 -4
- package/modules/lifecycle/routes/claims.js +12 -1
- package/modules/lifecycle/routes/project-settings.js +59 -0
- 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/government-walk.js +1 -0
- package/scripts/gds/run-unit-tests.js +3 -0
- package/src/bongos/project-settings.js +79 -0
- package/src/module-api.js +8 -1
- package/tests/autopickup_cap.mjs +236 -0
- package/tests/autopickup_cap_db.mjs +115 -0
- 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/government_act_alone_buildings.mjs +1 -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
|
@@ -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
|
|
@@ -169,52 +169,51 @@ test('"live" is ONE rule — chip, orb and address row all read freshUp()', () =
|
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
/* ── the queued strip is honest about which stage is current ── */
|
|
172
|
-
test('a queued standup lights "Request filed", not "Standing it up"
|
|
172
|
+
test('a queued standup lights "Request filed", not "Standing it up" on the card; part 03 says one fact is under way', () => {
|
|
173
173
|
const strip = fn('stripFor');
|
|
174
174
|
assert.match(strip, /if \(s === 'requested'\) \{[\s\S]*?stripMarkup\(revive \? 'revive' : 'standup', 0, 0,/, 'card: requested → stage 0 current');
|
|
175
175
|
assert.match(strip, /if \(s === 'provisioning'\) \{[\s\S]*?stripMarkup\(revive \? 'revive' : 'standup', 1, 1,/, 'card: provisioning → stage 1 current');
|
|
176
|
-
|
|
176
|
+
// part 03 (task 1004683) has no stages: a filed, running or queued standup is one fact, starting
|
|
177
|
+
assert.match(fn('homeFacts'), /else if \(s === 'requested' \|\| s === 'provisioning' \|\| act === 'provision'\) server = \{ state: 'now', title: 'Server and database starting'/, 'part 03: one fact under way');
|
|
177
178
|
assert.doesNotMatch(SCRIPT, /Waiting for a runner/, '"runner" is operator vocabulary');
|
|
178
179
|
assert.doesNotMatch(SCRIPT, /a runner picks it up/, '"runner" is operator vocabulary (retry message)');
|
|
179
180
|
});
|
|
180
181
|
|
|
181
182
|
/* ── the done panel never lies at the end of its watch, and never opens a door early ── */
|
|
182
|
-
test('
|
|
183
|
-
assert.
|
|
184
|
-
assert.
|
|
185
|
-
assert.match(
|
|
183
|
+
test('part 03\'s watch never gives up on a timer, and a read that is gone is said once (task 1004683)', () => {
|
|
184
|
+
assert.doesNotMatch(SCRIPT, /ticks > 400|this tab will stop watching now/, 'no 33-minute hand-off: no timer stands in for a fact');
|
|
185
|
+
assert.match(SCRIPT, /if \(m\.indexOf\('gone:'\) === 0\) \{[\s\S]{0,120}live\.gone = m\.slice\(5\);\n\s*paintFacts\(\);\n\s*stopPolling\(\);/, 'signed out or gone: the sentence says it, and the watch stops');
|
|
186
|
+
assert.match(fn('setupSentence'), /if \(live\.gone === 'missing'\) return \[\{ t: 'This project can’t be seen any more\. It may have been switched off\.' \}\];/);
|
|
186
187
|
});
|
|
187
188
|
|
|
188
|
-
test('"Builders can sign in" ticks on verified sign-in, and
|
|
189
|
-
// the
|
|
190
|
-
//
|
|
191
|
-
const
|
|
192
|
-
assert.match(
|
|
193
|
-
assert.match(
|
|
194
|
-
assert.match(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const hall = fn('renderStepEnterHall');
|
|
198
|
-
assert.match(hall, /if \(domain\) return '';/);
|
|
199
|
-
assert.match(hall, /bongos dev/);
|
|
189
|
+
test('"Builders can sign in" ticks on verified sign-in, and the door into the project opens only when it answers (tasks 1004649, 1004683)', () => {
|
|
190
|
+
// the fact's tick is the server's 'done'; the address is named, and the Enter Genesis door
|
|
191
|
+
// opened, only on a resolving address (task 1004505)
|
|
192
|
+
const gh = fn('ghFacts');
|
|
193
|
+
assert.match(gh, /var signIn = \{ key: 'sign_in', title: 'Builders can sign in' \};/);
|
|
194
|
+
assert.match(gh, /if \(live\.manifest === 'done' \|\| ghStep\('sign_in'\) === 'done'\) \{ signIn\.state = 'done'; signIn\.sub = dom && live\.addrReady \? 'Tested on ' \+ dom : ''; \}/, 'done only once the platform has seen sign-in work');
|
|
195
|
+
assert.match(fn('paintAlive'), /addressReady\(inst\) && inst\.last_verified_up === true/, 'the door gates on a resolving, answering address');
|
|
196
|
+
// the hall is entered through the Enter Genesis door; no local-hall one-liner is left on part 03
|
|
197
|
+
assert.doesNotMatch(SCRIPT, /bongos dev|Enter your builders hall/);
|
|
200
198
|
});
|
|
201
199
|
|
|
202
|
-
test('
|
|
203
|
-
assert.match(
|
|
204
|
-
assert.
|
|
200
|
+
test('a domainless project is standing, never "alive": GitHub waits for an address (task 1004683)', () => {
|
|
201
|
+
assert.match(fn('setupSentence'), /if \(!hostOk\(inst\.domain\)\) return \[\{ t: 'It has no address yet, so GitHub has to wait\. Attach one any time from ' \}, \{ t: 'its own page', href: manageHref\(\) \}/);
|
|
202
|
+
assert.match(fn('homeFacts'), /if \(dom && s === 'active' && act !== 'teardown'\) \{/, '"Address answers" is never reached without an address');
|
|
205
203
|
});
|
|
206
204
|
|
|
207
|
-
test('
|
|
208
|
-
assert.match(SCRIPT, /
|
|
205
|
+
test('part 03\'s facts name every GitHub step the canonical plan has (task 1004683)', () => {
|
|
206
|
+
assert.match(SCRIPT, /var FACT_PLAN_STEP = \{ approve: 'oauth-app', install: 'app-install', sign_in: 'first-sign-in', device_flow: 'device-flow' \};/);
|
|
207
|
+
assert.match(HUB, /data-onboard-step="github-repo"/);
|
|
208
|
+
assert.match(HUB, /data-onboard-step="enter-hall"/);
|
|
209
209
|
});
|
|
210
210
|
|
|
211
211
|
/* ── the manifest leg: acknowledges GitHub, offers a way out, survives a reload ── */
|
|
212
212
|
test('the manifest step acknowledges code_received, offers a restart after a stall, and rides the CREATED record', () => {
|
|
213
213
|
// 'converting' is the same stage — it used to fall through to "waiting for approval" (task 1004572)
|
|
214
|
-
assert.match(SCRIPT, /if \(live\.manifest === 'code_received' \|\| live\.manifest === 'converting'\)/, 'GitHub’s half is acknowledged');
|
|
215
|
-
assert.match(SCRIPT, /Approved on GitHub\. The platform is placing the app’s keys on your project/);
|
|
214
|
+
assert.match(SCRIPT, /else if \(live\.manifest === 'code_received' \|\| live\.manifest === 'converting'\) \{ approve\.state = 'now'; approve\.sub = 'Approved\. The platform is collecting the app’s keys\.'; \}/, 'GitHub’s half is acknowledged');
|
|
216
215
|
// the way back is the SAME attempt, on show from the start — never a new link after 90s (task 1004652)
|
|
217
|
-
assert.match(SCRIPT, /
|
|
216
|
+
assert.match(SCRIPT, /approve\.state = 'you'; approve\.act = 'reopen';/, 'a waiting fact can reopen GitHub');
|
|
218
217
|
assert.doesNotMatch(SCRIPT, /manifestStale|> 90000/, 'no 90-second restart door left');
|
|
219
218
|
assert.match(SCRIPT, /function rememberManifest\(stateToken, status, appSlug\)/);
|
|
220
219
|
assert.match(SCRIPT, /rememberManifest\(d\.state, 'pending'\)/, 'the token is remembered when GitHub is opened');
|
|
@@ -231,7 +230,7 @@ test('the wizard’s 401 recoveries keep the repo scope they already granted', (
|
|
|
231
230
|
const plain = (SCRIPT.match(/\/auth\/web\/start\?return=\/projects%3Fview%3Dnew'\)/g) || []).length;
|
|
232
231
|
assert.equal(plain, 0, 'no wizard redirect drops the scope');
|
|
233
232
|
assert.ok((SCRIPT.match(/\/auth\/web\/start\?return=\/projects%3Fview%3Dnew&repos=1'\); throw new Error\('signing in'\)/g) || []).length >= 2, 'create + manifest');
|
|
234
|
-
assert.match(SCRIPT,
|
|
233
|
+
assert.match(SCRIPT, /\{ t: 'Sign in again', href: API \+ '\/auth\/web\/start\?return=\/projects%3Fview%3Dnew&repos=1' \}, \{ t: ' to keep watching\.' \}/, 'the progress watch');
|
|
235
234
|
// every hub sign-in door asks for repo access since task 1004549, so signInAgain does too
|
|
236
235
|
assert.match(fn('signInAgain'), /\(currentView === 'new' \? '\/projects%3Fview%3Dnew' : '\/projects'\) \+ '&repos=1'/, 'signInAgain from the wizard');
|
|
237
236
|
});
|
|
@@ -544,21 +543,23 @@ test('an unmapped create failure gets a plain sentence with the raw text folded
|
|
|
544
543
|
});
|
|
545
544
|
|
|
546
545
|
test('a manifest error says a plain sentence, folds the route text, and sends a domainless project to its address', () => {
|
|
547
|
-
const steps = fn('
|
|
548
|
-
assert.doesNotMatch(steps, /'GitHub setup hit a snag'
|
|
549
|
-
assert.match(steps, /else if \(live\.manifestCode === 'domain_required'\)
|
|
546
|
+
const steps = fn('ghFacts');
|
|
547
|
+
assert.doesNotMatch(steps, /'GitHub setup hit a snag'/, 'the raw route text is no longer the headline');
|
|
548
|
+
assert.match(steps, /else if \(live\.manifestCode === 'domain_required'\) \{ approve\.sub = 'It needs an address first\. Attach one on its own page\.'; approve\.act = 'manage';/,
|
|
550
549
|
'the address lives on the project’s own page since task 1003355 — the list would be a dead end');
|
|
551
|
-
assert.match(fn('
|
|
550
|
+
assert.match(fn('manageHref'), /return instanceId \? '\?view=manage&id=' \+ encodeURIComponent\(instanceId\) : '\?view=mine';/,
|
|
552
551
|
'and it is named by id whenever the flow has one');
|
|
553
|
-
// every
|
|
554
|
-
//
|
|
555
|
-
assert.equal((SCRIPT.match(/projectPageLink\(/g) || []).length,
|
|
556
|
-
|
|
552
|
+
// every part 03 pointer that names a CONTROL goes the same way (task 1004683: drawn as
|
|
553
|
+
// nodes through manageHref); the invite pointer still rides projectPageLink's markup
|
|
554
|
+
assert.equal((SCRIPT.match(/projectPageLink\(/g) || []).length, 2, 'the helper and the invite pointer (task 1002346)');
|
|
555
|
+
assert.equal((SCRIPT.match(/manageHref\(\)/g) || []).length, 6,
|
|
556
|
+
'its definition, projectPageLink, the switched-off sentence, the no-address sentence, the extras pointer, and a fact\'s manage door (a stopped standup\'s retry, the domain_required snag)');
|
|
557
557
|
for (const dead of ['Retry it \\(and see the technical details\\) from <a href="\\?view=mine">',
|
|
558
558
|
'Bring it back any time from <a href="\\?view=mine">']) {
|
|
559
559
|
assert.doesNotMatch(SCRIPT, new RegExp(dead), 'a control the list no longer has must not be pointed at there');
|
|
560
560
|
}
|
|
561
|
-
assert.match(steps,
|
|
561
|
+
assert.match(steps, /approve\.sub = 'GitHub setup didn’t start\. Nothing was changed on GitHub\.'; approve\.tech = live\.manifestMsg \|\| '';/, 'the route text folds under Technical details');
|
|
562
|
+
assert.match(fn('factNode'), /det\.appendChild\(textNode\('summary', null, 'Technical details'\)\);\n\s*det\.appendChild\(textNode\('p', null, r\.tech\)\);/, 'as text, never markup');
|
|
562
563
|
assert.match(SCRIPT, /e\.code = \(d\.error && d\.error\.code\) \|\| '';/, 'the route code travels with the error');
|
|
563
564
|
assert.match(SCRIPT, /live\.manifestCode = String\(\(err && err\.code\) \|\| ''\);/);
|
|
564
565
|
assert.equal((SCRIPT.match(/manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null/g) || []).length, 2, 'both resets carry the code (and, since task 1004572, the failure stage)');
|
|
@@ -613,20 +614,15 @@ test('teardown copy is state-aware: a queued project is told nothing was built',
|
|
|
613
614
|
assert.doesNotMatch(truth, /status === 'error'|status === 'provisioning'/, 'error and provisioning keep today’s text — a failed run may already have built things');
|
|
614
615
|
});
|
|
615
616
|
|
|
616
|
-
test('founder voice on
|
|
617
|
-
const
|
|
618
|
-
// the mockup's words (
|
|
619
|
-
assert.match(
|
|
620
|
-
assert.
|
|
621
|
-
assert.match(
|
|
622
|
-
//
|
|
623
|
-
|
|
624
|
-
assert.
|
|
625
|
-
assert.equal((df.match(/class="[^"]*soptional/g) || []).length, 3, 'every branch is marked optional, so "connected" never waits on it');
|
|
626
|
-
assert.equal((df.match(/<strong>Enable Device Flow<\/strong>/g) || []).length, 2, 'GitHub’s own checkbox label stays verbatim in the instructions');
|
|
627
|
-
const manual = fn('manualOauthDetails');
|
|
628
|
-
assert.match(manual, /The platform team places the generated credentials; this step ticks itself once they land\.<\/p><\/details>/);
|
|
629
|
-
assert.doesNotMatch(manual, /auth_configured|healthz/, 'no JSON health check for a founder to perform');
|
|
617
|
+
test('founder voice on command-line sign-in, and no manual OAuth chore on the page (task 1004683)', () => {
|
|
618
|
+
const gh = fn('ghFacts');
|
|
619
|
+
// the mockup's words (setup-status.html): one title, marked optional
|
|
620
|
+
assert.match(gh, /var device = \{ key: 'device_flow', title: 'Command-line sign-in', opt: true \};/, 'the outcome-first title, optional by design');
|
|
621
|
+
assert.match(gh, /device\.sub = settings \? \['Turn it on later in ', \{ t: 'the app settings', href: settings \}, '\.'\] : 'Turn it on later in the app settings\.';/, 'one plain line, linking the app\'s own settings once GitHub has made it');
|
|
622
|
+
assert.match(fn('factRequired'), /return !r\.opt;/, 'the optional fact never holds "ready" back');
|
|
623
|
+
// the manual OAuth-app details live behind the platform docs now, not on this page
|
|
624
|
+
assert.doesNotMatch(SCRIPT, /function manualOauthDetails\(|Prefer to create an OAuth app by hand/);
|
|
625
|
+
assert.doesNotMatch(gh, /auth_configured|healthz/, 'no JSON health check for a founder to perform');
|
|
630
626
|
});
|
|
631
627
|
|
|
632
628
|
test('repo visibility: the public line stays one line, then the door or the reason — the mechanism lives in the fold', () => {
|
|
@@ -657,15 +653,13 @@ test('a failed manage read offers a retry; the no-address sentence offers a door
|
|
|
657
653
|
});
|
|
658
654
|
|
|
659
655
|
/* ── accessibility follow-ups (task 1003246) ── */
|
|
660
|
-
test('
|
|
661
|
-
assert.match(HUB, /<
|
|
662
|
-
assert.
|
|
663
|
-
assert.match(fn('
|
|
664
|
-
assert.match(fn('
|
|
665
|
-
assert.
|
|
666
|
-
assert.
|
|
667
|
-
assert.doesNotMatch(fn('renderProgress'), /el\.innerHTML =/, 'renderProgress included');
|
|
668
|
-
assert.match(SCRIPT, /lastStepsHtml = '';\n\s*lastProgHtml = '';/, 'starting another project resets both guards');
|
|
656
|
+
test('part 03 speaks through one stable live region, change-only, and each fact says its state (task 1004683)', () => {
|
|
657
|
+
assert.match(HUB, /<p class="setNow" id="setupNow" role="status" aria-live="polite"><\/p>/, 'the live region is the static sentence');
|
|
658
|
+
assert.match(fn('paintFacts'), /if \(key !== sentenceSaid\) \{/, 'an identical read performs no write to it');
|
|
659
|
+
assert.match(fn('factNode'), /t\.appendChild\(textNode\('span', 'vh', ', ' \+ FACT_SAID\[r\.state\]\)\);/, 'each fact says its state to a screen reader');
|
|
660
|
+
assert.match(fn('factNode'), /d\.setAttribute\('aria-hidden', 'true'\);/, 'the dot is for the eye only');
|
|
661
|
+
assert.doesNotMatch(HUB, /id="liveProgress"|id="hopStatus"/, 'the old strip and its echo are gone');
|
|
662
|
+
assert.match(SCRIPT, /factsDrawn = \{\}; sentenceSaid = '';/, 'starting another project resets both guards');
|
|
669
663
|
assert.match(COSMOS, /\.vh\{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset\(50%\);white-space:nowrap;\}/, 'the visually-hidden utility is in cosmos');
|
|
670
664
|
assert.match(fn('stripMarkup'), /role="status" aria-live="polite"/, 'the mine-card strip is left alone this pass');
|
|
671
665
|
});
|
|
@@ -747,25 +741,30 @@ test('the done panel re-learns the GitHub-App leg from the instance read, yieldi
|
|
|
747
741
|
assert.match(sp, /var ga = data\.github_app;\n\s*if \(ga && ga\.status && !manifestPollTimer\) \{/, 'seeded from the same 5s read; a running manifest poll owns live.manifest');
|
|
748
742
|
assert.match(sp, /var seeded = ga\.status === 'done' \? 'done' : ga\.status;/, 'the status route’s vocabulary, verbatim');
|
|
749
743
|
assert.match(sp, /if \(ga\.app_slug\) live\.appSlug = ga\.app_slug;\n\s*live\.manifest = seeded;/);
|
|
750
|
-
assert.match(sp, /if \(seedChanged\)
|
|
744
|
+
assert.match(sp, /if \(seedChanged\) rememberManifest\(undefined, live\.manifest, live\.appSlug \|\| undefined\);/, 'change-only: an identical tick never rewrites the bridge');
|
|
751
745
|
const seedAt = sp.indexOf('var ga = data.github_app;');
|
|
752
|
-
const
|
|
753
|
-
assert.ok(seedAt > 0 && seedAt <
|
|
746
|
+
const paintAt = sp.indexOf('live.inst = inst || null; live.readAt = Date.now();');
|
|
747
|
+
assert.ok(seedAt > 0 && seedAt < paintAt, 'the manifest seed lands before the facts are redrawn, so one paintFacts() paints both');
|
|
754
748
|
/* the bridge moved from enter() to enterCreated() when task 1003503 split the
|
|
755
749
|
created-record branch out — same seed, same place in the same flow */
|
|
756
750
|
assert.match(fn('enterCreated'), /if \(created\.manifest\) \{ live\.manifest = created\.manifest; live\.appSlug = created\.app_slug \|\| null; \}/, 'the sessionStorage bridge stays as the pre-first-tick seed');
|
|
757
751
|
});
|
|
758
752
|
|
|
759
753
|
/* ── a settings push is named on both surfaces, by state, with no duration promise (task 1003140) ── */
|
|
760
|
-
test('a queued or running settings push is named on the card strip AND
|
|
761
|
-
assert.equal((SCRIPT.match(/settings: \{ names: \['Change saved', 'Restarting to apply it', 'Applied'\], cls: '' \}/g) || []).length,
|
|
762
|
-
'FLOWS (card)
|
|
754
|
+
test('a queued or running settings push is named on the card strip AND part 03 — by state, no "briefly"', () => {
|
|
755
|
+
assert.equal((SCRIPT.match(/settings: \{ names: \['Change saved', 'Restarting to apply it', 'Applied'\], cls: '' \}/g) || []).length, 1,
|
|
756
|
+
'FLOWS (card) carries the settings flow; part 03 has no stage strip (task 1004683)');
|
|
763
757
|
const strip = fn('stripFor');
|
|
764
758
|
assert.match(strip, /a === 'settings-apply'/, 'the card strip names the intent');
|
|
765
759
|
assert.match(strip, /A settings change is queued — this card updates when it starts\./, 'pending: queued, not "restarting"');
|
|
766
760
|
assert.match(strip, /Applying a settings change — the project restarts to pick it up\./, 'running: the restart is said plainly');
|
|
767
|
-
const prog = fn('
|
|
768
|
-
assert.match(prog, /
|
|
769
|
-
assert.match(prog, /
|
|
770
|
-
assert.
|
|
761
|
+
const prog = fn('homeFacts');
|
|
762
|
+
assert.match(prog, /act === 'settings-apply'/, 'part 03 has the same branch — a restarting project is not read as "isn’t answering"');
|
|
763
|
+
assert.match(prog, /oi\.state === 'running'/, 'by state');
|
|
764
|
+
assert.match(prog, /'A settings change is queued\. It restarts to apply it\.'/);
|
|
765
|
+
// the settings branch only: part 03's "Usually a few minutes." for a fresh standup is the
|
|
766
|
+
// owner's approved mockup word (setup-status.html, 2026-10-09), not a settings promise
|
|
767
|
+
const settingsBranch = prog.slice(prog.indexOf("act === 'settings-apply'"), prog.indexOf("else if (s === 'active') server ="));
|
|
768
|
+
assert.ok(settingsBranch.length > 40, 'the settings branch is found');
|
|
769
|
+
assert.doesNotMatch(strip + settingsBranch, /briefly|shortly|soon\b|minute|second/i, 'no wall-clock or duration promise (F16)');
|
|
771
770
|
});
|
|
@@ -178,19 +178,14 @@ test('the empty state points a new owner at the wizard view — never at a hall'
|
|
|
178
178
|
assert.doesNotMatch(body, /Enter the Hall/i);
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
test('the
|
|
182
|
-
// QA walk F7 (task 1002702 → 1002721
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// Owner dropdown strands the build watching the wrong address) and must state
|
|
187
|
-
// the visibility choice up front, before GitHub's own radio is the first
|
|
188
|
-
// mention.
|
|
181
|
+
test('part 03 never asks the founder to make the repo by hand: the platform makes it at Create (task 1004683)', async () => {
|
|
182
|
+
// QA walk F7 (task 1002702 → 1002721) once needed the "Create the empty GitHub repo" step
|
|
183
|
+
// to say where the repo lands. Since GitHub in one trip (task 1004649) the platform makes
|
|
184
|
+
// the empty repo at Create (ensureFreshRepo), so that step and its adopt / auto-create
|
|
185
|
+
// variants are gone from part 03 (the design of record setup-status.html).
|
|
189
186
|
const { body } = await request('/projects', 'game.test.local');
|
|
190
|
-
assert.
|
|
191
|
-
assert.match(body,
|
|
192
|
-
'the step must say the build watches the exact owner/name');
|
|
193
|
-
assert.match(body, /public or private/i, 'visibility must be stated in our copy, not first seen on GitHub');
|
|
187
|
+
assert.doesNotMatch(body, /Create the empty GitHub repo|function renderStepGithubRepo\(|Owner box/, 'the pre-one-trip repo step is gone');
|
|
188
|
+
assert.match(body, /<div id="ghFresh" class="wizField" data-onboard-step="github-repo" hidden>/, 'the new repo part 02 makes at Create is the plan\'s github-repo step');
|
|
194
189
|
});
|
|
195
190
|
|
|
196
191
|
test('teardown copy states what the runner actually does (F17)', async () => {
|
|
@@ -213,101 +208,47 @@ test('teardown copy states what the runner actually does (F17)', async () => {
|
|
|
213
208
|
assert.doesNotMatch(body, /database (is|will be) (deleted|dropped|destroyed|removed)/i);
|
|
214
209
|
});
|
|
215
210
|
|
|
216
|
-
|
|
211
|
+
// part 03's Its home (task 1004683): three facts read off the owner's instance read
|
|
212
|
+
const homeFactsOf = (body) => (body.match(/function homeFacts\(inst, now\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
213
|
+
|
|
214
|
+
test('Its home reads the open intent — a queued revive is starting, never told to start another (F22)', async () => {
|
|
217
215
|
const { body } = await request('/projects', 'game.test.local');
|
|
218
|
-
const fn = (body
|
|
219
|
-
assert.ok(fn, '
|
|
220
|
-
assert.match(fn, /open_intent/, 'the
|
|
221
|
-
assert.match(fn, /
|
|
216
|
+
const fn = homeFactsOf(body);
|
|
217
|
+
assert.ok(fn, 'homeFacts() must be extractable');
|
|
218
|
+
assert.match(fn, /open_intent/, 'the server fact is chosen from the open intent, not status alone');
|
|
219
|
+
assert.match(fn, /act === 'provision'\) server = \{ state: 'now', title: 'Server and database starting'/, 'a queued standup or revive is starting');
|
|
222
220
|
// a requested instance ALWAYS has its provision intent open — the status
|
|
223
221
|
// branch must win there or it goes dead and diverges from the mine card
|
|
224
|
-
assert.match(fn,
|
|
222
|
+
assert.match(fn, /s !== 'requested'/, 'requested speaks its status, not its (always-present) intent');
|
|
225
223
|
assert.match(body, /inst\.open_intent = data\.open_intent \|\| null/, 'the watch hands the intent through');
|
|
226
|
-
// teardown
|
|
227
|
-
assert.match(fn, /'teardown'
|
|
228
|
-
assert.
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
test('the done panel lights Live on a verified answer, never on status alone (task 1002895)', async () => {
|
|
232
|
-
const { body } = await request('/projects', 'game.test.local');
|
|
233
|
-
const fn = (body.match(/function renderProgress\(inst\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
234
|
-
assert.ok(fn, 'renderProgress() must be extractable');
|
|
235
|
-
assert.match(fn, /last_verified_up/, 'the strip reads the verification overlay, not status alone');
|
|
236
|
-
// the three honest rungs: verified, failed probe, and never-probed —
|
|
237
|
-
// null is deliberately distinct from false and must not read as a failure
|
|
238
|
-
assert.match(fn, /answering at/i, 'verified speaks the list view\'s language');
|
|
239
|
-
assert.match(fn, /isn’t answering right now/, 'a failed probe is spoken, never painted green');
|
|
240
|
-
assert.match(fn, /checking that/, 'the never-probed instance gets its own calm rung');
|
|
241
|
-
// gap row 8: domainless is never probed at all — it states its truth instead
|
|
242
|
-
// of promising the hall (sign-in stays locked without an address)
|
|
243
|
-
assert.match(fn, /no address yet/, 'the domainless path cannot fall into an unverified hole');
|
|
244
|
-
assert.doesNotMatch(fn, /Your project is up/, 'the status-alone headline is dead');
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
/* ── The done panel's GitHub step (task 1002855) ─────────────────────────────
|
|
248
|
-
'We created <repo> on GitHub for you' named a repo and handed the owner nothing
|
|
249
|
-
to click, while the manual-create branch right below it rendered a full button:
|
|
250
|
-
the one path where the PLATFORM did the work was the one that stranded them
|
|
251
|
-
(owner-reported during the task 1002697 walk). Adopt named a repo unlinked too. ── */
|
|
252
|
-
|
|
253
|
-
test('the auto-created repo is linked in the done panel, not merely named', async () => {
|
|
254
|
-
const { body } = await request('/projects', 'game.test.local');
|
|
255
|
-
const fn = (body.match(/function renderStepGithubRepo\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
256
|
-
assert.ok(fn, 'renderStepGithubRepo() must be extractable');
|
|
257
|
-
const auto = (fn.match(/if \(state\.repoAutoCreated\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
258
|
-
assert.ok(auto, 'the auto-created branch must be extractable');
|
|
259
|
-
assert.match(auto, /We created/, 'this is the copy the owner reported');
|
|
260
|
-
// BOTH copies — scaffold landed and not yet landed — carry the link
|
|
261
|
-
assert.equal((auto.match(/repoLink/g) || []).length, 2,
|
|
262
|
-
'the landed and pre-landed copies must both link the repo');
|
|
263
|
-
assert.doesNotMatch(auto, /repoLabel/,
|
|
264
|
-
'no auto-created copy may still name the repo without linking it');
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test('the adopt branch links the repo it names', async () => {
|
|
268
|
-
const { body } = await request('/projects', 'game.test.local');
|
|
269
|
-
const fn = (body.match(/function renderStepGithubRepo\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
270
|
-
const adopt = (fn.match(/if \(state\.mode === 'adopt'\) \{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
271
|
-
assert.ok(adopt, 'the adopt branch must be extractable');
|
|
272
|
-
assert.equal((adopt.match(/repoLink/g) || []).length, 2,
|
|
273
|
-
'both adopt copies must link the repo the owner picked');
|
|
274
|
-
assert.doesNotMatch(adopt, /repoLabel/, 'adopt may not name a repo unlinked either');
|
|
224
|
+
// a teardown is a calm fact, never painted as a failure
|
|
225
|
+
assert.match(fn, /act === 'teardown' \|\| s === 'tearing_down'\) server = \{ state: 'todo', title: 'Server and database switching off'/);
|
|
226
|
+
assert.doesNotMatch(body, /or start another below/, 'never told to start another');
|
|
275
227
|
});
|
|
276
228
|
|
|
277
|
-
test('
|
|
229
|
+
test('"Address answers" ticks on a verified answer, never on status alone (task 1002895)', async () => {
|
|
278
230
|
const { body } = await request('/projects', 'game.test.local');
|
|
279
|
-
const fn = (body
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
assert.match(
|
|
284
|
-
assert.
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
assert.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
231
|
+
const fn = homeFactsOf(body);
|
|
232
|
+
assert.match(fn, /else if \(inst\.last_verified_up === true\) \{ answers\.state = 'done';/, 'only a verified answer ticks it');
|
|
233
|
+
// the three honest rungs: verified, failed probe, and never-probed — null is
|
|
234
|
+
// deliberately distinct from false and must not read as a failure
|
|
235
|
+
assert.match(fn, /else if \(inst\.last_verified_up === false\) \{ answers\.state = 'now'; answers\.sub = 'Not answering yet\. We keep checking\.';/, 'a failed probe is spoken, never painted green');
|
|
236
|
+
assert.match(fn, /answers\.sub = 'Checking that it answers\.';/, 'the never-probed instance gets its own calm rung');
|
|
237
|
+
// gap row 8: domainless is never probed at all — the fact stays not reached
|
|
238
|
+
assert.match(fn, /if \(dom && s === 'active' && act !== 'teardown'\) \{/, 'no address: the fact is never under way');
|
|
239
|
+
assert.doesNotMatch(body, /Your project is up/, 'the status-alone headline is dead');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('the repo is named as text from the server read, never a guessed link (tasks 1002855, 1004683)', async () => {
|
|
243
|
+
// Part 03 names the repo its app installs on in the Install fact: the owner read's
|
|
244
|
+
// github_setup.repo (github-setup.js targetRepo, strict), re-checked by seedSetup, or the
|
|
245
|
+
// page's own owner/repo. Never a link the page built: nothing on it is the founder's to do.
|
|
293
246
|
const { body } = await request('/projects', 'game.test.local');
|
|
294
|
-
const
|
|
295
|
-
assert.ok(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
assert.equal(
|
|
300
|
-
urlFor({ mode: 'greenfield', owner: 'someone', repo: 'thing', repoAutoCreated: true, repoUrl: 'https://github.com/someone/thing' }),
|
|
301
|
-
'https://github.com/someone/thing');
|
|
302
|
-
// a draft written before task 1002855 has no stored URL — it degrades to none
|
|
303
|
-
assert.equal(
|
|
304
|
-
urlFor({ mode: 'greenfield', owner: 'someone', repo: 'thing', repoAutoCreated: true }), '');
|
|
305
|
-
// adopt has no server-supplied URL: the repo the owner picked IS the address
|
|
306
|
-
assert.equal(urlFor({ mode: 'adopt', owner: 'someone', repo: 'thing' }), 'https://github.com/someone/thing');
|
|
307
|
-
// nothing named yet, and anything that isn't a github.com URL, link to nothing
|
|
308
|
-
assert.equal(urlFor({ mode: 'adopt', owner: '', repo: '' }), '');
|
|
309
|
-
assert.equal(urlFor({ mode: 'greenfield', owner: 'a', repo: 'b', repoUrl: 'javascript:alert(1)' }), '');
|
|
310
|
-
assert.equal(urlFor({ mode: 'greenfield', owner: 'a', repo: 'b', repoUrl: 'https://evil.example/x' }), '');
|
|
247
|
+
const fn = (body.match(/function repoName\(\)\s*\{([\s\S]*?)\n \}/) || [, ''])[1];
|
|
248
|
+
assert.ok(fn, 'repoName() must be extractable');
|
|
249
|
+
assert.match(fn, /if \(live\.repo\) return live\.repo;/);
|
|
250
|
+
assert.match(body, /var repo = typeof more\.repo === 'string' && \/\^\[A-Za-z0-9\]\[A-Za-z0-9-\]\{0,38\}\\\/\[A-Za-z0-9\._-\]\{1,100\}\$\/\.test\(more\.repo\)/, 'the server\'s repo is re-checked before it is shown');
|
|
251
|
+
assert.doesNotMatch(body, /function repoLinkFor\(|class="ghbtn"/, 'no guessed repo link, no GitHub pill');
|
|
311
252
|
});
|
|
312
253
|
|
|
313
254
|
test('the create-repo response keeps the html_url it already returned', async () => {
|
|
@@ -448,10 +389,12 @@ test('the address row surfaces the manual-DNS note the runner recorded (F16)', a
|
|
|
448
389
|
assert.match(body, /inst\.dns_note/, 'the card reads the dns_note the runner writes');
|
|
449
390
|
});
|
|
450
391
|
|
|
451
|
-
test('the watch
|
|
452
|
-
// the wizard
|
|
392
|
+
test('the watch never gives up on a timer, and "the hall has it" is dead', async () => {
|
|
393
|
+
// the wizard lives on /projects; since task 1004683 its watch has no 33-minute cap — it
|
|
394
|
+
// stops when the facts say nothing more will change (setupSettled), never on a tick count
|
|
453
395
|
const { body } = await request('/projects', 'game.test.local');
|
|
454
|
-
assert.
|
|
396
|
+
assert.doesNotMatch(body, /this tab will stop watching now|ticks > 400/, 'no timer stands in for a fact');
|
|
397
|
+
assert.match(body, /var terminal = setupSettled\(\);/, 'the facts decide when the watch stops');
|
|
455
398
|
assert.doesNotMatch(body, /the hall has it/, 'the false poll-stop copy must be gone');
|
|
456
399
|
});
|
|
457
400
|
|