@bongos/core 1.19.469 → 1.19.470
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 +27 -22
- package/docs/copy-inventory.md +40 -43
- package/docs/copy-registry.json +155 -182
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/atlas.css +96 -88
- package/modules/hall-ui/public/atlas.html +43 -20
- package/modules/hall-ui/public/atlas.js +118 -20
- package/modules/hall-ui/public/atlas.states.json +52 -1
- package/modules/hall-ui/public/modules.css +67 -86
- package/modules/hall-ui/public/modules.html +46 -35
- package/modules/hall-ui/public/modules.js +195 -103
- package/modules/hall-ui/public/modules.states.json +48 -1
- package/modules/hall-ui/public/style.css +8 -8
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/hall_catalog_world.mjs +181 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// tests/hall_catalog_world.mjs — the catalog pages in the chrome world (task
|
|
2
|
+
// 1003472, goal 1000081): modules and the repo atlas, REDESIGNED rather than
|
|
3
|
+
// re-skinned. Static pins over the shipped source: the module catalog is ONE
|
|
4
|
+
// panel of the oversight family's rows (the card grid, the boxed search and
|
|
5
|
+
// the .mod-chip alias are gone), the description is clamped with a details
|
|
6
|
+
// disclosure under it, the right column is the family's grant toggle or a
|
|
7
|
+
// state pill and never an accent fill, the submit door sits on the row, and
|
|
8
|
+
// the figure band carries the numbers the page head used to say as prose; the
|
|
9
|
+
// atlas keeps its nine semantic colours and puts every piece of chrome around
|
|
10
|
+
// the scene in the shell's dialect — the panel is a .scroll, the view switch
|
|
11
|
+
// the tab strip, the detail card a hairline panel with no shadow — and gains
|
|
12
|
+
// an island index a keyboard or a phone can open.
|
|
13
|
+
import test from 'node:test';
|
|
14
|
+
import assert from 'node:assert/strict';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
|
+
const PUB = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
21
|
+
const read = (f) => fs.readFileSync(path.join(PUB, f), 'utf8').replace(/\r\n/g, '\n');
|
|
22
|
+
// Negative pins read the code, not its comments (a comment that says what left
|
|
23
|
+
// would otherwise fail the pin that says it left).
|
|
24
|
+
const decomment = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/<!--[\s\S]*?-->/g, '').replace(/^\s*\/\/.*$/gm, '');
|
|
25
|
+
const STYLE = read('style.css');
|
|
26
|
+
const OVERSIGHT = read('oversight.css');
|
|
27
|
+
const tokensDefined = (css, where) => {
|
|
28
|
+
for (const t of new Set([...css.matchAll(/var\((--[a-z0-9-]+)\)/g)].map((m) => m[1]))) {
|
|
29
|
+
const declared = (src) => new RegExp(`(^\\s*|[{;]\\s*)${t}:`, 'm').test(src);
|
|
30
|
+
assert.ok(declared(STYLE) || declared(OVERSIGHT) || declared(css),
|
|
31
|
+
`${where} spends ${t}, which neither style.css, oversight.css nor the sheet itself defines`);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const noAlias = (rawCss, where) => {
|
|
35
|
+
const css = decomment(rawCss);
|
|
36
|
+
const alias = css.match(/--(surface|surface-2|border|border-strong|accent-weak|accent-text)\b/g) || [];
|
|
37
|
+
assert.deepEqual(alias, [], `${where} spends a compat alias: ${alias.join(', ')}`);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// ---- modules ----------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
test('modules: ONE panel of family rows — the filter bar, the figure band and two groups on one .scroll', () => {
|
|
43
|
+
const html = read('modules.html');
|
|
44
|
+
assert.match(html, /<link rel="stylesheet" href="\/builders\/oversight\.css">\s*<link rel="stylesheet" href="\/builders\/modules\.css">/, 'the family sheet loads before the page sheet');
|
|
45
|
+
assert.equal((html.match(/class="scroll[ "]/g) || []).length, 1, 'one panel, not three');
|
|
46
|
+
assert.match(html, /<div id="mod-filters"><\/div>/, 'the kit filter bar has a mount');
|
|
47
|
+
assert.match(html, /class="ov-figs" id="mod-figs"/, 'the figure band is the family\'s');
|
|
48
|
+
assert.match(html, /<ul class="ov-rows" id="core-grid">/, 'the core catalog is a run of rows');
|
|
49
|
+
assert.match(html, /<ul class="ov-rows" id="own-grid">/, 'the project\'s own modules are a run of rows');
|
|
50
|
+
assert.equal((html.match(/class="ov-group__head"/g) || []).length, 2, 'two group heads');
|
|
51
|
+
assert.doesNotMatch(decomment(html), /mod-grid|mod-card|mod-browse|submit-scroll|class="settings-status"/, 'the card grid, the boxed search and the third panel are gone');
|
|
52
|
+
assert.doesNotMatch(html, /\d+ module\(s\)/, 'the page head no longer says the numbers as prose');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('modules: the row is the oversight row — title with the mono key, clamped description, facts, one control on the right', () => {
|
|
56
|
+
const js = read('modules.js');
|
|
57
|
+
assert.match(js, /<li class="ov-row mod-row" data-key=/, 'each module is an .ov-row');
|
|
58
|
+
assert.match(js, /<span class="ov-row__title">\$\{escapeHtml\(m\.title \|\| m\.key\)\}<span class="ov-mono">\$\{escapeHtml\(m\.key\)\}<\/span>/, 'the key rides inside the title in mono');
|
|
59
|
+
assert.match(js, /class="ov-row__sub mod-row__desc"/, 'the description is the sub line');
|
|
60
|
+
assert.match(js, /<details class="mod-more"/, 'a details disclosure opens the rest');
|
|
61
|
+
assert.match(js, /<div class="ov-row__facts">\$\{provenancePill\(m\)\}\$\{attributionFacts\(m\)\}\$\{upkeepFact\(m\)\}<\/div>/, 'provenance, credit and upkeep are the facts line');
|
|
62
|
+
assert.match(js, /<div class="ov-row__right mod-row__right">\$\{rightHtml\(m\)\}<\/div>/, 'the right column is one function');
|
|
63
|
+
const css = read('modules.css');
|
|
64
|
+
assert.match(css, /\.mod-row__desc \{[^}]*-webkit-line-clamp: 2/, 'the description clamps to two lines');
|
|
65
|
+
assert.match(css, /\.mod-row:has\(\.mod-more\[open\]\) \.mod-row__desc/, 'and lifts the clamp while the details are open');
|
|
66
|
+
assert.doesNotMatch(decomment(js), /class="mod-chip|innerHTML = .*style=|<span style=/, 'no .mod-chip, no inline style');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('modules: the switch is the family grant toggle or a state pill — never an accent fill, never a dead control', () => {
|
|
70
|
+
const js = read('modules.js');
|
|
71
|
+
assert.match(js, /class="ov-toggle mod-toggle" aria-pressed=/, 'the toggle is .ov-toggle with aria-pressed');
|
|
72
|
+
assert.match(js, /: `<span class="fact-pill \$\{on \? 'fact-pill--ok' : ''\}"/, 'a viewer below the floor sees the state as a pill');
|
|
73
|
+
assert.match(js, /rankLabel\('metic'\)/, 'the floor is written from the pack');
|
|
74
|
+
assert.doesNotMatch(js, /Metic\+/, 'and never as a hardcoded rank');
|
|
75
|
+
const css = decomment(read('modules.css'));
|
|
76
|
+
assert.doesNotMatch(css, /background:\s*var\(--accent\)/, 'no accent fill on a state');
|
|
77
|
+
assert.doesNotMatch(css, /button\.mod-toggle|\[data-on=/, 'the old accent-filled toggle is gone');
|
|
78
|
+
assert.match(js, /class="btn-ghost btn--sm mod-submit" data-submit-key=/, 'the submit door is a ghost pill on the row');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('modules: upkeep colours the exception — undeclared is the warn pill, maintained a plain fact', () => {
|
|
82
|
+
const js = read('modules.js');
|
|
83
|
+
assert.match(js, /fact-pill fact-pill--warn"[^>]*>upkeep undeclared</, 'undeclared upkeep is the warn pill');
|
|
84
|
+
assert.match(js, /<span class="ov-fact">upkeep <b>\$\{escapeHtml\(status\)\}<\/b><\/span>/, 'a maintained module reads as a plain fact');
|
|
85
|
+
assert.match(js, /fact-pill fact-pill--info">core-loaded</, 'core-loaded is the info pill');
|
|
86
|
+
assert.match(js, /<span class="fact-chip">\$\{escapeHtml\(m\.license\)\}<\/span>/, 'the licence is a chip');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('modules: the kit filter bar carries the search and a state lens, and the empty state is the kit\'s', () => {
|
|
90
|
+
const js = read('modules.js');
|
|
91
|
+
assert.match(js, /kit\.makeFilterBar\(\{/, 'the bar is the kit\'s');
|
|
92
|
+
assert.match(js, /search: \{ id: 'mod-search'/, 'the search keeps its id (the states file fills it)');
|
|
93
|
+
assert.match(js, /\{ id: 'mod-state', label: 'State'/, 'a state lens');
|
|
94
|
+
assert.match(js, /kit\.emptyStateHtml\(/, 'empties are the kit\'s');
|
|
95
|
+
assert.doesNotMatch(decomment(js), /mod-empty/, '.mod-empty is gone');
|
|
96
|
+
const html = read('modules.html');
|
|
97
|
+
assert.match(html, /<script src="\/builders\/hall-kit\.js"><\/script>/, 'and the page loads the kit');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('modules: the figure band carries modules, enabled, built here and undeclared upkeep', () => {
|
|
101
|
+
const js = read('modules.js');
|
|
102
|
+
for (const label of ['modules', 'enabled', 'built here', 'no upkeep']) {
|
|
103
|
+
assert.match(js, new RegExp(`fig\\('${label}'`), `figure: ${label}`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('modules.css spends only the fifteen, and every token it names exists', () => {
|
|
108
|
+
const css = read('modules.css');
|
|
109
|
+
noAlias(css, 'modules.css');
|
|
110
|
+
tokensDefined(css, 'modules.css');
|
|
111
|
+
assert.doesNotMatch(decomment(css), /#[0-9a-f]{3,8}\b/i, 'no literal colours');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('the shell no longer aliases .mod-chip onto the pill', () => {
|
|
115
|
+
const alias = STYLE.slice(STYLE.indexOf('.fact-pill,\n'), STYLE.indexOf('{', STYLE.indexOf('.fact-pill,\n')));
|
|
116
|
+
assert.doesNotMatch(alias, /mod-chip/, 'modules takes .fact-pill by name');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// ---- atlas ------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
test('atlas: the nine semantic colours stay fixed literals, and nothing else in the sheet is one', () => {
|
|
122
|
+
const css = read('atlas.css');
|
|
123
|
+
for (const t of ['--ab-sun', '--ab-sky', '--ab-coral', '--ab-paper', '--ab-good', '--ab-core', '--ab-db', '--ab-ghost', '--ab-danger']) {
|
|
124
|
+
assert.match(css, new RegExp(`${t}:#[0-9a-f]{6}`), `${t} is a fixed literal (ADR 0094)`);
|
|
125
|
+
}
|
|
126
|
+
const rest = decomment(css).replace(/--ab-(sun|sky|coral|paper|good|core|db|ghost|danger):#[0-9a-f]{6}/g, '');
|
|
127
|
+
assert.deepEqual(rest.match(/#[0-9a-f]{3,8}\b/gi) || [], [], 'no other literal colour');
|
|
128
|
+
noAlias(css, 'atlas.css');
|
|
129
|
+
tokensDefined(css, 'atlas.css');
|
|
130
|
+
assert.doesNotMatch(rest, /box-shadow/, 'no box-shadow: the world draws depth with hairlines');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('atlas: the panel is the shell\'s .scroll and the view switch is the shell\'s tab strip', () => {
|
|
134
|
+
const html = read('atlas.html');
|
|
135
|
+
assert.match(html, /<section class="scroll atlas-page"/, 'the map sits on a .scroll');
|
|
136
|
+
assert.match(html, /<div class="kit-tabs ab-views" role="tablist"/, 'the view switch is .kit-tabs');
|
|
137
|
+
assert.equal((html.match(/class="setup__tab" role="tab" data-view=/g) || []).length, 2, 'two tabs');
|
|
138
|
+
assert.doesNotMatch(decomment(html), /ab-modes|ab-chip"|class="card"/, 'the segmented box, the live chip and the empty card are gone');
|
|
139
|
+
const css = decomment(read('atlas.css'));
|
|
140
|
+
assert.doesNotMatch(css, /\.ab-modes|\.ab-chip\b|radial-gradient|\.ab-empty \.card/, 'and their rules with them');
|
|
141
|
+
const js = read('atlas.js');
|
|
142
|
+
assert.match(js, /setAttribute\('aria-selected'/, 'the script switches the tabs by aria-selected');
|
|
143
|
+
assert.doesNotMatch(decomment(js), /classList\.toggle\('active'/, 'not by an .active class');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('atlas: the detail card is a hairline panel whose facts are the shell\'s pills and chips, and a neighbour is a door', () => {
|
|
147
|
+
const css = read('atlas.css');
|
|
148
|
+
assert.match(css, /\.ab-popup\{[^}]*border:var\(--line\) solid var\(--rule\)/, 'the card closes on one rule hairline');
|
|
149
|
+
const js = read('atlas.js');
|
|
150
|
+
assert.match(js, /fact-pill fact-pill--' \+ \(PILL\[n\.badge\.k\]/, 'the badge is a status pill');
|
|
151
|
+
assert.match(js, /<span class="fact-chip">' \+ esc\(n\.metric\)/, 'the metric is a chip');
|
|
152
|
+
assert.match(js, /<button type="button" class="fact-chip ab-jump" data-node=/, 'a connected island is a button');
|
|
153
|
+
assert.doesNotMatch(decomment(js), /ab-chip2|ab-badge ab-b-|↔/, 'the glass chips, the private badges and the arrow glyph are gone');
|
|
154
|
+
assert.match(js, /ev\.key === 'Escape'/, 'Escape closes the card');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('atlas: the island index renders every node of the view as a row a keyboard can open', () => {
|
|
158
|
+
const html = read('atlas.html');
|
|
159
|
+
assert.match(html, /<details class="kit-collapse ab-index" id="ab-index">/, 'the index is a collapsible under the map');
|
|
160
|
+
assert.match(html, /<ul class="ov-rows" id="ab-index-rows"><\/ul>/, 'its rows are the family\'s');
|
|
161
|
+
const js = read('atlas.js');
|
|
162
|
+
assert.match(js, /function drawIndex\(d\)/, 'the index is drawn per view');
|
|
163
|
+
assert.match(js, /<li class="ov-row ov-row--dense ab-ix" data-node=/, 'each island is a dense row');
|
|
164
|
+
assert.match(js, /class="ov-row__title ab-ix__t" aria-expanded="false" aria-controls=/, 'the title is a disclosure button');
|
|
165
|
+
assert.match(js, /body\.innerHTML = detailHTML\(nodeById\(id\)\)/, 'and opens the same detail the map\'s card shows');
|
|
166
|
+
assert.match(js, /matchMedia\('\(max-width: 640px\)'\)\.matches\) indexEl\.open = true/, 'open by default on a phone, where the index is the read');
|
|
167
|
+
assert.match(js, /drawIndex\(d\);/, 'drawn on every render');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('atlas: the hub breathes at the world\'s ambient floor, not as a pulse', () => {
|
|
171
|
+
const css = read('atlas.css');
|
|
172
|
+
const m = css.match(/animation:abbreathe (\d+)s/);
|
|
173
|
+
assert.ok(m && Number(m[1]) >= 20, 'the loop is 20s or slower');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('the states files exercise the redesign\'s new states', () => {
|
|
177
|
+
const mod = JSON.parse(read('modules.states.json'));
|
|
178
|
+
for (const s of ['browse', 'search-empty', 'lens', 'details', 'out']) assert.ok(mod.states[s], `modules: ${s}`);
|
|
179
|
+
const atlas = JSON.parse(read('atlas.states.json'));
|
|
180
|
+
for (const s of ['map', 'truth', 'detail', 'index', 'out']) assert.ok(atlas.states[s], `atlas: ${s}`);
|
|
181
|
+
});
|