@bongos/core 1.21.96 → 1.21.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +39 -34
- 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 +3 -1
- package/clients/bongos-client/index.mjs +2 -0
- package/docs/api/openapi.json +50 -3
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +363 -363
- package/docs/copy-registry.json +435 -435
- package/docs/design/project-startup-direction.md +13 -0
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-readings.json +250 -250
- package/modules/provisioning/github-setup.js +73 -4
- package/modules/provisioning/routes/github-setup.js +43 -1
- package/modules/provisioning/routes/provisioning.js +8 -4
- package/modules/public-landing/public/projects.html +185 -30
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/gds/provision-github-setup.js +29 -9
- package/scripts/gds/provision.js +20 -9
- package/src/module-api.js +1 -1
- package/tests/github_one_trip.mjs +4 -4
- package/tests/github_org_account.mjs +3 -2
- package/tests/github_pick_up.mjs +366 -0
- package/tests/projects_hub_pre_uat.mjs +3 -2
- package/tests/provisioning_github_app_routes.mjs +73 -0
- package/tests/wizard_github_feedback.mjs +95 -29
|
@@ -29,6 +29,9 @@ provisioning.getOAuthManifestByState = async (_db, state) => (state === 'GOODSTA
|
|
|
29
29
|
? { id: 99, instance_id: 42, requested_by: 7, status: 'pending', expires_at: new Date(Date.now() + 600000).toISOString() }
|
|
30
30
|
: null);
|
|
31
31
|
provisioning.attachManifestCode = async (_db, id, code) => ({ id, status: 'code_received', github_code: code });
|
|
32
|
+
// the latest attempt the mint guard and the pick-up route read (task 1004652); none by default
|
|
33
|
+
let latest = null;
|
|
34
|
+
provisioning.getLatestOAuthManifestForInstance = async () => latest;
|
|
32
35
|
|
|
33
36
|
// A branding with a known publicOrigin so mothershipOrigin() resolves deterministically.
|
|
34
37
|
const branding = require(`${ROOT}/src/branding.js`);
|
|
@@ -281,6 +284,76 @@ console.log('\nGET /provisioning/github-app/status (the page poll → auto-open
|
|
|
281
284
|
t('missing state → 400', res.status === 400, `status=${res.status}`);
|
|
282
285
|
}
|
|
283
286
|
|
|
287
|
+
console.log('\nNever a second app (task 1004652): the mint guard and "Pick up where it stopped":');
|
|
288
|
+
{
|
|
289
|
+
const mint = async () => fetch(`${base}/provisioning/instances/42/github-app`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
|
|
290
|
+
const soon = new Date(Date.now() + 600000).toISOString();
|
|
291
|
+
// a link still waiting on GitHub is handed back — the SAME state, no new attempt row
|
|
292
|
+
stored = null;
|
|
293
|
+
latest = { id: 70, instance_id: 42, state: 'WAITINGSTATE', status: 'pending', attempts: 0, expires_at: soon, created_at: new Date().toISOString() };
|
|
294
|
+
let res = await mint();
|
|
295
|
+
let j = await res.json();
|
|
296
|
+
t('a waiting link is reopened, not re-minted: same state, same expiry, no new row', res.status === 200 && j.state === 'WAITINGSTATE' && j.expires_at === soon && stored === null && j.post_url === 'https://github.com/settings/apps/new?state=WAITINGSTATE', JSON.stringify(j));
|
|
297
|
+
// GitHub already made the app (keys placed, sign-in unconfirmed): refused, nothing minted
|
|
298
|
+
for (const row of [
|
|
299
|
+
{ id: 71, instance_id: 42, state: 'S71', status: 'error', attempts: 1, app_id: 5, app_slug: 'mercury-app', expires_at: soon },
|
|
300
|
+
{ id: 72, instance_id: 42, state: 'S72', status: 'done', attempts: 1, app_id: 5, app_slug: 'mercury-app', expires_at: soon },
|
|
301
|
+
{ id: 73, instance_id: 42, state: 'S73', status: 'error', attempts: 2, app_id: null, app_slug: null, expires_at: soon },
|
|
302
|
+
]) {
|
|
303
|
+
stored = null; latest = row;
|
|
304
|
+
res = await mint(); j = await res.json();
|
|
305
|
+
t(`an attempt GitHub approved (${row.status}, attempts ${row.attempts}) refuses a new link: 409 app_exists, nothing minted`, res.status === 409 && j.error.code === 'app_exists' && stored === null, JSON.stringify(j));
|
|
306
|
+
}
|
|
307
|
+
stored = null; latest = { id: 74, instance_id: 42, state: 'S74', status: 'converting', attempts: 1, expires_at: soon };
|
|
308
|
+
res = await mint(); j = await res.json();
|
|
309
|
+
t('an attempt the runner is converting refuses a new link: 409 github_in_progress', res.status === 409 && j.error.code === 'github_in_progress' && stored === null, JSON.stringify(j));
|
|
310
|
+
// nothing was made on GitHub: a fresh link is minted
|
|
311
|
+
for (const row of [
|
|
312
|
+
{ id: 75, instance_id: 42, state: 'S75', status: 'pending', attempts: 0, expires_at: new Date(Date.now() - 1000).toISOString() },
|
|
313
|
+
{ id: 76, instance_id: 42, state: 'S76', status: 'error', attempts: 0, expires_at: soon },
|
|
314
|
+
]) {
|
|
315
|
+
stored = null; latest = row;
|
|
316
|
+
res = await mint(); j = await res.json();
|
|
317
|
+
t(`nothing made on GitHub (${row.status}, attempts ${row.attempts}) → a fresh state is minted`, res.status === 200 && stored && stored.state === j.state && j.state !== row.state, JSON.stringify(j));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// the pick-up route: 'convert' re-queues the SAME attempt with the code it holds
|
|
321
|
+
const queries = [];
|
|
322
|
+
const pg = require(`${ROOT}/src/module-api.js`).pool;
|
|
323
|
+
const prevQuery = pg.query;
|
|
324
|
+
pg.query = async (sql, params) => { queries.push({ sql, params }); return { rows: /UPDATE provisioning_oauth_manifests/.test(sql) ? [{ id: params[0] }] : [] }; };
|
|
325
|
+
const pickUp = async () => fetch(`${base}/provisioning/instances/42/github-app/pick-up`, { method: 'POST' });
|
|
326
|
+
latest = { id: 80, instance_id: 42, state: 'S80', status: 'error', attempts: 1, github_code: 'CODE', app_id: null, app_slug: null, created_at: new Date(Date.now() - 10 * 60000).toISOString(), expires_at: soon };
|
|
327
|
+
const before = reported.length;
|
|
328
|
+
stored = null;
|
|
329
|
+
res = await pickUp(); j = await res.json();
|
|
330
|
+
const requeue = queries.find((q) => /UPDATE provisioning_oauth_manifests/.test(q.sql));
|
|
331
|
+
t("pick-up 'convert' re-queues attempt 80 itself (status code_received, attempts reset), mints nothing", res.status === 200 && j.action === 'convert' && requeue && requeue.params[0] === 80 && /status = 'code_received', attempts = 0/.test(requeue.sql) && /github_code IS NOT NULL/.test(requeue.sql) && stored === null, JSON.stringify(j));
|
|
332
|
+
// 'check': keys placed, the runner's window closed → a check request, no new attempt
|
|
333
|
+
queries.length = 0;
|
|
334
|
+
latest = { id: 81, instance_id: 42, state: 'S81', status: 'error', attempts: 1, app_id: 5, app_slug: 'mercury-app', created_at: new Date(Date.now() - 5 * 3600000).toISOString(), resolved_at: new Date(Date.now() - 4 * 3600000).toISOString(), expires_at: soon };
|
|
335
|
+
res = await pickUp(); j = await res.json();
|
|
336
|
+
const asked = reported.slice(before).filter((e) => e.event === 'github-app-check-requested');
|
|
337
|
+
t("pick-up 'check' records one check request for the project and writes no attempt", res.status === 200 && j.action === 'check' && asked.length === 1 && asked[0].instanceId === 42 && !queries.some((q) => /UPDATE|INSERT/.test(q.sql)) && stored === null, JSON.stringify(j));
|
|
338
|
+
// nothing the route itself can pick up: 409 naming the page's own action
|
|
339
|
+
for (const [row, action] of [
|
|
340
|
+
[{ id: 82, instance_id: 42, state: 'S82', status: 'pending', attempts: 0, expires_at: soon }, 'reopen'],
|
|
341
|
+
[{ id: 83, instance_id: 42, state: 'S83', status: 'error', attempts: 2, github_code: null, app_id: null, created_at: new Date().toISOString(), expires_at: soon }, 'operator'],
|
|
342
|
+
]) {
|
|
343
|
+
latest = row;
|
|
344
|
+
res = await pickUp(); j = await res.json();
|
|
345
|
+
t(`pick-up on '${action}' is the page's own: 409 nothing_to_pick_up, action named`, res.status === 409 && j.error.code === 'nothing_to_pick_up' && j.error.action === action, JSON.stringify(j));
|
|
346
|
+
}
|
|
347
|
+
// another builder's project is a 404, like a missing one
|
|
348
|
+
const prevInst = provisioning.getInstanceById;
|
|
349
|
+
provisioning.getInstanceById = async () => ({ id: 42, owner_builder_id: 8, slug: 'mercury', domain: 'mercury.example.com', status: 'active' });
|
|
350
|
+
res = await pickUp();
|
|
351
|
+
t('pick-up on someone else’s project → 404', res.status === 404, `status=${res.status}`);
|
|
352
|
+
provisioning.getInstanceById = prevInst;
|
|
353
|
+
pg.query = prevQuery;
|
|
354
|
+
latest = null;
|
|
355
|
+
}
|
|
356
|
+
|
|
284
357
|
console.log('\nparseId contract (the (req,res) signature — a bad id is a clean 400, not a 500):');
|
|
285
358
|
{
|
|
286
359
|
const res = await fetch(`${base}/provisioning/instances/not-a-number/github-app`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
|
|
@@ -69,9 +69,9 @@ test('publicGithubApp: effective status, failure only on error, started_at, and
|
|
|
69
69
|
|
|
70
70
|
// ── 2. the words for each stage ─────────────────────────────────────────────
|
|
71
71
|
|
|
72
|
-
// from the shared helpers of the four GitHub steps (task 1004649) to the
|
|
72
|
+
// from the shared helpers of the four GitHub steps (task 1004649) to the renderer table
|
|
73
73
|
const RS = SCRIPT.indexOf(' /* The four GitHub steps of part 03');
|
|
74
|
-
const RE = SCRIPT.indexOf('
|
|
74
|
+
const RE = SCRIPT.indexOf(' var STEP_RENDERERS = {', RS);
|
|
75
75
|
const RENDER = SCRIPT.slice(RS, RE);
|
|
76
76
|
|
|
77
77
|
test('the GitHub step region is still findable (a rename must fail loudly)', () => {
|
|
@@ -79,11 +79,10 @@ test('the GitHub step region is still findable (a rename must fail loudly)', ()
|
|
|
79
79
|
assert.match(RENDER, /function renderStepOauthApp\(\)/);
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
function stage(live, {
|
|
82
|
+
function stage(live, { tab = null, fn = 'renderStepOauthApp' } = {}) {
|
|
83
83
|
const sandbox = {
|
|
84
|
-
live: { manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null, instStatus: 'active', addrReady: true, ...live },
|
|
84
|
+
live: { manifest: null, manifestMsg: '', manifestCode: '', manifestFailure: null, appSlug: null, setup: null, installUrl: null, instStatus: 'active', addrReady: true, pickUp: null, verifying: false, askedPlatform: false, ...live },
|
|
85
85
|
state: { name: 'Mercury', slug: 'mercury' },
|
|
86
|
-
manifestStale: stale,
|
|
87
86
|
autoOpenArmed: false,
|
|
88
87
|
resolvedDomain: () => 'mercury.example.com',
|
|
89
88
|
apexBase: () => 'example.com',
|
|
@@ -95,17 +94,26 @@ function stage(live, { stale = false } = {}) {
|
|
|
95
94
|
progressMarkup: (now, done, note, cls) => `<prog now=${now} done=${done} cls="${cls}">${note}</prog>`,
|
|
96
95
|
};
|
|
97
96
|
vm.createContext(sandbox);
|
|
98
|
-
vm.runInContext(`${RENDER}\nvar out =
|
|
97
|
+
vm.runInContext(`${RENDER}\nghTabTrouble = ${JSON.stringify(tab)};\nvar out = ${fn}();`, sandbox);
|
|
99
98
|
return sandbox.out;
|
|
100
99
|
}
|
|
101
100
|
const startButton = /data-action="manifest-start"/;
|
|
101
|
+
const NEVER_A_CHORE = /\/contact|Contact us|delete it first|Developer settings → GitHub Apps\) so|Try again anyway|Start the setup again/;
|
|
102
102
|
|
|
103
|
-
test('pending: waiting for approval,
|
|
103
|
+
test('pending: waiting for approval, says where a missing GitHub tab went, and reopens the SAME setup', () => {
|
|
104
104
|
const html = stage({ manifest: 'pending' });
|
|
105
105
|
assert.match(html, /Waiting for your approval on GitHub/);
|
|
106
106
|
assert.match(html, /No GitHub tab\? It may have opened behind this window or in another one/);
|
|
107
|
-
assert.doesNotMatch(html, startButton, 'no
|
|
108
|
-
assert.match(
|
|
107
|
+
assert.doesNotMatch(html, startButton, 'no new-link button while GitHub holds this one (task 1004652)');
|
|
108
|
+
assert.match(html, /class="linklike" data-action="reopen-github">No GitHub tab\? Reopen GitHub/, 'the way back is there from the first second, not after 90');
|
|
109
|
+
// the tab this page opened, seen from here (task 1004652)
|
|
110
|
+
const behind = stage({ manifest: 'pending' }, { tab: 'behind' });
|
|
111
|
+
assert.match(behind, /Your GitHub tab opened behind this window/);
|
|
112
|
+
assert.match(behind, /class="ghbtn" data-action="reopen-github">Reopen GitHub ↗/);
|
|
113
|
+
const closed = stage({ manifest: 'pending' }, { tab: 'closed' });
|
|
114
|
+
assert.match(closed, /The GitHub tab closed before the app was approved, so nothing was made on GitHub/);
|
|
115
|
+
assert.match(closed, /data-action="reopen-github"/);
|
|
116
|
+
assert.doesNotMatch(behind + closed, startButton);
|
|
109
117
|
});
|
|
110
118
|
|
|
111
119
|
test('converting is the SAME stage as code_received — approved, placing credentials — never "waiting for approval"', () => {
|
|
@@ -118,24 +126,59 @@ test('converting is the SAME stage as code_received — approved, placing creden
|
|
|
118
126
|
}
|
|
119
127
|
});
|
|
120
128
|
|
|
121
|
-
test('
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
assert.match(
|
|
125
|
-
|
|
126
|
-
assert.match(
|
|
127
|
-
assert.match(
|
|
128
|
-
assert.
|
|
129
|
-
assert.
|
|
130
|
-
assert.
|
|
129
|
+
test('sign-in unconfirmed: the app stays ticked, sign-in is the stopped step, and picking it up makes no second app', () => {
|
|
130
|
+
const live = { manifest: 'error', manifestFailure: 'sign_in_unconfirmed', appSlug: 'mercury-cloudbongos-com', pickUp: 'check' };
|
|
131
|
+
const approve = stage(live);
|
|
132
|
+
assert.match(approve, /^<li class="sdone"/, 'GitHub made the app and its keys are placed: that much is true');
|
|
133
|
+
const signIn = stage(live, { fn: 'renderStepFirstSignIn' });
|
|
134
|
+
assert.match(signIn, /^<li class="sstopped"/);
|
|
135
|
+
assert.match(signIn, /Your project didn’t confirm that sign-in works\. Nothing is lost, and no second app will be made\./);
|
|
136
|
+
assert.match(signIn, /data-action="pick-up">Pick up where it stopped/);
|
|
137
|
+
assert.match(signIn, /data-action="ask-platform">Send it to the platform team/, 'a project that still will not answer goes to the platform team from here');
|
|
138
|
+
assert.doesNotMatch(approve + signIn, startButton, 'no new link: it would make a second app');
|
|
139
|
+
assert.doesNotMatch(approve + signIn, NEVER_A_CHORE);
|
|
140
|
+
// while the runner is asking again, the step says so and offers no button that repeats it
|
|
141
|
+
const checking = stage({ ...live, pickUp: null, verifying: true }, { fn: 'renderStepFirstSignIn' });
|
|
142
|
+
assert.match(checking, /We’re asking it again, with the app GitHub already made/);
|
|
143
|
+
assert.doesNotMatch(checking, /data-action="pick-up"/);
|
|
144
|
+
// sent: the step says the platform team has it
|
|
145
|
+
assert.match(stage({ ...live, askedPlatform: true }, { fn: 'renderStepFirstSignIn' }), /Sent\. The platform team has it/);
|
|
146
|
+
assert.match(stage({ ...live, askedPlatform: 'failed' }, { fn: 'renderStepFirstSignIn' }), /That didn’t send\. Try again\.[\s\S]*data-action="ask-platform"/);
|
|
131
147
|
});
|
|
132
148
|
|
|
133
|
-
test('approved
|
|
134
|
-
const html = stage({ manifest: 'error', manifestFailure: 'finish_failed' });
|
|
135
|
-
assert.match(html,
|
|
136
|
-
assert.match(html, /
|
|
149
|
+
test('approved, conversion stopped with its code still young: pick it up, no second app', () => {
|
|
150
|
+
const html = stage({ manifest: 'error', manifestFailure: 'finish_failed', pickUp: 'convert' });
|
|
151
|
+
assert.match(html, /^<li class="sstopped"/);
|
|
152
|
+
assert.match(html, /GitHub approved the app, and the platform stopped before it finished collecting the app’s keys\. Nothing is lost, and no second app will be made\./);
|
|
153
|
+
assert.match(html, /data-action="pick-up">Pick up where it stopped/);
|
|
154
|
+
assert.doesNotMatch(html, startButton);
|
|
155
|
+
assert.doesNotMatch(html, NEVER_A_CHORE);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('approved but the keys never reached us: the platform team, sent from the page — never a GitHub chore', () => {
|
|
159
|
+
const html = stage({ manifest: 'error', manifestFailure: 'finish_failed', pickUp: 'operator' });
|
|
160
|
+
assert.match(html, /GitHub made the app, but its keys never reached your project/);
|
|
161
|
+
assert.match(html, /data-action="ask-platform">Send it to the platform team/);
|
|
162
|
+
assert.doesNotMatch(html, startButton, 'a new link would leave the founder with two apps');
|
|
163
|
+
assert.doesNotMatch(html, NEVER_A_CHORE);
|
|
137
164
|
assert.doesNotMatch(html, /Nothing was changed on GitHub/);
|
|
138
|
-
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('the install the runner stopped checking for: the mockup’s sentence, and a pick-up that opens the SAME app’s install', () => {
|
|
168
|
+
const html = stage({ manifest: 'done', appSlug: 'mercury-app', installUrl: 'https://github.com/apps/mercury-app/installations/new', pickUp: 'check' }, { fn: 'renderStepAppInstall' });
|
|
169
|
+
assert.match(html, /^<li class="sstopped" data-onboard-step="app-install">/);
|
|
170
|
+
assert.match(html, /GitHub stopped before the app was installed on your repo\. Nothing is lost, and no second app will be made\./);
|
|
171
|
+
assert.match(html, /<a class="ghbtn" href="https:\/\/github\.com\/apps\/mercury-app\/installations\/new" target="_blank" rel="noopener" data-action="pick-up">Pick up where it stopped ↗<\/a>/);
|
|
172
|
+
// still being checked: the ordinary step, no stop
|
|
173
|
+
assert.doesNotMatch(stage({ manifest: 'done', appSlug: 'mercury-app', pickUp: null, verifying: true }, { fn: 'renderStepAppInstall' }), /sstopped/);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('device flow after the runner stopped checking: "Check again", once the install has ticked', () => {
|
|
177
|
+
const setup = { approve: 'done', install: 'done', sign_in: 'done', device_flow: 'later', connected: true };
|
|
178
|
+
const again = stage({ manifest: 'done', appSlug: 'mercury-app', setup, pickUp: 'check' }, { fn: 'renderStepDeviceFlow' });
|
|
179
|
+
assert.match(again, /data-action="pick-up">Turned it on\? Check again/);
|
|
180
|
+
assert.doesNotMatch(stage({ manifest: 'done', appSlug: 'mercury-app', setup, pickUp: null, verifying: true }, { fn: 'renderStepDeviceFlow' }), /Check again/);
|
|
181
|
+
assert.doesNotMatch(stage({ manifest: 'done', appSlug: 'mercury-app', setup: { ...setup, install: 'now', connected: false }, pickUp: 'check' }, { fn: 'renderStepDeviceFlow' }), /Check again/, 'the install step carries the action until it ticks');
|
|
139
182
|
});
|
|
140
183
|
|
|
141
184
|
test('a failure before GitHub (or a refused start) keeps the honest "nothing changed" line', () => {
|
|
@@ -175,7 +218,8 @@ function reloaded(githubApp, now) {
|
|
|
175
218
|
const sandbox = {
|
|
176
219
|
data: { github_app: githubApp },
|
|
177
220
|
live: { manifest: null, manifestFailure: null, appSlug: null },
|
|
178
|
-
manifestPollTimer: null, manifestStartedAt: 0,
|
|
221
|
+
manifestPollTimer: null, manifestStartedAt: 0, ghTabTrouble: null,
|
|
222
|
+
document: { hidden: false, hasFocus: () => true },
|
|
179
223
|
rememberManifest: () => {}, renderSteps: () => renders.push(1),
|
|
180
224
|
Date: { now: () => now, parse: Date.parse },
|
|
181
225
|
};
|
|
@@ -184,19 +228,41 @@ function reloaded(githubApp, now) {
|
|
|
184
228
|
return { sandbox, renders, tick: () => vm.runInContext(SEED, sandbox) };
|
|
185
229
|
}
|
|
186
230
|
|
|
187
|
-
test('a reloaded page
|
|
231
|
+
test('a reloaded page seeds the wait from when the link was minted, and keeps watching it', () => {
|
|
188
232
|
const minted = '2026-10-04T20:20:00Z';
|
|
189
233
|
const young = reloaded({ status: 'pending', app_slug: null, failure: null, started_at: minted }, Date.parse(minted) + 30000);
|
|
190
234
|
assert.equal(young.sandbox.live.manifest, 'pending');
|
|
191
235
|
assert.equal(young.sandbox.manifestStartedAt, Date.parse(minted), 'seeded from the server');
|
|
192
|
-
assert.equal(young.sandbox.manifestStale, false);
|
|
193
236
|
|
|
194
237
|
const old = reloaded({ status: 'pending', app_slug: null, failure: null, started_at: minted }, Date.parse(minted) + 120000);
|
|
195
|
-
old.tick(); // the next 5s read: same status
|
|
196
|
-
|
|
238
|
+
old.tick(); // the next 5s read: same status
|
|
239
|
+
// a reloaded page holds no handle on a GitHub tab, so it claims nothing about one; the
|
|
240
|
+
// step's "Reopen GitHub" is always on show instead (task 1004652)
|
|
241
|
+
assert.equal(old.sandbox.ghTabTrouble, null);
|
|
197
242
|
assert.equal(vm.runInContext('manifestInFlight()', old.sandbox), true);
|
|
198
243
|
});
|
|
199
244
|
|
|
245
|
+
test('the GitHub tab this page opened: behind or closed is noticed in seconds, not after 90', () => {
|
|
246
|
+
const r = reloaded({ status: 'pending', app_slug: null, failure: null, started_at: null }, 0);
|
|
247
|
+
const at = (tab, left, since, now, focused) => vm.runInContext(`ghTabState(${tab}, ${left}, ${since}, ${now}, ${focused})`, r.sandbox);
|
|
248
|
+
assert.equal(at('null', false, 0, 60000, true), null, 'no handle, nothing claimed');
|
|
249
|
+
assert.equal(at('{ closed: true }', true, 0, 1000, false), 'closed', 'closed is seen at once');
|
|
250
|
+
assert.equal(at('{ closed: false }', false, 0, 5000, true), 'behind', 'this page kept the focus five seconds after opening it');
|
|
251
|
+
assert.equal(at('{ closed: false }', false, 0, 3000, true), null, 'not before a few seconds');
|
|
252
|
+
assert.equal(at('{ closed: false }', true, 0, 60000, false), null, 'the founder went to the tab: it is in front');
|
|
253
|
+
assert.equal(at('{ closed: false }', false, 0, 60000, false), null, 'this page lost the focus: not behind');
|
|
254
|
+
assert.doesNotMatch(SCRIPT, /90000/, 'the 90-second wait is gone');
|
|
255
|
+
// the poll's check redraws only on a change, and stops once the link is no longer waiting
|
|
256
|
+
r.sandbox.ghTab = { closed: true };
|
|
257
|
+
vm.runInContext('ghTab = { closed: true }; ghTabSince = 0; noteManifestWait();', r.sandbox);
|
|
258
|
+
assert.equal(r.sandbox.ghTabTrouble, 'closed');
|
|
259
|
+
const n = r.renders.length;
|
|
260
|
+
vm.runInContext('noteManifestWait();', r.sandbox);
|
|
261
|
+
assert.equal(r.renders.length, n, 'no redraw when nothing moved');
|
|
262
|
+
vm.runInContext("live.manifest = 'code_received'; noteManifestWait();", r.sandbox);
|
|
263
|
+
assert.equal(r.sandbox.ghTabTrouble, null, 'approved: the tab no longer matters');
|
|
264
|
+
});
|
|
265
|
+
|
|
200
266
|
test('a reloaded page learns HOW a failure happened, not just that it did', () => {
|
|
201
267
|
const r = reloaded({ status: 'error', app_slug: 'mercury-cloudbongos-com', failure: 'sign_in_unconfirmed', started_at: null }, NOW);
|
|
202
268
|
assert.equal(r.sandbox.live.manifest, 'error');
|