@bongos/core 1.21.91 → 1.21.92
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 -39
- package/docs/copy-inventory.md +357 -367
- package/docs/copy-registry.json +559 -641
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +15 -18
- package/docs/page-readings.json +1235 -918
- package/modules/public-landing/public/projects-hall-live.states.json +6 -35
- package/modules/public-landing/public/projects.html +1019 -983
- package/modules/public-landing/public/projects.probes.json +15 -18
- package/modules/public-landing/public/projects.states.json +22 -25
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/onboard.mjs +8 -8
- package/tests/project_invite_ui.mjs +6 -5
- package/tests/projects_hub.mjs +77 -98
- package/tests/projects_hub_app_status.mjs +3 -3
- package/tests/projects_hub_app_step.mjs +8 -8
- package/tests/projects_hub_cloud_hop.mjs +29 -53
- package/tests/projects_hub_look.mjs +29 -39
- package/tests/projects_hub_module_picker.mjs +32 -39
- package/tests/projects_hub_pre_uat.mjs +11 -14
- package/tests/wizard_demo.mjs +30 -25
- package/tests/wizard_draft_resume.mjs +24 -32
- package/tests/wizard_front_door.mjs +91 -243
- package/tests/wizard_intent_resume.mjs +28 -48
- package/tests/wizard_one_page.mjs +201 -0
- package/tests/wizard_physics.mjs +46 -41
- package/tests/wizard_physics_more.mjs +71 -106
- package/tests/wizard_ship_mode.mjs +1 -1
- package/tests/wizard_six_screens.mjs +0 -129
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
// tests/wizard_front_door.mjs — the create
|
|
2
|
-
//
|
|
1
|
+
// tests/wizard_front_door.mjs — the create page's front door (task 1004417, BV2.PS05; reshaped
|
|
2
|
+
// by task 1004648, the one-page flow — docs/design/project-startup-direction.md "One-page flow").
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// equal doors, try the demo / start my project, both waiting for the tick. The demo is
|
|
10
|
-
// its own route (BV2.PS09). The separate terms screen is gone.
|
|
11
|
-
// The wizard carries no progress rail: an eyebrow names the screen, one Back walks back, and
|
|
12
|
-
// the night room (#wizRoom) stands behind every screen in place of the planet and bongo pair.
|
|
13
|
-
// Sign-in stays where it was: the gate (step 0) comes first.
|
|
4
|
+
// Sign-in stays the gate in front (step 0). After it there is ONE page (step 1) and, once the
|
|
5
|
+
// project exists, the same page with part 03 live (step 2). The fork screen and its two doors are
|
|
6
|
+
// gone: the demo is a small link under the heading, and the terms are one required tick above
|
|
7
|
+
// Create, recorded on the project against the terms version the SERVER stamps
|
|
8
|
+
// (tests/provisioning_terms_ack.mjs holds that half).
|
|
14
9
|
//
|
|
15
10
|
// Most of this RUNS the page's own script, the vm-slice idiom of tests/wizard_draft_resume.mjs:
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
11
|
+
// the tick's wiring and show()'s bookkeeping are sliced out of the inline script and executed
|
|
12
|
+
// under a minimal DOM stub. Every slice boundary is asserted, so a rename fails loudly instead of
|
|
13
|
+
// leaving this file quietly testing nothing.
|
|
19
14
|
//
|
|
20
15
|
// projects.html is CRLF on a Windows checkout; this file normalises at read time.
|
|
21
16
|
//
|
|
@@ -32,238 +27,97 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
32
27
|
const LANDING = path.join(ROOT, 'modules', 'public-landing', 'public');
|
|
33
28
|
const HUB = fs.readFileSync(path.join(LANDING, 'projects.html'), 'utf8').replace(/\r\n/g, '\n');
|
|
34
29
|
const SCRIPT = HUB.slice(HUB.lastIndexOf('<script>'), HUB.lastIndexOf('</script>'));
|
|
35
|
-
const STYLE = HUB.slice(HUB.indexOf('<style>'), HUB.indexOf('</style>'));
|
|
36
|
-
|
|
37
|
-
const panel = (id) => (HUB.match(new RegExp(`<section class="panel[^"]*" id="${id}"[\\s\\S]*?\\n {10}</section>`)) || [''])[0];
|
|
38
|
-
const FORK = panel('panel-fork');
|
|
39
30
|
const VIEW_NEW = (HUB.match(/<section id="view-new" hidden>[\s\S]*?\n<\/section>/) || [''])[0];
|
|
31
|
+
const PAGE = (VIEW_NEW.match(/<section class="panel on" id="panel-page"[\s\S]*?\n {10}<\/section>/) || [''])[0];
|
|
40
32
|
const MAP = {};
|
|
41
|
-
for (const m of ((SCRIPT.match(/var panels = \{([^}]*)\}/) || [, ''])[1]).matchAll(/(\d+):\s*'([a-z-]+)'/g)) MAP[m[
|
|
33
|
+
for (const m of ((SCRIPT.match(/var panels = \{([^}]*)\}/) || [, ''])[1]).matchAll(/(\d+):\s*'([a-z-]+)'/g)) MAP[Number(m[1])] = m[2];
|
|
42
34
|
|
|
43
35
|
// ── the order ───────────────────────────────────────────────────────────────────
|
|
44
|
-
test('the front
|
|
45
|
-
assert.
|
|
46
|
-
assert.
|
|
47
|
-
assert.
|
|
48
|
-
assert.
|
|
49
|
-
assert.
|
|
50
|
-
assert.doesNotMatch(HUB, /id="panel-terms"/, 'and no markup is left for it');
|
|
51
|
-
assert.ok(HUB.indexOf('id="panel-gate"') < HUB.indexOf('id="panel-fork"')
|
|
52
|
-
&& HUB.indexOf('id="panel-fork"') < HUB.indexOf('id="panel-what"')
|
|
53
|
-
&& HUB.indexOf('id="panel-what"') < HUB.indexOf('id="panel-checks"'), 'the markup reads in the same order');
|
|
54
|
-
assert.match(FORK, /<form data-next="2">/, 'the fork\'s primary door goes on to the first question');
|
|
55
|
-
assert.ok(VIEW_NEW, 'the ?view=new section is findable');
|
|
56
|
-
assert.doesNotMatch(VIEW_NEW, /data-back=/, 'no per-panel Back buttons — one #wizBack walks the screens');
|
|
57
|
-
assert.equal((VIEW_NEW.match(/id="wizBack"/g) || []).length, 1, 'one Back, in the wizard\'s top row');
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
test('the numbered rail is gone (task 1004504) — no steps list, no painter, no rule hiding it', () => {
|
|
61
|
-
const beats = [...HUB.matchAll(/<li[^>]*data-step="(\d+)"/g)].map((m) => Number(m[1]));
|
|
62
|
-
assert.deepEqual(beats, [], 'no rail beat is left anywhere on the page');
|
|
63
|
-
assert.doesNotMatch(HUB, /class="steps"/, 'no <ol class="steps"> in the wizard');
|
|
64
|
-
assert.doesNotMatch(SCRIPT, /paintRail\(/, 'no rail painter, and no call to one');
|
|
65
|
-
assert.doesNotMatch(STYLE, /\.wizFront \.steps|#view-new \.steps/, 'no rule left styling or hiding a rail');
|
|
36
|
+
test('sign-in stays the gate in front; then the page, which carries the static `on` for a no-script visitor', () => {
|
|
37
|
+
assert.deepEqual(MAP, { 0: 'panel-gate', 1: 'panel-page', 2: 'panel-page' });
|
|
38
|
+
assert.ok(VIEW_NEW.indexOf('id="panel-gate"') < VIEW_NEW.indexOf('id="panel-page"'), 'the markup reads in the same order');
|
|
39
|
+
assert.ok(PAGE, '#panel-page ships as real markup (C7)');
|
|
40
|
+
assert.doesNotMatch(VIEW_NEW, /id="panel-terms"|id="panel-fork"|id="wizStart"|id="wizBack"/, 'no terms screen, no fork, no doors, no Back');
|
|
41
|
+
assert.doesNotMatch(HUB, /<li[^>]*data-step="\d+"|class="steps"/, 'and no progress rail');
|
|
66
42
|
});
|
|
67
43
|
|
|
68
|
-
// ──
|
|
69
|
-
test('
|
|
70
|
-
assert.
|
|
71
|
-
assert.match(
|
|
72
|
-
|
|
73
|
-
assert.
|
|
74
|
-
assert.
|
|
75
|
-
assert.match(
|
|
76
|
-
'the whole row is its label, so the target is the sentence, not a 20px box — and the terms are the platform\'s own /terms, in a new tab so the draft is not left behind');
|
|
77
|
-
assert.equal((FORK.match(/type="submit"/g) || []).length, 1, 'one primary door');
|
|
78
|
-
assert.doesNotMatch(FORK, /\bBongos\b/, 'no product name baked where a child instance would ship it');
|
|
44
|
+
// ── the tick ────────────────────────────────────────────────────────────────────
|
|
45
|
+
test('the terms are one tick above Create: the whole row is the label, and the terms open in a new tab', () => {
|
|
46
|
+
assert.match(PAGE, /<input type="checkbox" id="termsAck" aria-describedby="h-terms">/, 'one checkbox, named by its hint');
|
|
47
|
+
assert.match(PAGE, /<label class="termsAck">\s*<input[^>]*>\s*<span>I can sign for this project, and I accept the <a class="termsRead" href="\/terms" target="_blank" rel="noopener">terms<\/a>\.<\/span>/,
|
|
48
|
+
'the target is the sentence, not an 18px box — and the platform\'s own /terms, in a new tab so the draft is not left behind');
|
|
49
|
+
assert.ok(PAGE.indexOf('id="termsAck"') < PAGE.indexOf('id="doCreate"'), 'the tick sits above Create');
|
|
50
|
+
assert.ok(PAGE.indexOf('id="part-run"') < PAGE.indexOf('id="termsAck"'), 'and below the summary');
|
|
51
|
+
assert.equal((HUB.match(/id="termsAck"/g) || []).length, 1);
|
|
79
52
|
});
|
|
80
53
|
|
|
81
54
|
test('the page never names a terms version — the server stamps which text was acknowledged', () => {
|
|
82
|
-
// A version baked here would be a second copy of TERMS_VERSION that drifts, and worse, a
|
|
83
|
-
// value a caller could send. The create request carries only the tick.
|
|
84
55
|
assert.doesNotMatch(HUB, /terms_version|termsVersion|terms_ack_version/, 'no version travels from the page');
|
|
85
|
-
assert.doesNotMatch(
|
|
56
|
+
assert.doesNotMatch(PAGE, /\b20\d\d-\d\d-\d\d\b/, 'and none is printed on the screen');
|
|
86
57
|
});
|
|
87
58
|
|
|
88
|
-
test('the resume offer lives
|
|
59
|
+
test('the resume offer lives at the top of the page, once', () => {
|
|
89
60
|
assert.equal((HUB.match(/id="wizResume"/g) || []).length, 1);
|
|
90
|
-
assert.ok(
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// ── the validator, executed ────────────────────────────────────────────────────
|
|
94
|
-
const VALIDATE_RE = /function validateStep\(step\) \{[\s\S]*?\n {4}\}/;
|
|
95
|
-
const validateSlice = (SCRIPT.match(VALIDATE_RE) || [null])[0];
|
|
96
|
-
|
|
97
|
-
function runValidate(step, termsAck, extra = {}) {
|
|
98
|
-
const els = {};
|
|
99
|
-
const $ = (id) => (els[id] = els[id] || { textContent: '', className: '', classList: { add() {} }, focus() {} });
|
|
100
|
-
const sandbox = { $, state: { termsAck, name: 'Mercury', projType: 'game', demo: null, confirmed: {}, ...extra }, validateRender: () => true, repoAccess: true };
|
|
101
|
-
sandbox.globalThis = sandbox;
|
|
102
|
-
vm.createContext(sandbox);
|
|
103
|
-
vm.runInContext(`${validateSlice}\nvar ok = validateStep(${step});`, sandbox);
|
|
104
|
-
return { ok: sandbox.ok, hint: els['h-terms'], state: sandbox.state };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
test('validateStep is still findable, with the terms clause inside it', () => {
|
|
108
|
-
assert.ok(validateSlice, 'validateStep() was not found in projects.html');
|
|
109
|
-
assert.match(validateSlice, /if \(step === 1\) \{\s*if \(!state\.termsAck\)/, 'the clause is part of the slice');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test('Start my project is refused until the box is ticked, and the refusal says why in the hint', () => {
|
|
113
|
-
const refused = runValidate(1, false);
|
|
114
|
-
assert.equal(refused.ok, false, 'no tick, no way past the fork');
|
|
115
|
-
assert.equal(refused.hint.className, 'hint err');
|
|
116
|
-
assert.match(refused.hint.textContent, /Tick the box to go on/);
|
|
117
|
-
const given = runValidate(1, true);
|
|
118
|
-
assert.equal(given.ok, true, 'a tick goes on');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('Start my project clears a demo started earlier — the submit door is a real project', () => {
|
|
122
|
-
// task 1004420: "Start my project" is a real project, so a demo the founder
|
|
123
|
-
// set up and then walked back from must not ride the create
|
|
124
|
-
const r = runValidate(1, true, { demo: { people: 3, hours_each: 4 }, confirmed: { team: true } });
|
|
125
|
-
assert.equal(r.ok, true);
|
|
126
|
-
assert.equal(r.state.demo, null, 'the walked-back demo does not ride the create');
|
|
127
|
-
assert.deepEqual(JSON.parse(JSON.stringify(r.state.confirmed)), {}, 'and its rows are asked again');
|
|
128
|
-
assert.equal(runValidate(1, false, { demo: { people: 3 } }).state.demo !== null, true, 'an unticked box changes nothing');
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('"What are you making?" asks a name and a kind (task 1004504)', () => {
|
|
132
|
-
assert.equal(runValidate(2, true).ok, true, 'a name and a kind go on');
|
|
133
|
-
assert.equal(runValidate(2, true, { name: ' ' }).ok, false, 'no name, no way on');
|
|
134
|
-
assert.equal(runValidate(2, true, { projType: null }).ok, false, 'no kind, no way on — Not sure is a fine answer');
|
|
61
|
+
assert.ok(PAGE.indexOf('id="wizResume"') > -1 && PAGE.indexOf('id="wizResume"') < PAGE.indexOf('id="page-h"'), 'above the heading, where a deliberate "New project" lands');
|
|
135
62
|
});
|
|
136
63
|
|
|
137
|
-
// ── the wiring, executed
|
|
138
|
-
const WIRE_START =
|
|
139
|
-
const WIRE_END = ' /* ── the demo route
|
|
140
|
-
const wireSlice = SCRIPT.includes(WIRE_START) && SCRIPT.
|
|
64
|
+
// ── the tick's wiring, executed ─────────────────────────────────────────────────
|
|
65
|
+
const WIRE_START = " $('termsAck').checked = !!state.termsAck;";
|
|
66
|
+
const WIRE_END = ' /* ── the demo route';
|
|
67
|
+
const wireSlice = SCRIPT.includes(WIRE_START) && SCRIPT.indexOf(WIRE_END) > SCRIPT.indexOf(WIRE_START)
|
|
68
|
+
? SCRIPT.slice(SCRIPT.indexOf(WIRE_START), SCRIPT.indexOf(WIRE_END)) : null;
|
|
141
69
|
|
|
142
|
-
function runWiring(
|
|
143
|
-
const handlers = {};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
70
|
+
function runWiring(termsAck) {
|
|
71
|
+
const els = {}, handlers = {};
|
|
72
|
+
let saves = 0, paints = 0;
|
|
73
|
+
const $ = (id) => (els[id] = els[id] || {
|
|
74
|
+
id, checked: false, textContent: '', className: '',
|
|
75
|
+
addEventListener(type, fn) { handlers[`${id}:${type}`] = fn; },
|
|
148
76
|
});
|
|
149
|
-
const
|
|
150
|
-
let saves = 0;
|
|
151
|
-
const sandbox = { $: (id) => els[id], state: { termsAck: initialAck }, saveDraft: () => { saves += 1; } };
|
|
77
|
+
const sandbox = { $, state: { termsAck }, saveDraft() { saves += 1; }, paintCreate() { paints += 1; } };
|
|
152
78
|
sandbox.globalThis = sandbox;
|
|
153
79
|
vm.createContext(sandbox);
|
|
154
80
|
vm.runInContext(wireSlice, sandbox);
|
|
155
|
-
return { els, handlers, sandbox, saves: () => saves };
|
|
81
|
+
return { els, handlers, sandbox, saves: () => saves, paints: () => paints };
|
|
156
82
|
}
|
|
157
83
|
|
|
158
|
-
test('the
|
|
84
|
+
test('the tick\'s wiring is findable', () => {
|
|
159
85
|
assert.ok(wireSlice && wireSlice.length > 100, 'the wiring block was not found in projects.html');
|
|
160
86
|
assert.match(wireSlice, /\$\('termsAck'\)\.addEventListener\('change'/, 'the tick is part of the slice');
|
|
161
|
-
assert.match(wireSlice, /\$\('wizStart'\)\.disabled = !state\.termsAck;/, 'and so is the door painter');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('both doors wait for the tick: disabled in the markup, and painted from the answer', () => {
|
|
165
|
-
assert.match(FORK, /<button type="button" class="btn big ghost" id="wizDemo"[^>]*\bdisabled>/, 'the demo door ships disabled');
|
|
166
|
-
assert.match(FORK, /<button type="submit" class="btn big" id="wizStart" disabled>/, 'and so does Start my project');
|
|
167
|
-
const fresh = runWiring(false);
|
|
168
|
-
assert.equal(fresh.els.wizDemo.disabled, true, 'an unticked draft keeps the demo door shut');
|
|
169
|
-
assert.equal(fresh.els.wizStart.disabled, true, 'and the real-project door');
|
|
170
|
-
fresh.els.termsAck.checked = true; fresh.handlers['termsAck:change'].call(fresh.els.termsAck);
|
|
171
|
-
assert.equal(fresh.els.wizDemo.disabled, false, 'ticking opens both doors');
|
|
172
|
-
assert.equal(fresh.els.wizStart.disabled, false);
|
|
173
|
-
fresh.els.termsAck.checked = false; fresh.handlers['termsAck:change'].call(fresh.els.termsAck);
|
|
174
|
-
assert.ok(fresh.els.wizDemo.disabled && fresh.els.wizStart.disabled, 'unticking shuts them again');
|
|
175
|
-
const resumed = runWiring(true);
|
|
176
|
-
assert.ok(!resumed.els.wizDemo.disabled && !resumed.els.wizStart.disabled, 'a draft that ticked comes back with its doors open');
|
|
177
|
-
assert.match(STYLE, /#view-new \.doorPill \.btn\[disabled\]\{opacity:\.35;cursor:default;\}/, 'a shut door reads as shut');
|
|
178
|
-
assert.match(SCRIPT, /\$\('wizDemo'\)\.addEventListener\('click', function \(\) \{ if \(state\.termsAck\) demoShow\('setup'\); \}\);/,
|
|
179
|
-
'and the demo door refuses an unticked box even if the attribute is stripped');
|
|
180
87
|
});
|
|
181
88
|
|
|
182
|
-
test('the tick mirrors into the draft both ways, and a resumed draft paints its own answer', () => {
|
|
183
|
-
const { els, handlers, sandbox, saves } = runWiring(true);
|
|
89
|
+
test('the tick mirrors into the draft both ways, repaints Create, and a resumed draft paints its own answer', () => {
|
|
90
|
+
const { els, handlers, sandbox, saves, paints } = runWiring(true);
|
|
184
91
|
assert.equal(els.termsAck.checked, true, 'a draft that ticked comes back ticked (the GitHub round-trip keeps it)');
|
|
185
92
|
els.termsAck.checked = false; handlers['termsAck:change'].call(els.termsAck);
|
|
186
93
|
assert.equal(sandbox.state.termsAck, false, 'unticking is an answer too');
|
|
187
|
-
|
|
94
|
+
sandbox.$('h-terms').className = 'hint err';
|
|
188
95
|
els.termsAck.checked = true; handlers['termsAck:change'].call(els.termsAck);
|
|
189
96
|
assert.equal(sandbox.state.termsAck, true);
|
|
190
|
-
assert.equal(els['h-terms'].className, 'hint', 'ticking clears
|
|
97
|
+
assert.equal(els['h-terms'].className, 'hint', 'ticking clears a refusal');
|
|
191
98
|
assert.equal(saves(), 2, 'each change is saved');
|
|
99
|
+
assert.equal(paints(), 2, 'and Create repaints with it — it waits for the tick');
|
|
100
|
+
assert.equal(runWiring(false).els.termsAck.checked, false);
|
|
192
101
|
});
|
|
193
102
|
|
|
194
|
-
|
|
195
|
-
assert.match(FORK, /id="wizDemo" aria-controls="demoSetup" aria-expanded="false"/, 'the door names what it opens');
|
|
196
|
-
assert.match(FORK, /<div id="demoSetup" class="demoView" hidden>/, 'baked markup, hidden at rest, never built by script');
|
|
197
|
-
assert.doesNotMatch(FORK, /demoSoon|isn’t open yet/, 'the placeholder that said the demo was not open is gone');
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// ── the fork's door pill ────────────────────────────────────────────────────────
|
|
201
|
-
test('the fork is the door pill: two equal doors in one object, the accent only on the one that goes on', () => {
|
|
202
|
-
assert.match(FORK, /<h1 id="fork-h">You’re about to start a digital planet\.<\/h1>/);
|
|
203
|
-
const pill = (FORK.match(/<div class="doorPill" role="group" aria-label="How to begin">[\s\S]*?<\/div>/) || [''])[0];
|
|
204
|
-
assert.ok(pill, 'one grouped pill');
|
|
205
|
-
const doors = [...pill.matchAll(/<button type="(button|submit)" class="([^"]+)"[^>]*>([^<]+)<\/button>/g)];
|
|
206
|
-
assert.equal(doors.length, 2, 'exactly two doors');
|
|
207
|
-
assert.deepEqual(doors.map((d) => d[3]), ['Try the demo', 'Start my project']);
|
|
208
|
-
assert.equal(doors[0][2], 'btn big ghost', 'the demo door is the secondary: transparent at rest');
|
|
209
|
-
assert.equal(doors[1][2], 'btn big', 'begin carries the accent');
|
|
210
|
-
assert.equal(doors[1][1], 'submit', 'and is the form\'s one submit, so Enter goes on');
|
|
211
|
-
assert.match(STYLE, /\.doorPill\{display:inline-grid;grid-template-columns:1fr 1fr;/, 'two EQUAL tracks — the two routes are equal ways in');
|
|
212
|
-
assert.match(STYLE, /\.doorPill\{[^}]*border-radius:999px;/, 'pill or nothing');
|
|
213
|
-
assert.match(STYLE, /@media \(max-width:520px\)\{\s*\.doorPill\{display:grid;grid-template-columns:1fr;/, 'at a phone width the two stack, still one object');
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test('the night room stands behind every screen — the planet stage and the bongo pair are gone (task 1004504)', () => {
|
|
217
|
-
assert.match(VIEW_NEW, /<div class="wizRoom" id="wizRoom" data-screen="fork" aria-hidden="true">/, 'decorative, so hidden from a screen reader, and baked at the fork for a no-script visitor');
|
|
218
|
-
assert.match(VIEW_NEW, /<span class="rmSeed"><img src="\/assets\/world\/r2-chrome-blob-one-cut\.webp" alt=""><\/span>/, 'the seed is a real plate, root-absolute so it is stamped');
|
|
219
|
-
assert.ok(fs.existsSync(path.join(LANDING, 'assets', 'world', 'r2-chrome-blob-one-cut.webp')), 'and it exists on disk');
|
|
220
|
-
assert.doesNotMatch(HUB, /id="forkStage"|class="forkSeed"|class="forkRoutes"|bongoCell|id="physStage"/, 'no fork stage, seed orbit, route list, bongo pair or forming planet left in the markup');
|
|
221
|
-
assert.doesNotMatch(STYLE, /\.forkStage\{|\.forkOrbit\{|\.bongoCell|\.physStage/, 'and no rule left for them');
|
|
222
|
-
const at = STYLE.indexOf('#view-new .wizRoom{');
|
|
223
|
-
const end = STYLE.indexOf('/* under 900 the window', at);
|
|
224
|
-
assert.ok(at > -1 && end > at, 'the night room\'s CSS block is findable');
|
|
225
|
-
const block = STYLE.slice(at, end);
|
|
226
|
-
const hexes = [...block.matchAll(/#[0-9a-f]{3,8}\b/gi)].map((m) => m[0].toLowerCase());
|
|
227
|
-
assert.ok(hexes.length > 0 && hexes.every((h) => h === '#000' || h === '#fff'), `every colour a mix of the fifteen with black or white (${[...new Set(hexes)]})`);
|
|
228
|
-
for (const m of block.matchAll(/--rm-[a-z0-9-]+:([^;]+);/g)) {
|
|
229
|
-
assert.match(m[1], /^color-mix\(in srgb,var\(--(sea|accent)\)|^rgba\(255,255,255,/, `a room token is derived, never a new literal: ${m[0]}`);
|
|
230
|
-
}
|
|
231
|
-
const motion = block.indexOf('@media (prefers-reduced-motion:no-preference){');
|
|
232
|
-
assert.ok(motion > -1, 'the room\'s motion sits inside the kill switch');
|
|
233
|
-
assert.doesNotMatch(block.slice(0, motion), /animation:|transition:/, 'and none outside it: the rest frame is the whole composition');
|
|
234
|
-
assert.doesNotMatch(block, /:root/, 'room tokens live on the room, never a page :root');
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test('the two routes are described honestly for today: the demo is open (task 1004420), creation promises no questions that do not exist yet', () => {
|
|
238
|
-
const intro = (FORK.match(/<div id="forkIntro">[\s\S]*?<\/form>/) || [''])[0];
|
|
239
|
-
assert.match(intro, /<p class="lede">Try a small demo first, or start your real project\.<\/p>/);
|
|
240
|
-
assert.doesNotMatch(FORK, /coming soon/, 'the demo is built, so nothing says it is coming');
|
|
241
|
-
assert.doesNotMatch(FORK, /seven questions/i, 'the copy promises no question count');
|
|
242
|
-
assert.doesNotMatch(FORK, /\bBongos\b/, 'no product name baked where a child instance would ship it');
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// ── show(): the room, the eyebrow and the one Back, executed ───────────────────
|
|
246
|
-
// The slice runs from the SCREEN map through show(), so eyebrowFor() and prevStep() ride
|
|
247
|
-
// along; both ends are asserted.
|
|
103
|
+
// ── show(), executed ────────────────────────────────────────────────────────────
|
|
248
104
|
const SHOW_START = ' var SCREEN = {';
|
|
249
105
|
const SHOW_RE = /function show\(step, moveFocus\) \{[\s\S]*?\n {4}\}/;
|
|
250
106
|
const showMatch = SCRIPT.match(SHOW_RE);
|
|
251
107
|
const showSlice = showMatch && SCRIPT.includes(SHOW_START) && SCRIPT.indexOf(SHOW_START) < showMatch.index
|
|
252
108
|
? SCRIPT.slice(SCRIPT.indexOf(SHOW_START), showMatch.index + showMatch[0].length) : null;
|
|
253
109
|
|
|
254
|
-
function runShow(step, { demo = null
|
|
255
|
-
const cls = (
|
|
110
|
+
function runShow(step, { demo = null } = {}) {
|
|
111
|
+
const cls = () => { const set = new Set(); return { set, toggle(c, on) { if (on) set.add(c); else set.delete(c); } }; };
|
|
256
112
|
const els = {};
|
|
257
113
|
const $ = (id) => (els[id] = els[id] || {
|
|
258
|
-
id, classList: cls(
|
|
114
|
+
id, classList: cls(), hidden: false, textContent: '', attrs: {},
|
|
259
115
|
setAttribute(k, v) { this.attrs[k] = v; }, querySelector: () => null,
|
|
260
116
|
});
|
|
117
|
+
let painted = '';
|
|
261
118
|
const sandbox = {
|
|
262
|
-
$, state: { demo }, panels:
|
|
263
|
-
|
|
264
|
-
paintDoors() {}, physLoadAccountOnce() {}, paintChecks() {}, physDefaults() {},
|
|
265
|
-
renderModules() {}, syncHome() {}, ensureRepoAccess() {}, checkSlugAvailable() {}, renderAddrPanel() {},
|
|
266
|
-
syncAppStep() {}, syncRenderSetup() {}, paintSetup() {}, renderLook() {}, renderRecap() {},
|
|
119
|
+
$, state: { demo }, panels: MAP, saveDraft() {},
|
|
120
|
+
paintPage() { painted = 'page'; }, paintCreate() { painted = 'create'; },
|
|
267
121
|
};
|
|
268
122
|
sandbox.globalThis = sandbox;
|
|
269
123
|
vm.createContext(sandbox);
|
|
@@ -271,36 +125,34 @@ function runShow(step, { demo = null, demoOpen = false } = {}) {
|
|
|
271
125
|
return {
|
|
272
126
|
on: Object.keys(els).filter((id) => els[id].classList.set.has('on')),
|
|
273
127
|
screen: els.wizRoom.attrs['data-screen'], eyebrow: els.wizEyebrow.textContent,
|
|
274
|
-
|
|
128
|
+
parts: !els.wizParts.hidden, tag: !els.demoTag.hidden, painted,
|
|
129
|
+
done: [...els['panel-done'].classList.set].sort(),
|
|
275
130
|
};
|
|
276
131
|
}
|
|
277
132
|
|
|
278
|
-
test('show() turns
|
|
133
|
+
test('show() turns the gate or the page on, tells the room where it is, and names it in the eyebrow', () => {
|
|
279
134
|
assert.ok(showSlice, 'the SCREEN map through show() was not found in projects.html');
|
|
280
135
|
assert.match(showSlice, /function eyebrowFor\(step\)/, 'the eyebrow is part of the slice');
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
assert.equal(
|
|
290
|
-
assert.
|
|
291
|
-
assert.equal(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
assert.equal(
|
|
297
|
-
assert.equal(
|
|
298
|
-
assert.equal(runShow(1, {
|
|
299
|
-
|
|
300
|
-
assert.equal(runShow(
|
|
301
|
-
assert.equal(runShow(2).tag, false, 'no demo tag on a real project');
|
|
302
|
-
assert.equal(runShow(2, { demo: { people: 3 } }).tag, true, 'the demo tag rides every screen of a demo');
|
|
303
|
-
assert.match(SCRIPT, /function prevStep\(step\) \{\s*if \(step === 4 && state\.demo\) return 2;/, 'a demo, which skipped the checks, walks back past them too');
|
|
136
|
+
const gate = runShow(0);
|
|
137
|
+
assert.deepEqual(gate.on, ['panel-gate'], 'signed out: only the gate');
|
|
138
|
+
assert.equal(gate.screen, 'signin');
|
|
139
|
+
assert.equal(gate.eyebrow, 'Sign in');
|
|
140
|
+
const page = runShow(1);
|
|
141
|
+
assert.deepEqual(page.on, ['panel-page'], 'the page, and part 03 still waiting');
|
|
142
|
+
assert.equal(page.screen, 'page');
|
|
143
|
+
assert.equal(page.eyebrow, 'New project');
|
|
144
|
+
assert.equal(page.parts, true, 'parts 01 and 02 on show');
|
|
145
|
+
assert.deepEqual(page.done, ['waiting'], 'part 03 waits until the project exists');
|
|
146
|
+
assert.equal(page.painted, 'page', 'the page paints every control on entry');
|
|
147
|
+
const made = runShow(2);
|
|
148
|
+
assert.deepEqual(made.on.sort(), ['panel-done', 'panel-page'], 'created: the same page, part 03 live');
|
|
149
|
+
assert.deepEqual(made.done, ['live', 'on']);
|
|
150
|
+
assert.equal(made.parts, false, 'parts 01 and 02 stand down behind the recap');
|
|
151
|
+
assert.equal(made.screen, 'done');
|
|
152
|
+
assert.equal(made.eyebrow, 'Founding');
|
|
153
|
+
assert.equal(runShow(1, { demo: { people: 3 } }).eyebrow, 'New demo', 'a demo is marked');
|
|
154
|
+
assert.equal(runShow(1, { demo: { people: 3 } }).tag, true, 'and its heading carries the demo tag');
|
|
155
|
+
assert.equal(runShow(1).tag, false, 'a real project carries none');
|
|
304
156
|
});
|
|
305
157
|
|
|
306
158
|
// ── the create ──────────────────────────────────────────────────────────────────
|
|
@@ -308,34 +160,30 @@ test('the create carries the tick, and is never filed without it', () => {
|
|
|
308
160
|
const create = SCRIPT.slice(SCRIPT.indexOf("$('doCreate').addEventListener('click'"));
|
|
309
161
|
assert.match(create, /terms_acknowledged: state\.termsAck \? true : undefined,/,
|
|
310
162
|
'a real boolean when ticked, absent otherwise — the route refuses anything but a boolean');
|
|
311
|
-
const guard = create.indexOf('if (
|
|
312
|
-
assert.ok(guard > -1, 'an unticked
|
|
163
|
+
const guard = create.indexOf('if (createWhy()) {');
|
|
164
|
+
assert.ok(guard > -1, 'an unticked page is refused, with its reason said in place');
|
|
313
165
|
assert.ok(guard < create.indexOf('ensureFreshRepo()'), 'BEFORE anything is created — the repo included');
|
|
166
|
+
assert.match(SCRIPT, /if \(!state\.termsAck\) return 'Tick the terms to create your project\.';/);
|
|
314
167
|
});
|
|
315
168
|
|
|
316
169
|
test('the tick is part of the draft and of the reset, and the draft key moved with the renumbering', () => {
|
|
317
|
-
assert.match(SCRIPT, /var state = \{ step: 0,[^}]*termsAck: false
|
|
318
|
-
assert.match(SCRIPT, /state = \{ step: 1,[^}]*termsAck: false
|
|
319
|
-
assert.match(SCRIPT, /\$\('termsAck'\)\.checked = false;
|
|
320
|
-
assert.match(SCRIPT, /var DRAFT_KEY = 'cb-create-draft-
|
|
170
|
+
assert.match(SCRIPT, /var state = \{ step: 0,[^}]*termsAck: false \};/, 'the initial state declares it');
|
|
171
|
+
assert.match(SCRIPT, /state = \{ step: 1,[^}]*termsAck: false \};/, 'and "start another project" clears it — each project records its own');
|
|
172
|
+
assert.match(SCRIPT, /\$\('termsAck'\)\.checked = false; demoShow\(false\); checksShow\('main'\);/, 'and the reset unticks the box');
|
|
173
|
+
assert.match(SCRIPT, /var DRAFT_KEY = 'cb-create-draft-v10';/, 'a draft saved under the old numbering is ignored, not replayed');
|
|
321
174
|
});
|
|
322
175
|
|
|
323
176
|
// ── the look-before-you-ship kit walks it ─────────────────────────────────────
|
|
324
|
-
|
|
325
|
-
// project, or the demo door and the demo screen's Continue.
|
|
326
|
-
test('every kit walk past the fork ticks first, then takes a door', () => {
|
|
177
|
+
test('every kit walk that creates a project ticks the terms before it presses Create', () => {
|
|
327
178
|
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8')).states;
|
|
328
179
|
assert.ok(states['in-new'], 'the in-new state exists');
|
|
329
|
-
for (const sel of ['#panel-
|
|
330
|
-
const REAL = ['#termsAck', '#wizStart'];
|
|
331
|
-
const DEMO = ['#termsAck', '#wizDemo', '#demoStart'];
|
|
180
|
+
for (const sel of ['#panel-page', '#termsAck', '#doCreate']) assert.ok((states['in-new'].expect.visible || []).includes(sel), `arriving at ?view=new pictures the page (${sel})`);
|
|
332
181
|
const probes = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.probes.json'), 'utf8')).probes;
|
|
333
182
|
const walks = [...Object.entries(states).map(([k, v]) => [k, v.actions || []]), ...probes.map((p) => [p.name, p.steps])]
|
|
334
|
-
.filter(([, steps]) => steps.some((st) => st[1] === '#
|
|
335
|
-
assert.ok(walks.length >=
|
|
183
|
+
.filter(([, steps]) => steps.some((st) => st[0] === 'click' && st[1] === '#doCreate'));
|
|
184
|
+
assert.ok(walks.length >= 2, `every creating walk is found (${walks.length})`);
|
|
336
185
|
for (const [name, steps] of walks) {
|
|
337
186
|
const clicks = steps.filter((st) => st[0] === 'click').map((st) => st[1]);
|
|
338
|
-
|
|
339
|
-
assert.deepEqual(clicks.slice(0, door.length), door, `${name}: the tick, then a door, come first`);
|
|
187
|
+
assert.ok(clicks.indexOf('#termsAck') > -1 && clicks.indexOf('#termsAck') < clicks.indexOf('#doCreate'), `${name}: the tick comes before Create`);
|
|
340
188
|
}
|
|
341
189
|
});
|
|
@@ -59,8 +59,8 @@ test('both slices are still findable (a rename must fail loudly, not silently pa
|
|
|
59
59
|
assert.ok(offerSlice, 'paintResumeOffer() was not found in projects.html');
|
|
60
60
|
assert.match(enterSlice, /if \(focus && !resumeIntent\) \{/, 'the intent branch is part of the slice');
|
|
61
61
|
assert.match(enterSlice, /startProgress\(created\.id\);/, 'so is the resume branch it guards');
|
|
62
|
-
assert.match(enterSlice, /draft
|
|
63
|
-
'the STORED draft\'s step decides, not state.step — state.step is
|
|
62
|
+
assert.match(enterSlice, /if \(!\(draft && draft\.step === 1\)\) resetForNewProject\(\);/,
|
|
63
|
+
'the STORED draft\'s step decides, not state.step — state.step is 2 for the whole life of a finished standup');
|
|
64
64
|
// since task 1004549 a second draft in flight keeps its place on a non-deliberate
|
|
65
65
|
// arrival (tests/wizard_draft_resume.mjs slice 4); a deliberate nav still comes here
|
|
66
66
|
assert.match(SCRIPT, /if \(created && created\.id && \(focus \|\| !draftInFlight \|\| resumeIntent\)\) \{ enterCreated\(created, !!focus\); return; \}/,
|
|
@@ -76,7 +76,7 @@ function runEnter({ focus, resumeIntent = false, draft = null, record, handbackS
|
|
|
76
76
|
const calls = { show: [], resets: 0, offer: [], done: 0, progress: [], polls: 0, focused: 0, remembered: [] };
|
|
77
77
|
// state.step is the DONE step for the whole life of a finished standup — the value
|
|
78
78
|
// a draft-blind reading would have trusted
|
|
79
|
-
const state = { step: draft ? draft.step :
|
|
79
|
+
const state = { step: draft ? draft.step : 2, name: 'Mercury', slug: 'mercury', domain: '', mode: 'greenfield', noAddress: false, modules: null };
|
|
80
80
|
const sandbox = {
|
|
81
81
|
record, focus, resumeIntent, state,
|
|
82
82
|
DRAFT_KEY: 'draft',
|
|
@@ -104,7 +104,7 @@ const REC = { id: 'inst-1', name: 'Acme', slug: 'acme', domain: 'acme.example',
|
|
|
104
104
|
|
|
105
105
|
test('a reload / Back / identity settle (focus false) still resumes the done panel, watch and all', () => {
|
|
106
106
|
const { calls, state } = runEnter({ focus: false, record: REC });
|
|
107
|
-
assert.deepEqual(calls.show, [[
|
|
107
|
+
assert.deepEqual(calls.show, [[2, false]], 'the created page, part 03 live, and no focus steal on a non-deliberate arrival');
|
|
108
108
|
assert.equal(calls.resets, 0, 'nothing is reset — this is the same standup continuing');
|
|
109
109
|
assert.equal(calls.done, 1, 'the checklist is re-rendered');
|
|
110
110
|
assert.deepEqual(calls.progress, ['inst-1'], 'the progress watch is restarted from the record');
|
|
@@ -115,7 +115,7 @@ test('a reload / Back / identity settle (focus false) still resumes the done pan
|
|
|
115
115
|
|
|
116
116
|
test('a hand-back from the GitHub callback page resumes watching THAT attempt (task 1004548)', () => {
|
|
117
117
|
const { calls, sandbox } = runEnter({ focus: false, record: { ...REC }, handbackState: 'ab'.repeat(32) });
|
|
118
|
-
assert.deepEqual(calls.show, [[
|
|
118
|
+
assert.deepEqual(calls.show, [[2, false]], 'the founder lands on their checklist');
|
|
119
119
|
assert.equal(sandbox.manifestState, 'ab'.repeat(32), 'the sign-in step polls the attempt GitHub just approved');
|
|
120
120
|
assert.equal(sandbox.manifestPending, true, 'and the poll is queued for resumePolls()');
|
|
121
121
|
assert.equal(sandbox.live.manifest, 'code_received', 'it shows the wait, not "Set up GitHub sign-in" again');
|
|
@@ -126,13 +126,13 @@ test('a hand-back from the GitHub callback page resumes watching THAT attempt (t
|
|
|
126
126
|
assert.equal(done.sandbox.manifestPending, false, 'and nothing is polled for it');
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
test('a deliberate "New project" click after a finished standup opens a FRESH
|
|
129
|
+
test('a deliberate "New project" click after a finished standup opens a FRESH page', () => {
|
|
130
130
|
const { calls, state } = runEnter({ focus: true, record: REC });
|
|
131
131
|
assert.equal(calls.done, 0, 'the previous project\'s done panel is not painted');
|
|
132
132
|
assert.equal(calls.progress.length, 0, 'and its watch is not restarted');
|
|
133
133
|
assert.equal(calls.resets, 1, 'with no second draft to lose, the wizard forgets the finished project');
|
|
134
|
-
assert.deepEqual(calls.show, [[1, false]], '
|
|
135
|
-
assert.equal(calls.focused, 1, 'its first control, the
|
|
134
|
+
assert.deepEqual(calls.show, [[1, false]], 'the page, and it does not take the heading focus');
|
|
135
|
+
assert.equal(calls.focused, 1, 'its first control, the name, does — the deliberate-nav rule enter() already applies');
|
|
136
136
|
assert.equal(state.name, '', 'the old name is gone from the form');
|
|
137
137
|
});
|
|
138
138
|
|
|
@@ -145,7 +145,7 @@ test('…and the standup stays one click away, named, on the resume link', () =>
|
|
|
145
145
|
|
|
146
146
|
test('the resume link gets the done panel back — the record was never cleared', () => {
|
|
147
147
|
const { calls, sandbox } = runEnter({ focus: true, resumeIntent: true, record: REC });
|
|
148
|
-
assert.deepEqual(calls.show, [[
|
|
148
|
+
assert.deepEqual(calls.show, [[2, true]], 'a deliberate resume moves focus to part 03');
|
|
149
149
|
assert.deepEqual(calls.progress, ['inst-1'], 'the watch comes back with it');
|
|
150
150
|
assert.equal(sandbox.resumeIntent, false, 'the flag is ONE-SHOT — the next plain nav click must mean "new" again');
|
|
151
151
|
assert.deepEqual(calls.offer, [null], 'and the offer comes down behind it');
|
|
@@ -155,47 +155,27 @@ test('a second draft already under way is NOT wiped by navigating back to the wi
|
|
|
155
155
|
// The regression this guards: with the reset unconditional, typing a name, stepping
|
|
156
156
|
// away to "Your projects" and clicking "New project" again would blank the draft —
|
|
157
157
|
// trading the reported bug for a worse one.
|
|
158
|
-
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step:
|
|
158
|
+
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step: 1 } });
|
|
159
159
|
assert.equal(calls.resets, 0, 'a live draft is never reset');
|
|
160
|
-
assert.deepEqual(calls.show, [[
|
|
161
|
-
assert.equal(state.step,
|
|
160
|
+
assert.deepEqual(calls.show, [[1, false]], 'the second draft resumes on the page');
|
|
161
|
+
assert.equal(state.step, 1);
|
|
162
|
+
assert.equal(state.name, 'Mercury', 'with what was typed');
|
|
162
163
|
assert.deepEqual(calls.offer, ['Acme'], 'the offer stays up beside it');
|
|
163
164
|
});
|
|
164
165
|
|
|
165
|
-
test('the
|
|
166
|
-
// Caught in a render, not in a unit test: show(
|
|
166
|
+
test('the created page\'s own saved step is NOT a second draft — it must not block the reset', () => {
|
|
167
|
+
// Caught in a render, not in a unit test: show(2) calls saveDraft(), so DRAFT_KEY is
|
|
167
168
|
// non-empty again the moment a create lands. Reading "is there a draft?" left the
|
|
168
169
|
// finished project's name in the fresh form, under a line offering to resume it.
|
|
169
|
-
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step:
|
|
170
|
-
assert.equal(calls.resets, 1, 'the
|
|
171
|
-
assert.deepEqual(calls.show, [[1, false]], '
|
|
170
|
+
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step: 2, name: 'Mercury' } });
|
|
171
|
+
assert.equal(calls.resets, 1, 'the created step is bookkeeping, not a project being typed');
|
|
172
|
+
assert.deepEqual(calls.show, [[1, false]], 'a fresh page');
|
|
172
173
|
assert.equal(state.name, '', 'and the finished project\'s name does not survive into it');
|
|
173
|
-
assert.equal(calls.done, 0, 'never back onto the
|
|
174
|
+
assert.equal(calls.done, 0, 'never back onto the finished project\'s part 03');
|
|
174
175
|
});
|
|
175
176
|
|
|
176
|
-
test('a draft
|
|
177
|
-
|
|
178
|
-
// (task 1004417), 11 for the Look step (task 1004421), 4 for the six blended screens
|
|
179
|
-
// (task 1004504). A guard left behind would reset a second project sitting on its
|
|
180
|
-
// review, silently wiping it.
|
|
181
|
-
for (const step of [3, 4, 5]) {
|
|
182
|
-
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step } });
|
|
183
|
-
assert.equal(calls.resets, 0, `step ${step} is a live draft`);
|
|
184
|
-
assert.deepEqual(calls.show, [[step, true]]);
|
|
185
|
-
assert.equal(state.step, step);
|
|
186
|
-
}
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test('a draft on the fork or the first question is live too — a founder part-way through keeps their place (tasks 1004417, 1004504)', () => {
|
|
190
|
-
for (const step of [1, 2]) {
|
|
191
|
-
const { calls, state } = runEnter({ focus: true, record: REC, draft: { step } });
|
|
192
|
-
assert.equal(calls.resets, 0, `step ${step} is a live draft`);
|
|
193
|
-
assert.equal(state.step, step);
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('a draft whose step is missing or junk resets too — only 1-5 preserves anything', () => {
|
|
198
|
-
for (const draft of [{ step: 0 }, { step: 6 }, { name: 'half-typed' }]) {
|
|
177
|
+
test('a draft whose step is missing or junk resets too — only the page (step 1) preserves anything', () => {
|
|
178
|
+
for (const draft of [{ step: 0 }, { step: 2 }, { step: 6 }, { name: 'half-typed' }]) {
|
|
199
179
|
const { calls } = runEnter({ focus: true, record: REC, draft });
|
|
200
180
|
assert.equal(calls.resets, 1, `step ${JSON.stringify(draft.step)} is not a wizard step`);
|
|
201
181
|
assert.deepEqual(calls.show, [[1, false]]);
|
|
@@ -219,14 +199,14 @@ test('paintResumeOffer names the project by textContent and hides on null (no li
|
|
|
219
199
|
assert.doesNotMatch(offerSlice, /innerHTML/, 'this surface builds no live DOM from strings');
|
|
220
200
|
});
|
|
221
201
|
|
|
222
|
-
test('the offer ships as real markup
|
|
223
|
-
//
|
|
224
|
-
const panel = HUB.slice(HUB.indexOf('id="panel-
|
|
225
|
-
assert.ok(HUB.indexOf('id="panel-
|
|
202
|
+
test('the offer ships as real markup at the top of the page, hidden until there is something to resume', () => {
|
|
203
|
+
// the page is step 1 since task 1004648 — the offer sits above its heading
|
|
204
|
+
const panel = HUB.slice(HUB.indexOf('id="panel-page"'), HUB.indexOf('id="part-project"'));
|
|
205
|
+
assert.ok(HUB.indexOf('id="panel-page"') > -1 && HUB.indexOf('id="part-project"') > HUB.indexOf('id="panel-page"'), 'the page is findable');
|
|
226
206
|
assert.match(panel, /<p class="resumeOffer" id="wizResume" hidden>still watching <strong id="wizResumeName">your project<\/strong> — <a href="#" id="wizResumeGo">resume it<\/a><\/p>/,
|
|
227
|
-
'baked into the
|
|
228
|
-
assert.ok(panel.indexOf('id="wizResume"') < panel.indexOf('
|
|
229
|
-
'it sits above the
|
|
207
|
+
'baked into the page (C7), carrying its own hidden state');
|
|
208
|
+
assert.ok(panel.indexOf('id="wizResume"') < panel.indexOf('id="page-h"'),
|
|
209
|
+
'it sits above the heading, where a visitor reads it before anything else');
|
|
230
210
|
assert.equal((HUB.match(/id="wizResume"/g) || []).length, 1,
|
|
231
211
|
'and only once — a second copy would be a duplicate id');
|
|
232
212
|
assert.match(SCRIPT, /\$\('wizResumeGo'\)\.addEventListener\('click', function \(ev\) \{\n\s*ev\.preventDefault\(\);\n\s*resumeIntent = true;\n\s*enter\(true\);/,
|