@bongos/core 1.19.473 → 1.19.474

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.
@@ -0,0 +1,222 @@
1
+ // tests/hall_reference_world.mjs — the REFERENCE room in the chrome world:
2
+ // drachmae, and what the reading-room archetype owes a second document
3
+ // (task 1003473, goal 1000081).
4
+ //
5
+ // tests/hall_reading_world.mjs pins the reading rooms (the primer, the diagrams
6
+ // index, the deck, the ladder); this is the same job for the one document
7
+ // people SCAN rather than read. Everything here is a fact that is silent in a
8
+ // browser: the page still renders, it just renders the primer's instruments on
9
+ // a document that has no end to reach, or an outline that stops one level
10
+ // short of the questions people came with.
11
+ //
12
+ // What it locks:
13
+ // * ONE ROOM, ONE MODIFIER. drachmae opts into `room room--reference` and
14
+ // loads room.css alone. primer.css holds only the acknowledge gate and
15
+ // was loaded here for nothing; the reference rules live in room.css §8, so
16
+ // the next scanned document takes them by one body class.
17
+ // * THE LINEAR-READ INSTRUMENTS ARE OFF. No progress hairline, no section
18
+ // counters, no "About a five-minute read" — and the primer keeps all three.
19
+ // * THE OUTLINE GOES TO h3 DEPTH and lights the entry AND its part.
20
+ // * EVERY HEADING IS AN ADDRESS. `.room-anchor` on h2/h3/h4, a 24px box of
21
+ // its own, copying the absolute link on click; a deep link lands after the
22
+ // async render, or the browser's own jump finds nothing.
23
+ // * THE PARSER REACHES h4, and a heading that carries a link slugs by its
24
+ // text. The economy doc's six earning sources are `####`, and the shared
25
+ // reader stopped at `###`, so they rendered as paragraphs with a literal
26
+ // "####" — the largest defect on the page, and invisible to every floor.
27
+ // * NESTED EMPHASIS RENDERS. "**It's the API *list* price**" showed its
28
+ // asterisks because the bold rule refused an inner `*`.
29
+ // * THE LIVE TABLE IS A SECTION AHEAD OF THE CLOSING LINKS, not an appendix
30
+ // after "dig deeper".
31
+ //
32
+ // Reproduce the renders these came from (the populated document needs the
33
+ // fixture harness; the kit stub serves neither the markdown nor the bounty feed):
34
+ // node scripts/hall-preview/server.js --port 4620 --fixture-me
35
+ // node modules/ui-design/kit/render.js --states modules/hall-ui/public/drachmae.states.json \
36
+ // --base-in http://builders.localhost:4620 --base-out http://builders.localhost:4620
37
+
38
+ import { test } from 'node:test';
39
+ import assert from 'node:assert/strict';
40
+ import fs from 'node:fs';
41
+ import path from 'node:path';
42
+ import vm from 'node:vm';
43
+ import { fileURLToPath } from 'node:url';
44
+
45
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
46
+ const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
47
+ const read = (f) => fs.readFileSync(path.join(HALL, f), 'utf8');
48
+
49
+ const roomCss = read('room.css');
50
+ const mdReaderJs = read('md-reader.js');
51
+ const drachmaeHtml = read('drachmae.html');
52
+ const drachmaeJs = read('drachmae.js');
53
+ const drachmaeStates = JSON.parse(read('drachmae.states.json'));
54
+ const primerHtml = read('primer.html');
55
+ const primerJs = read('primer.js');
56
+
57
+ const decomment = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '');
58
+ const dehtmlcomment = (html) => html.replace(/<!--[\s\S]*?-->/g, '');
59
+ const dejscomment = (js) => js.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
60
+ // The body of the first rule matching `selector {`, comments stripped. Takes a
61
+ // PLAIN selector and escapes it here.
62
+ const rule = (css, selector) => {
63
+ const esc = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
64
+ const m = decomment(css).match(new RegExp(`(^|[},])\\s*${esc}\\s*\\{([^}]*)\\}`, 'm'));
65
+ return m ? m[2] : null;
66
+ };
67
+
68
+ // md-reader.js is a browser IIFE that only touches `window` and `location`.
69
+ function loadReader() {
70
+ const sandbox = { window: {}, location: { hostname: 'builders.example.com', href: 'https://builders.example.com/drachmae' } };
71
+ vm.createContext(sandbox);
72
+ vm.runInContext(mdReaderJs, sandbox, { filename: 'md-reader.js' });
73
+ return sandbox.window.OTBMdReader;
74
+ }
75
+
76
+ // ---- 1. one room, one modifier ----------------------------------------------
77
+
78
+ test('drachmae opts into the room and its reference rules, and loads the archetype sheet alone', () => {
79
+ assert.match(drachmaeHtml, /<body class="room room--reference"/, 'the reference is the room with one modifier, not a second archetype');
80
+ assert.match(drachmaeHtml, /href="\/builders\/room\.css"/, 'the archetype is loaded');
81
+ assert.doesNotMatch(dehtmlcomment(drachmaeHtml), /primer\.css/,
82
+ 'primer.css holds only the acknowledge gate, which a reference has no use for');
83
+ assert.match(drachmaeHtml, /class="page-head page-head--room"/, 'the head keeps the room voice');
84
+ });
85
+
86
+ test('the reference rules live in room.css, keyed on the body modifier', () => {
87
+ const css = decomment(roomCss);
88
+ assert.match(css, /\.room--reference \.primer-article h2::before,\s*\.room--reference \.primer-toc__list a::before\s*\{\s*content:\s*none/,
89
+ 'the counters are off on a reference');
90
+ assert.ok(rule(roomCss, '.room--reference .primer-toc__sub'), 'the nested contents have a rule');
91
+ assert.ok(rule(roomCss, '.room--reference .primer-toc__list a.is-trail'), 'the part of the lit entry is the trail');
92
+ assert.match(rule(roomCss, '.primer-article h2::before') || '', /counter-increment/,
93
+ 'and the primer keeps its counter — the modifier turns it off, the archetype still draws it');
94
+ });
95
+
96
+ // ---- 2. the linear-read instruments are off ---------------------------------
97
+
98
+ test('the reference carries no progress hairline, no acknowledge gate and no read time', () => {
99
+ const html = dehtmlcomment(drachmaeHtml);
100
+ assert.doesNotMatch(html, /primer-progress/, 'no progress hairline: a reference has no end to reach');
101
+ assert.doesNotMatch(dejscomment(drachmaeJs), /primer-progress|setupProgressBar/, 'and the script drives none');
102
+ assert.doesNotMatch(html, /primer-ack/, 'no acknowledge gate');
103
+ assert.doesNotMatch(html, /minute read/i, 'the sub line says what kind of document this is, not how long it takes');
104
+ });
105
+
106
+ test('the primer keeps all three — the modifier is drachmae\'s, not the room\'s', () => {
107
+ assert.doesNotMatch(primerHtml, /room--reference/);
108
+ assert.match(primerHtml, /primer-progress__bar/);
109
+ assert.match(primerJs, /function setupProgressBar/, 'the primer\'s gate is fed by its progress');
110
+ });
111
+
112
+ // ---- 3. the outline -----------------------------------------------------------
113
+
114
+ test('the outline is read from the rendered h2s AND h3s and nests them', () => {
115
+ assert.match(drachmaeJs, /querySelectorAll\('h2\[id\], h3\[id\]'\)/, 'the contents are collected from the DOM, to h3 depth');
116
+ assert.match(drachmaeJs, /class="primer-toc__sub"/, 'h3s nest under their part');
117
+ assert.match(drachmaeJs, /classList\.toggle\('is-trail'/, 'the spy marks the lit entry\'s part');
118
+ assert.match(drachmaeHtml, /id="primer-toc-current"/, 'the phone toggle has a middle that names the part');
119
+ assert.match(drachmaeJs, /current\.textContent = active \? active\.part : ''/, 'and the spy writes it');
120
+ });
121
+
122
+ test('the toggle\'s chevron is drawn, and the contents are named for a reference', () => {
123
+ assert.doesNotMatch(dehtmlcomment(drachmaeHtml), /▾/, 'no glyph standing in for an icon');
124
+ assert.match(drachmaeHtml, /<svg class="primer-toc__chevron"/);
125
+ assert.match(drachmaeHtml, /id="primer-toc-heading">On this page</, 'a reference is "on this page", not "in this guide"');
126
+ });
127
+
128
+ // ---- 4. every heading is an address -------------------------------------------
129
+
130
+ test('every h2, h3 and h4 gets an anchor that is a real link and copies the address', () => {
131
+ assert.match(drachmaeJs, /querySelectorAll\('h2\[id\], h3\[id\], h4\[id\]'\)/);
132
+ assert.match(drachmaeJs, /className = 'room-anchor'/);
133
+ assert.match(drachmaeJs, /a\.href = `#\$\{h\.id\}`/, 'the anchor is the heading\'s own id, so it works with no script');
134
+ assert.match(drachmaeJs, /navigator\.clipboard\.writeText\(url\)/, 'the click copies the absolute address');
135
+ const anchor = rule(roomCss, '.room--reference .primer-article .room-anchor');
136
+ assert.ok(anchor, 'the anchor has its own rule, scoped to the reference, at higher specificity than the prose link');
137
+ assert.match(anchor, /width:\s*24px/); assert.match(anchor, /height:\s*24px/);
138
+ assert.match(anchor, /text-decoration:\s*none/);
139
+ assert.ok(rule(roomCss, '.room--reference .primer-article :is(:target, .is-target) > .room-anchor'),
140
+ 'the one the reader arrived at takes the accent — by :target, or by the mark the landing sets when :target resolved against nothing');
141
+ });
142
+
143
+ test('a deep link lands after the async render, the live section and the fonts', () => {
144
+ assert.match(drachmaeJs, /function landOnHash\(\)[\s\S]*classList\.add\('is-target'\)[\s\S]*scrollIntoView/);
145
+ const src = dejscomment(drachmaeJs);
146
+ const inserted = src.indexOf('await insertBountySection(article)');
147
+ const landed = src.indexOf('.then(landOnHash)');
148
+ assert.ok(inserted > -1 && landed > inserted, 'landOnHash runs once the rate card has taken its place, or the offsets it lands on are wrong');
149
+ assert.match(src, /document\.fonts\.ready[\s\S]*\.then\(landOnHash\)/, 'and after the fonts: a swap after the jump moves everything above the target');
150
+ });
151
+
152
+ test('on a phone the collapsed outline rides under the top bar, so the toggle can name the part from anywhere', () => {
153
+ const m = decomment(roomCss).match(/@media \(max-width: 760px\)\s*\{\s*\.room--reference \.primer-toc\s*\{([^}]*)\}/);
154
+ assert.ok(m, 'the reference re-sticks the contents at phone width (the archetype makes them static there)');
155
+ assert.match(m[1], /position:\s*sticky/);
156
+ assert.match(m[1], /z-index:\s*var\(--z-progress\)/, 'below the top bar in the ladder');
157
+ });
158
+
159
+ // ---- 5. the live table --------------------------------------------------------
160
+
161
+ test('the live rate card is a section ahead of the closing links, not an appendix after them', () => {
162
+ assert.match(drachmaeJs, /CLOSING_H2 = \/\^see it\/i/);
163
+ assert.match(drachmaeJs, /article\.insertBefore\(section, closing\)/);
164
+ assert.match(drachmaeJs, /else article\.appendChild\(section\)/, 'a retitled footer falls back to appending');
165
+ });
166
+
167
+ // ---- 6. the parser, actually run ----------------------------------------------
168
+
169
+ test('the shared parser reaches h4 — the six earning sources rendered as paragraphs with a literal ####', () => {
170
+ const MD = loadReader();
171
+ const { html, headings } = MD.parseMarkdown('#### 4. Achievements\n\nUnlocking one pays its bonus.\n', (h) => h);
172
+ assert.equal(headings.length, 1);
173
+ assert.equal(headings[0].level, 4);
174
+ assert.match(html, /<h4 id="4-achievements">4\. Achievements<\/h4>/);
175
+ assert.doesNotMatch(html, /####/);
176
+ });
177
+
178
+ test('a paragraph breaks before an h4 the way it breaks before an h3', () => {
179
+ const MD = loadReader();
180
+ const { html } = MD.parseMarkdown('a line\n#### A head\n', (h) => h);
181
+ assert.match(html, /<p>a line<\/p>\s*<h4/);
182
+ });
183
+
184
+ test('a heading that carries a link slugs by its text, so the copied address stays readable', () => {
185
+ const MD = loadReader();
186
+ const withLink = '2. Per-task credits, adjusted by *kind* ([ADR 0023](../adr/0023-kind-multipliers-and-idea-bonus.md))';
187
+ assert.equal(MD.slugify(withLink), MD.slugify('2. Per-task credits, adjusted by *kind* (ADR 0023)'));
188
+ assert.doesNotMatch(MD.slugify(withLink), /adr0023|md$/, 'the target path is not part of the address');
189
+ });
190
+
191
+ test('emphasis inside a bold run renders — the API list-price line showed its asterisks', () => {
192
+ const MD = loadReader();
193
+ const { html } = MD.parseMarkdown('- **It is the API *list* price, deliberately** and not the rest\n', (h) => h);
194
+ assert.match(html, /<strong>It is the API <em>list<\/em> price, deliberately<\/strong> and not the rest/);
195
+ const two = MD.parseMarkdown('**a** and **b**\n', (h) => h).html;
196
+ assert.match(two, /<strong>a<\/strong> and <strong>b<\/strong>/, 'and two bold runs on one line stay two');
197
+ });
198
+
199
+ // ---- 7. the room's h4 -----------------------------------------------------------
200
+
201
+ test('the room gives the fourth level a rule, the measure and the anchor offset', () => {
202
+ const h4 = rule(roomCss, '.primer-article h4');
203
+ assert.ok(h4, 'room.css styles h4 now that the parser emits it');
204
+ assert.match(h4, /scroll-margin-top/, 'a deep link to an h4 clears the top bar');
205
+ const measured = decomment(roomCss).match(/\.primer-article\s*>\s*p,[\s\S]*?\{([^}]*)\}/);
206
+ assert.ok(measured, 'the measured-children list exists');
207
+ assert.match(measured[0], /\.primer-article\s*>\s*h4/, 'and the h4 is measured with the prose');
208
+ });
209
+
210
+ // ---- 8. the states file and the one h1 ------------------------------------------
211
+
212
+ test('the states file measures the reference\'s own states, including the deep link', () => {
213
+ for (const name of ['read', 'toc-open', 'jump', 'out']) assert.ok(drachmaeStates.states[name], `state ${name}`);
214
+ assert.match(drachmaeStates.states.jump.url, /#how-karma-is-earned-and-lost$/, 'the jump lands on an h3, so the trail is what the shot proves');
215
+ assert.ok(drachmaeStates.reducedMotion, 'the kill switch is exercised');
216
+ });
217
+
218
+ test('the reference renders exactly one h1, the page head\'s', () => {
219
+ const html = dehtmlcomment(drachmaeHtml);
220
+ assert.equal([...html.matchAll(/<h1\b/g)].length, 1);
221
+ assert.match(html, /<h1 class="page-head__h">/);
222
+ });