@bongos/core 1.20.48 → 1.20.49
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 +45 -35
- package/docs/copy-inventory.md +121 -107
- package/docs/copy-registry.json +259 -133
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +7 -2
- package/docs/page-readings.json +196 -170
- package/modules/hall-ui/public/criterion-uat-ui.js +280 -0
- package/modules/hall-ui/public/goal-map.html +5 -3
- package/modules/hall-ui/public/goals-page.js +23 -44
- package/modules/hall-ui/public/goals-render.js +24 -15
- package/modules/hall-ui/public/goals.css +45 -0
- package/modules/hall-ui/public/goals.html +5 -2
- package/modules/hall-ui/public/goals.states.json +38 -0
- package/modules/hall-ui/public/sky-data.js +9 -1
- package/modules/hall-ui/public/sky-interaction.js +1 -1
- package/modules/hall-ui/public/sky-panel.js +1 -1
- package/modules/hall-ui/public/thinking.html +5 -3
- package/modules/lifecycle/done-when.js +3 -1
- package/modules/lifecycle/sky.js +23 -8
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/hall-preview/server.js +2 -0
- package/src/module-api.js +1 -1
- package/tests/claim_action.mjs +6 -1
- package/tests/criterion_uat_ui.mjs +113 -0
- package/tests/goal_criterion_confirm_ui.mjs +188 -91
- package/tests/goal_map_page.mjs +2 -1
- package/tests/hall_sky.mjs +16 -0
- package/tests/helpers.mjs +3 -1
- package/tests/sky_read.mjs +7 -1
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
${waiting.length ? fact('waits on', waiting.map(name).join(', ')) : ''}
|
|
79
79
|
${waitedOn.length ? fact('waited on by', waitedOn.map(name).join(', ')) : ''}
|
|
80
80
|
</div>
|
|
81
|
-
${g.crit.length ? `<ul class="crit">${g.crit.slice(0, 10).map((c) => `<li><span class="ring${c.satisfied ? ' on' : ''}"></span><span>${esc(plain(c.text).slice(0, 120))}${plain(c.text).length > 120 ? '…' : ''}${c.bornOf ? ` <span class="born">· born of a thought</span>` : ''}${c.credits ? ` <span class="lbl">${c.credits} cr</span>` : ''}</span></li>`).join('')}</ul>` : ''}
|
|
81
|
+
${g.crit.length ? `<ul class="crit">${g.crit.slice(0, 10).map((c) => `<li><span class="ring${c.satisfied ? ' on' : ''}"></span><span>${esc(plain(c.text).slice(0, 120))}${plain(c.text).length > 120 ? '…' : ''}${c.bornOf ? ` <span class="born">· born of a thought</span>` : ''}${c.credits ? ` <span class="lbl">${c.credits} cr</span>` : ''}${c.uatLabel ? ` <span class="lbl">${esc(c.uatLabel)}</span>` : ''}${c.backendOnly ? ' <span class="lbl">backend-only</span>' : ''}</span></li>`).join('')}</ul>` : ''}
|
|
82
82
|
<div class="doors">
|
|
83
83
|
<button type="button" class="door primary" id="descendBtn">${g.status === 'open' ? 'descend into the well' : 'look closer'}</button>
|
|
84
84
|
${edgesOf(g).length ? `<button type="button" class="door" id="chartBtn">chart its routes</button>` : ''}
|
|
@@ -142,11 +142,13 @@
|
|
|
142
142
|
<script src="/builders/palette.js" defer></script>
|
|
143
143
|
|
|
144
144
|
<script src="/builders/idea-objects.js?v=2026-09-27-split"></script>
|
|
145
|
-
|
|
145
|
+
<!-- task 1004402: the UAT state words the rings share with the goal page. -->
|
|
146
|
+
<script src="/builders/criterion-uat-ui.js?v=2026-09-30-uat1"></script>
|
|
147
|
+
<script src="/builders/sky-data.js?v=2026-09-30-uat1"></script>
|
|
146
148
|
<script src="/builders/sky-layout.js?v=2026-09-27-split"></script>
|
|
147
149
|
<script src="/builders/sky-draw.js?v=2026-09-27-split"></script>
|
|
148
|
-
<script src="/builders/sky-interaction.js?v=2026-09-
|
|
149
|
-
<script src="/builders/sky-panel.js?v=2026-09-
|
|
150
|
+
<script src="/builders/sky-interaction.js?v=2026-09-30-uat1"></script>
|
|
151
|
+
<script src="/builders/sky-panel.js?v=2026-09-30-uat1"></script>
|
|
150
152
|
<script src="/builders/sky-composer.js?v=2026-09-27-split"></script>
|
|
151
153
|
<script src="/builders/sky-tour.js?v=2026-09-27-split"></script>
|
|
152
154
|
</body>
|
|
@@ -221,7 +221,9 @@ async function criterionProgress(versionId) {
|
|
|
221
221
|
// about coverage. It uses idx_tasks_version_status (version_id leading).
|
|
222
222
|
const [{ rows }, { rows: unattr }, goalRows] = await Promise.all([
|
|
223
223
|
pool.query(
|
|
224
|
-
|
|
224
|
+
// MATERIALIZED (task 1004402): the per-criterion UAT subqueries run once per
|
|
225
|
+
// criterion here, never once per (criterion, task) row after the join.
|
|
226
|
+
`WITH ranked AS MATERIALIZED (
|
|
225
227
|
SELECT d.id, d.version_id, d.criterion_id, d.criterion_md, d.satisfied, d.goal_id,
|
|
226
228
|
ROW_NUMBER() OVER (ORDER BY d.sort_order, d.criterion_id) AS cnum,
|
|
227
229
|
${uatReadColumnsSql('d')}
|
package/modules/lifecycle/sky.js
CHANGED
|
@@ -42,7 +42,7 @@ const api = require('../../src/module-api');
|
|
|
42
42
|
const { pool } = api;
|
|
43
43
|
const goalGraph = require('./goal-graph');
|
|
44
44
|
const goalCategory = require('./goal-category');
|
|
45
|
-
const { isPendingReview } = require('./done-when');
|
|
45
|
+
const { isPendingReview, uatState, uatReadColumnsSql } = require('./done-when');
|
|
46
46
|
const { TERMINAL_STATUSES } = require('./task-status');
|
|
47
47
|
|
|
48
48
|
const SKY_CACHE_TTL_MS = 60 * 1000;
|
|
@@ -67,15 +67,24 @@ async function skyRows({ versionId = null, viewerId = null, viewerIsArchon = fal
|
|
|
67
67
|
activePool.query(
|
|
68
68
|
// The criterion→task join, read once. Flat rows on purpose (see the header):
|
|
69
69
|
// the fold happens in shapeSky so a test can compare it with the rollup's.
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// task 1004402: each criterion's UAT columns (ADR 0351) are read ONCE per
|
|
71
|
+
// criterion, in a MATERIALIZED step before the task join fans the rows out;
|
|
72
|
+
// spliced into the flat join they re-ran once per linked task, on the one
|
|
73
|
+
// read that is the whole project's sky.
|
|
74
|
+
`WITH crit AS MATERIALIZED (
|
|
75
|
+
SELECT c.id, c.goal_id, c.criterion_id, c.criterion_md, c.satisfied, c.sort_order,
|
|
76
|
+
${uatReadColumnsSql('c')}
|
|
77
|
+
FROM done_when_criteria c
|
|
78
|
+
JOIN goals g ON g.id = c.goal_id
|
|
79
|
+
WHERE c.goal_id IS NOT NULL
|
|
80
|
+
AND ($1::text IS NULL OR g.version_id = $1)
|
|
81
|
+
)
|
|
82
|
+
SELECT c.id::text AS id, c.goal_id::text AS goal_id, c.criterion_id, c.criterion_md,
|
|
83
|
+
c.satisfied, c.sort_order, c.uat_kind, c.backend_only,
|
|
72
84
|
t.id::text AS task_id, t.status AS task_status
|
|
73
|
-
FROM
|
|
74
|
-
JOIN goals g ON g.id = c.goal_id
|
|
85
|
+
FROM crit c
|
|
75
86
|
LEFT JOIN task_criteria tc ON tc.criterion_id = c.id
|
|
76
87
|
LEFT JOIN tasks t ON t.id = tc.task_id
|
|
77
|
-
WHERE c.goal_id IS NOT NULL
|
|
78
|
-
AND ($1::text IS NULL OR g.version_id = $1)
|
|
79
88
|
ORDER BY c.goal_id, c.sort_order, c.criterion_id, t.id`,
|
|
80
89
|
[version]
|
|
81
90
|
),
|
|
@@ -111,21 +120,27 @@ function foldCriteria(rows = []) {
|
|
|
111
120
|
criterion_id: row.criterion_id,
|
|
112
121
|
criterion_md: row.criterion_md,
|
|
113
122
|
satisfied: row.satisfied === true,
|
|
123
|
+
backend_only: row.backend_only === true,
|
|
114
124
|
task_ids: [],
|
|
115
125
|
_unshipped: 0,
|
|
126
|
+
_shipped: 0,
|
|
127
|
+
_uatKind: row.uat_kind ?? null,
|
|
116
128
|
};
|
|
117
129
|
byId.set(c.id, c);
|
|
118
130
|
}
|
|
119
131
|
if (row.task_id != null) {
|
|
120
132
|
c.task_ids.push(String(row.task_id));
|
|
121
133
|
if (!TERMINAL_STATUSES.includes(row.task_status)) c._unshipped += 1;
|
|
134
|
+
if (row.task_status === 'shipped') c._shipped += 1;
|
|
122
135
|
}
|
|
123
136
|
}
|
|
124
137
|
return [...byId.values()].map((c) => {
|
|
125
|
-
const { _unshipped, ...rest } = c;
|
|
138
|
+
const { _unshipped, _shipped, _uatKind, ...rest } = c;
|
|
126
139
|
return {
|
|
127
140
|
...rest,
|
|
128
141
|
pending_review: isPendingReview({ satisfied: c.satisfied, linked: c.task_ids.length, unshipped: _unshipped }),
|
|
142
|
+
// task 1004402: the same derivation the goal page and /status use.
|
|
143
|
+
uat_state: uatState({ satisfied: c.satisfied, linked: c.task_ids.length, unshipped: _unshipped, shipped: _shipped, latestKind: _uatKind }),
|
|
129
144
|
};
|
|
130
145
|
});
|
|
131
146
|
}
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.49",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.20.
|
|
9
|
+
"version": "1.20.49",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.49",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -8401,5 +8401,11 @@
|
|
|
8401
8401
|
"id": "1004422",
|
|
8402
8402
|
"text": "While a new project is being founded, its builders hall now shows a clear \"project founding mode\" band at the top and dims the rooms that open later. A hall that isn't being founded stays exactly as it was."
|
|
8403
8403
|
}
|
|
8404
|
+
],
|
|
8405
|
+
"1.20.49": [
|
|
8406
|
+
{
|
|
8407
|
+
"id": "1004402",
|
|
8408
|
+
"text": "A goal's page now shows where each checklist item really stands: Awaiting UAT, Satisfied, or Closed before UAT, with a Backend-only tag where there is nothing to record. The old Confirm button is gone. Instead, an item whose w"
|
|
8409
|
+
}
|
|
8404
8410
|
]
|
|
8405
8411
|
}
|
|
@@ -105,6 +105,8 @@ const FIXTURE_FALLBACKS = [
|
|
|
105
105
|
[/^goals\/\d+\/dependency-forest$/, 'goals__dependency-forest'],
|
|
106
106
|
[/^goals\/\d+\/requests$/, 'goals__requests'],
|
|
107
107
|
[/^goals\/\d+$/, 'goals__detail'],
|
|
108
|
+
// a criterion's UAT panel (task 1004402): one Awaiting UAT reading serves any id.
|
|
109
|
+
[/^done-when\/\d+\/uat$/, 'done-when__uat'],
|
|
108
110
|
[/^tasks\/\d+$/, 'tasks__detail'],
|
|
109
111
|
// the #/idea/N + #/blocker/N deep-link overlay (task-detail.js) — one record
|
|
110
112
|
// fixture serves any id so the overlay seam assertion renders real content.
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.20.
|
|
78
|
+
const CORE_VERSION = '1.20.49'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/claim_action.mjs
CHANGED
|
@@ -401,8 +401,13 @@ await test('the cache-bust moved on every page whose script changed', () => {
|
|
|
401
401
|
['goals.html', ['goals-render.js', 'goals-page.js']],
|
|
402
402
|
]) {
|
|
403
403
|
const html = read('modules', 'hall-ui', 'public', page);
|
|
404
|
+
// The invariant is "busted AT OR AFTER the claim-action change", not the
|
|
405
|
+
// literal stamp: a later change to the same script bumps it again (task
|
|
406
|
+
// 1004402 did), and a pin on the string would fail that correct bump.
|
|
404
407
|
for (const s of scripts) {
|
|
405
|
-
|
|
408
|
+
const m = new RegExp(s.replace('.', '\\.') + '\\?v=(\\d{4}-\\d{2}-\\d{2})').exec(html);
|
|
409
|
+
assert.ok(m, `${page}: ${s} carries no dated cache-bust`);
|
|
410
|
+
assert.ok(m[1] >= '2026-09-26', `${page}: ${s} not busted since the claim-action change (${m[1]})`);
|
|
406
411
|
}
|
|
407
412
|
}
|
|
408
413
|
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// tests/criterion_uat_ui.mjs — the hall's criterion UAT helpers (task 1004402,
|
|
2
|
+
// ADR 0351), DB-free and DOM-free. The page-level wiring is driven for real in
|
|
3
|
+
// tests/goal_criterion_confirm_ui.mjs; this file pins the vocabulary and the
|
|
4
|
+
// panel's variations.
|
|
5
|
+
//
|
|
6
|
+
// 1. The labels equal the server's (modules/lifecycle/criterion-uat.js
|
|
7
|
+
// stateLabel), so the hall, /status and /goal-uat say the same thing. The
|
|
8
|
+
// browser cannot require server code, so this is the lockstep.
|
|
9
|
+
// 2. The backend-only switch appears only where it can work: an OPEN criterion,
|
|
10
|
+
// in the full view, for a reviewer, on a server that knows the rule.
|
|
11
|
+
// 3. The panel asks for what the server will require and nothing else: the
|
|
12
|
+
// attestation only where the site cannot read its own deploy; no file field
|
|
13
|
+
// on a backend-only criterion; no form for a viewer who cannot sign.
|
|
14
|
+
// 4. A stale sign-off is said to no longer count, never shown as the UAT.
|
|
15
|
+
//
|
|
16
|
+
// Run: node tests/criterion_uat_ui.mjs
|
|
17
|
+
|
|
18
|
+
import { strict as assert } from 'node:assert';
|
|
19
|
+
import { createRequire } from 'node:module';
|
|
20
|
+
import { makeRunner } from './helpers.mjs';
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
const ui = require('../modules/hall-ui/public/criterion-uat-ui.js');
|
|
24
|
+
const server = require('../modules/lifecycle/criterion-uat.js');
|
|
25
|
+
const { test, summary } = makeRunner();
|
|
26
|
+
|
|
27
|
+
await test('LABELS: every server state has the same words in the hall', () => {
|
|
28
|
+
for (const state of Object.values(server.UAT_STATES)) {
|
|
29
|
+
assert.equal(ui.STATE_LABELS[state], server.stateLabel(state), state);
|
|
30
|
+
}
|
|
31
|
+
assert.equal(Object.keys(ui.STATE_LABELS).length, Object.values(server.UAT_STATES).length, 'no hall-only state');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await test('STATE: an older server with no uat_state reads plainly and never as a UAT state', () => {
|
|
35
|
+
assert.equal(ui.stateOf({ satisfied: true }), 'legacy_satisfied');
|
|
36
|
+
assert.equal(ui.stateOf({ satisfied: false }), 'open');
|
|
37
|
+
assert.equal(ui.badgesHtml({ satisfied: true }), '');
|
|
38
|
+
assert.equal(ui.actionHtml({ id: 1, satisfied: true }, { canReview: true, full: true }), '');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
await test('BACKEND-ONLY SWITCH: only on an OPEN criterion, in the full view, for a reviewer', () => {
|
|
42
|
+
const open = { id: 9, uat_state: 'open', satisfied: false };
|
|
43
|
+
assert.match(ui.actionHtml(open, { canReview: true, full: true }), /data-uat-backend="9" data-on="1"/);
|
|
44
|
+
assert.match(ui.actionHtml({ ...open, backend_only: true }, { canReview: true, full: true }), /data-on="0"[^>]*>Not backend-only/);
|
|
45
|
+
assert.equal(ui.actionHtml(open, { canReview: true, full: false }), '', 'not in the capped band: it is an authoring act');
|
|
46
|
+
assert.equal(ui.actionHtml(open, { canReview: false, full: true }), '');
|
|
47
|
+
assert.doesNotMatch(ui.actionHtml({ id: 9, uat_state: 'awaiting_uat' }, { canReview: true, full: true }), /data-uat-backend/,
|
|
48
|
+
'once awaiting, the server refuses it, so the page does not offer it');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const VIEW = (over = {}) => ({
|
|
52
|
+
criterion: { id: 42 },
|
|
53
|
+
uat_state: 'awaiting_uat',
|
|
54
|
+
backend_only: false,
|
|
55
|
+
checks: { code: true, live: { available: false }, uat: null, stale_uat: null },
|
|
56
|
+
...over,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await test('PANEL: a site that cannot read its deploy asks for the attestation; one that can, does not', () => {
|
|
60
|
+
const blind = ui.panelHtml(VIEW(), { apiBase: '/api/bongos', canReview: true });
|
|
61
|
+
assert.match(blind, /data-uat-attest="42"/);
|
|
62
|
+
assert.match(blind, /data-uat-file="42"/);
|
|
63
|
+
const seeing = ui.panelHtml(VIEW({ checks: { code: true, live: { available: true, live: true }, uat: null } }), { apiBase: '/api/bongos', canReview: true });
|
|
64
|
+
assert.doesNotMatch(seeing, /data-uat-attest/);
|
|
65
|
+
assert.match(seeing, /Running on the live site\./);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await test('PANEL: not live yet names the tasks, in plain words', () => {
|
|
69
|
+
const html = ui.panelHtml(VIEW({ checks: { code: true, live: { available: true, live: false, not_live: [{ task_id: '12' }] }, uat: null } }), { apiBase: '', canReview: true });
|
|
70
|
+
assert.match(html, /not on the live site yet \(#12\)/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await test('PANEL: a backend-only criterion has no file field and asks how it was checked', () => {
|
|
74
|
+
const html = ui.panelHtml(VIEW({ backend_only: true }), { apiBase: '', canReview: true });
|
|
75
|
+
assert.doesNotMatch(html, /data-uat-file/);
|
|
76
|
+
assert.match(html, /How you checked it/);
|
|
77
|
+
assert.match(html, />Sign off</);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await test('PANEL: a viewer who cannot sign sees the checks and no form', () => {
|
|
81
|
+
const html = ui.panelHtml(VIEW(), { apiBase: '', canReview: false });
|
|
82
|
+
assert.match(html, /uat-checks/);
|
|
83
|
+
assert.doesNotMatch(html, /data-uat-form/);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await test('PANEL: a stale sign-off is said to no longer count, and its recording still plays', () => {
|
|
87
|
+
const stale = { kind: 'uat', signed_off_by_login: 'ana', created_at: '2026-09-20T10:00:00Z', recording_file: 'uat-42-0123456789abcdef.mp4' };
|
|
88
|
+
const html = ui.panelHtml(VIEW({ checks: { code: true, live: { available: false }, uat: null, stale_uat: stale } }), { apiBase: '/api/bongos', canReview: true });
|
|
89
|
+
assert.match(html, /no longer counts: more work shipped after it/);
|
|
90
|
+
assert.match(html, /<video class="uat-video" controls preload="metadata" src="\/api\/bongos\/criterion-uats\/recordings\/uat-42-0123456789abcdef\.mp4">/);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await test('PANEL: a closed criterion shows who signed it and when, and offers no form', () => {
|
|
94
|
+
const html = ui.panelHtml(VIEW({ uat_state: 'satisfied_uat', checks: { code: true, live: null, uat: { kind: 'uat', signed_off_by_login: 'ana', created_at: '2026-09-28T10:00:00Z', note: 'made a rank' } } }), { apiBase: '', canReview: true });
|
|
95
|
+
assert.match(html, /Tried on the live site by ana/);
|
|
96
|
+
assert.match(html, /made a rank/);
|
|
97
|
+
assert.doesNotMatch(html, /data-uat-form/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await test('ESCAPING: a note or a login can never become markup', () => {
|
|
101
|
+
const html = ui.panelHtml(VIEW({ uat_state: 'satisfied_uat', checks: { code: true, uat: { kind: 'uat', signed_off_by_login: '<b>x</b>', created_at: '2026-09-28', note: '"><script>alert(1)</script>' } } }), { apiBase: '', canReview: false });
|
|
102
|
+
assert.doesNotMatch(html, /<script>|<b>x<\/b>/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await test('REFUSALS: the server\'s sentence wins; the fallbacks are plain words', () => {
|
|
106
|
+
assert.equal(ui.refusalText({ error: { code: 'work_not_live', message: 'Some of this work is not deployed yet.' } }, 409), 'Some of this work is not deployed yet.');
|
|
107
|
+
assert.match(ui.refusalText({}, 413), /100 MB cap/);
|
|
108
|
+
assert.match(ui.refusalText(null, 500), /did not go through \(500\)/);
|
|
109
|
+
assert.equal(ui.checkFile({ type: 'video/webm', size: 5 }), null);
|
|
110
|
+
assert.equal(ui.checkFile(null), 'Choose the recording first.');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
summary();
|