@bongos/core 1.24.59 → 1.24.60
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 +12 -12
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/bongos/css-literal-scan.js +72 -14
- package/src/module-api.js +1 -1
- package/tests/design_counts_ratchet.mjs +59 -2
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/bongos/package-core.js",
|
|
5
|
-
"core_version": "1.24.
|
|
6
|
-
"core_contract": "1.24.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.24.60",
|
|
6
|
+
"core_contract": "1.24.60",
|
|
7
|
+
"source_commit": "fef925d217da9148be0808c73ee46288043dc2de",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-11-07T15:
|
|
9
|
+
"built_at": "2026-11-07T15:39:07.882Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 638,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 4571,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "02fb38f9111b66794f346c5bb82422a3f315f456b1c9d73a5b3eb1c136097fc1",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -3007,7 +3007,7 @@
|
|
|
3007
3007
|
{
|
|
3008
3008
|
"path": "docs/module-api-changelog.md",
|
|
3009
3009
|
"mode": "0000644",
|
|
3010
|
-
"sha256": "
|
|
3010
|
+
"sha256": "71913622d0d94c4120e620d5aea32ff476a6e879c5e166da3144eedc75619dda"
|
|
3011
3011
|
},
|
|
3012
3012
|
{
|
|
3013
3013
|
"path": "docs/modules-contract.md",
|
|
@@ -11902,12 +11902,12 @@
|
|
|
11902
11902
|
{
|
|
11903
11903
|
"path": "package-lock.json",
|
|
11904
11904
|
"mode": "0000644",
|
|
11905
|
-
"sha256": "
|
|
11905
|
+
"sha256": "dfea60d54c9a25b94f32d02e56aecb552db65f0a68d3ca9470db04acbd8b3fd3"
|
|
11906
11906
|
},
|
|
11907
11907
|
{
|
|
11908
11908
|
"path": "package.json",
|
|
11909
11909
|
"mode": "0000644",
|
|
11910
|
-
"sha256": "
|
|
11910
|
+
"sha256": "fe9083b4b86df7ab6427311717f99a1bba6d8c7e079e2f37c32b4722a3bc4186"
|
|
11911
11911
|
},
|
|
11912
11912
|
{
|
|
11913
11913
|
"path": "public-docs/index.html",
|
|
@@ -11927,7 +11927,7 @@
|
|
|
11927
11927
|
{
|
|
11928
11928
|
"path": "release-notes.json",
|
|
11929
11929
|
"mode": "0000644",
|
|
11930
|
-
"sha256": "
|
|
11930
|
+
"sha256": "2735b7f5e5d4c0bf2b24fc78d79738c5b57a9eaf4734823a79e67ea8e0fb39b2"
|
|
11931
11931
|
},
|
|
11932
11932
|
{
|
|
11933
11933
|
"path": "scripts/bongos/CLAUDE.md",
|
|
@@ -12267,7 +12267,7 @@
|
|
|
12267
12267
|
{
|
|
12268
12268
|
"path": "scripts/bongos/css-literal-scan.js",
|
|
12269
12269
|
"mode": "0000644",
|
|
12270
|
-
"sha256": "
|
|
12270
|
+
"sha256": "1dbcdb46cfd195c1080bdd37bc8337b0926cd5f6e281cd131656801f6c92b236"
|
|
12271
12271
|
},
|
|
12272
12272
|
{
|
|
12273
12273
|
"path": "scripts/bongos/cutover-codemod.js",
|
|
@@ -15877,7 +15877,7 @@
|
|
|
15877
15877
|
{
|
|
15878
15878
|
"path": "src/module-api.js",
|
|
15879
15879
|
"mode": "0000644",
|
|
15880
|
-
"sha256": "
|
|
15880
|
+
"sha256": "66eed53ac99b04f6c4c0b5e33b13b44c2b1d53239198bf8b41fe13a454d866d9"
|
|
15881
15881
|
},
|
|
15882
15882
|
{
|
|
15883
15883
|
"path": "src/module-loader/catalog.js",
|
|
@@ -17412,7 +17412,7 @@
|
|
|
17412
17412
|
{
|
|
17413
17413
|
"path": "tests/design_counts_ratchet.mjs",
|
|
17414
17414
|
"mode": "0000644",
|
|
17415
|
-
"sha256": "
|
|
17415
|
+
"sha256": "3e7457685c915d01d11ba4dd1481e726f82c83a3ce481f5190893e4b0636b7bc"
|
|
17416
17416
|
},
|
|
17417
17417
|
{
|
|
17418
17418
|
"path": "tests/design_reference.mjs",
|
|
@@ -3372,5 +3372,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3372
3372
|
landed since 1.24.57 with no explicit bump. run 42380593295. (task 1002620)
|
|
3373
3373
|
1.24.59 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3374
3374
|
landed since 1.24.58 with no explicit bump. run 42385816081. (task 1002620)
|
|
3375
|
+
1.24.60 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3376
|
+
landed since 1.24.59 with no explicit bump. run 42387285187. (task 1002620)
|
|
3375
3377
|
---------------------------------------------------------------------------
|
|
3376
3378
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.60",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.60",
|
|
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.24.
|
|
3
|
+
"version": "1.24.60",
|
|
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
|
@@ -11329,5 +11329,11 @@
|
|
|
11329
11329
|
"id": "1005133",
|
|
11330
11330
|
"text": "The logo files and the link-preview pictures are now checked automatically, so a logo change can no longer ship with out-of-date copies; the docs now say plainly that the off-site status page is updated by hand."
|
|
11331
11331
|
}
|
|
11332
|
+
],
|
|
11333
|
+
"1.24.60": [
|
|
11334
|
+
{
|
|
11335
|
+
"id": "1005134",
|
|
11336
|
+
"text": "The automatic check that stops pages from typing in their own colours and sizes now also catches colours given a custom name and styles written directly inside a page, so those shortcuts can no longer slip new colours past it."
|
|
11337
|
+
}
|
|
11332
11338
|
]
|
|
11333
11339
|
}
|
|
@@ -21,16 +21,28 @@
|
|
|
21
21
|
// tokens; the count stops the set of breakpoints sprawling).
|
|
22
22
|
// Sizes, radii, shadows and breakpoints are counted per DECLARATION / RULE.
|
|
23
23
|
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
24
|
+
// CUSTOM PROPERTIES (task 1005134). A colour typed into a custom-property
|
|
25
|
+
// declaration (`:root{--mine:#123456}`, `body{--x:rgb(…)}`) COUNTS as a colour,
|
|
26
|
+
// so a new sheet cannot dodge its zero start by naming its colours. Only the
|
|
27
|
+
// shared token definitions are exempt, because they are exactly where literals
|
|
28
|
+
// belong: the whole of the shared token sheet and the hall's token block (its
|
|
29
|
+
// :root and the two byte-identical dark twins) — TOKEN_SHEETS. The pack splice
|
|
30
|
+
// (serve-internal.js themeMarkup) is built in JavaScript at serve time and is
|
|
31
|
+
// never scanned. A custom property's sizes/radii/shadows are not counted — those
|
|
32
|
+
// kinds are read off the property that SPENDS a value, and a --x is not one.
|
|
33
|
+
// Page-private palettes are also guarded by tests/hall_private_palettes.mjs.
|
|
34
|
+
//
|
|
35
|
+
// WHAT IT DOES NOT COUNT. Custom-property NAMES are removed from a value before
|
|
36
|
+
// it is read (so --ds-space-12 is not a "12"), and url(...) bodies are emptied (a
|
|
37
|
+
// data URI is not a colour). A literal inside a var() fallback DOES count: a
|
|
38
|
+
// fallback is a typed-in value.
|
|
31
39
|
//
|
|
32
40
|
// Scope: every tracked *.css outside docs/ (the design mocks there are not
|
|
33
|
-
// served), node_modules and vendor
|
|
41
|
+
// served), node_modules and vendor/; the inline <style> blocks of every served
|
|
42
|
+
// .html (one under a public/, public-docs/, views/ or content/ folder); and the
|
|
43
|
+
// <style> blocks a server file writes as a string (STYLE_STRING_SOURCES: the
|
|
44
|
+
// branded error page). Task 1005134: a page could otherwise type its colours into
|
|
45
|
+
// a <style> tag and never be counted.
|
|
34
46
|
//
|
|
35
47
|
// Print the counts: node scripts/bongos/css-literal-scan.js [--all]
|
|
36
48
|
|
|
@@ -42,6 +54,18 @@ const ROOT = path.resolve(__dirname, '..', '..');
|
|
|
42
54
|
const KINDS = ['colours', 'font_sizes', 'radii', 'shadows', 'breakpoints'];
|
|
43
55
|
|
|
44
56
|
const EXCLUDE_RE = /^docs\/|(^|\/)(node_modules|vendor)\//;
|
|
57
|
+
// A served page: an .html under a folder the server serves or renders from.
|
|
58
|
+
const SERVED_HTML_RE = /(^|\/)(public|public-docs|views|content)\/.*\.html$/;
|
|
59
|
+
// Server files that write a page's <style> block as a string.
|
|
60
|
+
const STYLE_STRING_SOURCES = ['src/error-page.js'];
|
|
61
|
+
const STYLE_BLOCK_RE = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
62
|
+
// The shared token definitions, where a typed-in colour belongs: `true` exempts
|
|
63
|
+
// every custom property in the sheet; a RegExp exempts those in a rule whose
|
|
64
|
+
// selector matches it (the hall's token block: :root and the two dark twins).
|
|
65
|
+
const TOKEN_SHEETS = {
|
|
66
|
+
'modules/ui-design/public/tokens.css': true,
|
|
67
|
+
'modules/hall-ui/public/style.css': /^(?::root|html\[data-theme="dark"\]|html:not\(\[data-theme\]\))$/,
|
|
68
|
+
};
|
|
45
69
|
const COMMENT_RE = /\/\*[\s\S]*?\*\//g;
|
|
46
70
|
const URL_RE = /url\(\s*(?:"[^"]*"|'[^']*'|[^)]*)\)/gi;
|
|
47
71
|
const VAR_NAME_RE = /--[\w-]+/g;
|
|
@@ -84,17 +108,43 @@ function hasLength(value) {
|
|
|
84
108
|
return false;
|
|
85
109
|
}
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
// The selector of the innermost rule around a position, read by walking the text
|
|
112
|
+
// forward (positions must be asked in increasing order). '' at the top level.
|
|
113
|
+
function selectorWalker(css) {
|
|
114
|
+
const stack = [];
|
|
115
|
+
let pos = 0;
|
|
116
|
+
let segStart = 0;
|
|
117
|
+
return (idx) => {
|
|
118
|
+
for (; pos < idx; pos += 1) {
|
|
119
|
+
const ch = css[pos];
|
|
120
|
+
if (ch === '{') { stack.push(css.slice(segStart, pos).replace(/\s+/g, ' ').trim()); segStart = pos + 1; }
|
|
121
|
+
else if (ch === '}') { stack.pop(); segStart = pos + 1; }
|
|
122
|
+
else if (ch === ';') segStart = pos + 1;
|
|
123
|
+
}
|
|
124
|
+
return stack.length ? stack[stack.length - 1] : '';
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The five counts for one stylesheet's text. `tokens` marks the shared token
|
|
130
|
+
* definitions: true (the whole sheet) or a RegExp over a rule's selector.
|
|
131
|
+
*/
|
|
132
|
+
function countSheet(text, { tokens = false } = {}) {
|
|
89
133
|
const counts = Object.fromEntries(KINDS.map((k) => [k, 0]));
|
|
90
134
|
const css = String(text).replace(COMMENT_RE, ' ').replace(URL_RE, 'url()');
|
|
91
135
|
AT_RULE_RE.lastIndex = 0;
|
|
92
136
|
let m;
|
|
93
137
|
while ((m = AT_RULE_RE.exec(css))) if (MEDIA_LENGTH_RE.test(m[2])) counts.breakpoints += 1;
|
|
138
|
+
const selectorAt = selectorWalker(css);
|
|
94
139
|
DECL_RE.lastIndex = 0;
|
|
95
140
|
while ((m = DECL_RE.exec(css))) {
|
|
96
141
|
const prop = m[1].toLowerCase();
|
|
97
|
-
if (prop.startsWith('--'))
|
|
142
|
+
if (prop.startsWith('--')) {
|
|
143
|
+
// A custom property: its colours count unless it is a shared token definition.
|
|
144
|
+
const exempt = tokens === true || (tokens instanceof RegExp && tokens.test(selectorAt(m.index + m[0].indexOf(m[1]))));
|
|
145
|
+
if (!exempt) counts.colours += colourCount(m[2].replace(VAR_NAME_RE, ' '));
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
98
148
|
const value = m[2].replace(VAR_NAME_RE, ' ');
|
|
99
149
|
counts.colours += colourCount(value);
|
|
100
150
|
if ((prop === 'font-size' || prop === 'font') && hasLength(value)) counts.font_sizes += 1;
|
|
@@ -106,8 +156,16 @@ function countSheet(text) {
|
|
|
106
156
|
}
|
|
107
157
|
|
|
108
158
|
function trackedStylesheets(root = ROOT) {
|
|
109
|
-
const out = execFileSync('git', ['ls-files', '-z', '--', '*.css'], { cwd: root, encoding: 'utf8' });
|
|
110
|
-
return out.split('\0').filter((f) => f && !EXCLUDE_RE.test(f)
|
|
159
|
+
const out = execFileSync('git', ['ls-files', '-z', '--', '*.css', '*.html', ...STYLE_STRING_SOURCES], { cwd: root, encoding: 'utf8' });
|
|
160
|
+
return out.split('\0').filter((f) => f && !EXCLUDE_RE.test(f)
|
|
161
|
+
&& (f.endsWith('.css') || SERVED_HTML_RE.test(f) || STYLE_STRING_SOURCES.includes(f))).sort();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// The CSS a file carries: a stylesheet is all CSS; a page or a server file
|
|
165
|
+
// carries only what sits inside its <style> blocks.
|
|
166
|
+
function cssOf(file, text) {
|
|
167
|
+
if (file.endsWith('.css')) return text;
|
|
168
|
+
return [...String(text).matchAll(STYLE_BLOCK_RE)].map((b) => b[1]).join('\n');
|
|
111
169
|
}
|
|
112
170
|
|
|
113
171
|
/**
|
|
@@ -123,7 +181,7 @@ function scanCssLiterals({ root = ROOT, files = null, read = null } = {}) {
|
|
|
123
181
|
let text;
|
|
124
182
|
try { text = reader(f); } catch { continue; }
|
|
125
183
|
scanned += 1;
|
|
126
|
-
const c = countSheet(text);
|
|
184
|
+
const c = countSheet(cssOf(f, text), { tokens: TOKEN_SHEETS[f] || false });
|
|
127
185
|
if (KINDS.some((k) => c[k] > 0)) sheets[f] = c;
|
|
128
186
|
}
|
|
129
187
|
return { sheets, scanned };
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.60'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -28,7 +28,7 @@ const readReal = (f) => fs.readFileSync(path.join(ROOT, f), 'utf8');
|
|
|
28
28
|
|
|
29
29
|
test('the detector counts typed-in values and leaves tokens alone', () => {
|
|
30
30
|
const c = scan.countSheet(`
|
|
31
|
-
:root { --ds-x: 14px; --tone: #123456; } /* token definitions: not counted */
|
|
31
|
+
:root { --ds-x: 14px; --tone: #123456; } /* a token sheet's definitions: not counted */
|
|
32
32
|
.a { font-size: 13px; border-radius: 6px; color: #abc; }
|
|
33
33
|
.b { font-size: var(--ds-text-sm); border-radius: var(--ds-radius-tile); color: var(--ink); }
|
|
34
34
|
.c { box-shadow: 0 2px 4px rgba(0, 0, 0, .2); } /* a shadow; the black scrim is allowed */
|
|
@@ -36,7 +36,7 @@ test('the detector counts typed-in values and leaves tokens alone', () => {
|
|
|
36
36
|
.e { background: color-mix(in srgb, var(--ink) 40%, rgb(12 34 56)); padding: 0; }
|
|
37
37
|
@media (max-width: 720px) { .f { border-radius: 0; } }
|
|
38
38
|
@media (prefers-reduced-motion: reduce) { .g { transition: none; } }
|
|
39
|
-
|
|
39
|
+
`, { tokens: true });
|
|
40
40
|
assert.deepEqual(c, { colours: 2, font_sizes: 1, radii: 1, shadows: 1, breakpoints: 1 });
|
|
41
41
|
});
|
|
42
42
|
|
|
@@ -170,3 +170,60 @@ test('the freshness gate covers the logo set: mark.svg, the pack and both PNGs',
|
|
|
170
170
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
|
+
|
|
174
|
+
// task 1005134: two loopholes closed. (1) A sheet that names its colours as custom
|
|
175
|
+
// properties used to count zero, so a new sheet could bypass its zero start; now
|
|
176
|
+
// only the shared token definitions are exempt. (2) A page's inline <style> and the
|
|
177
|
+
// error page's CSS string were never scanned; now they are, with baselines.
|
|
178
|
+
const HALL_TOKENS = /^(?::root|html\[data-theme="dark"\]|html:not\(\[data-theme\]\))$/;
|
|
179
|
+
|
|
180
|
+
test('a colour typed into a custom property counts, outside the shared token definitions', () => {
|
|
181
|
+
assert.equal(scan.countSheet(':root { --mine: #123456; }').colours, 1, ':root{--mine:#123456}');
|
|
182
|
+
assert.equal(scan.countSheet('body { --x: rgb(10, 20, 30); --y: hsl(1 2% 3%); }').colours, 2, 'body{--x:rgb(...)}');
|
|
183
|
+
assert.equal(scan.countSheet('.v { --veil: rgba(0, 0, 0, .4); --tone: var(--ink); }').colours, 0, 'a scrim and a reference are not typed-in colours');
|
|
184
|
+
assert.equal(scan.countSheet(':root { --mine: #123456; } .x { --y: #654321; }', { tokens: true }).colours, 0, 'the shared token sheet is exempt');
|
|
185
|
+
const hall = ':root { --a: #111111; } html[data-theme="dark"] { --a: #222222; } '
|
|
186
|
+
+ '@media (prefers-color-scheme: dark) { html:not([data-theme]) { --a: #222222; } } '
|
|
187
|
+
+ '.brand { --mark-ink: #333333; } @media (max-width: 1px) { .y { --z: #444444; } }';
|
|
188
|
+
assert.equal(scan.countSheet(hall, { tokens: HALL_TOKENS }).colours, 2, 'the hall token block is exempt; its other rules are not');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('a NEW sheet that names its colours as custom properties fails fitness', () => {
|
|
192
|
+
const NEW = 'modules/x/public/new.css';
|
|
193
|
+
const { sheets } = scan.scanCssLiterals({ files: [NEW], read: () => ':root { --mine: #123456; } body { --x: rgb(1, 2, 3); }' });
|
|
194
|
+
const d = ratchets.compareDesignCounts(sheets, { _comment: 'note' });
|
|
195
|
+
assert.ok(d.violations.some((v) => v.includes(`colours in ${NEW} regressed: 2 > baseline 0`)), d.violations.join('\n'));
|
|
196
|
+
// …while a definition planted in the shared token sheet, or the hall's :root, stays exempt.
|
|
197
|
+
const planted = scan.scanCssLiterals({
|
|
198
|
+
read: (f) => readReal(f) + (f === 'modules/ui-design/public/tokens.css' || f === HALL_SHEET ? '\n:root { --mine: #123456; }\n' : ''),
|
|
199
|
+
});
|
|
200
|
+
assert.deepEqual(ratchets.checkQualityRatchets({ metrics: {}, designCounts: planted.sheets }).violations, []);
|
|
201
|
+
const leak = scan.scanCssLiterals({ read: (f) => readReal(f) + (f === HALL_SHEET ? '\n.planted { --mine: #123456; }\n' : '') });
|
|
202
|
+
const r = ratchets.checkQualityRatchets({ metrics: {}, designCounts: leak.sheets });
|
|
203
|
+
assert.ok(r.violations.some((v) => v.includes(`colours in ${HALL_SHEET} regressed`)), r.violations.join('\n'));
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('inline <style> blocks in served pages and the error page CSS are scanned', () => {
|
|
207
|
+
const PAGE = 'modules/public-landing/public/terms.html';
|
|
208
|
+
const ERR = 'src/error-page.js';
|
|
209
|
+
const { sheets } = scan.scanCssLiterals();
|
|
210
|
+
assert.ok(sheets[ERR], `${ERR} is scanned`);
|
|
211
|
+
assert.ok(sheets[PAGE], `${PAGE} is scanned`);
|
|
212
|
+
assert.ok(!Object.keys(sheets).some((f) => /\/styles\/[^/]+\/mock\.html$|\/specimens\/.*\.html$/.test(f)), 'unserved mocks and specimens are not');
|
|
213
|
+
assert.equal(scan.countSheet('').colours, 0);
|
|
214
|
+
const planted = scan.scanCssLiterals({
|
|
215
|
+
read: (f) => {
|
|
216
|
+
const t = readReal(f);
|
|
217
|
+
if (f === PAGE || f === ERR) return t.replace('</style>', '.planted { color: #123456; font-size: 13px; }\n</style>');
|
|
218
|
+
return t;
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
const r = ratchets.checkQualityRatchets({ metrics: {}, designCounts: planted.sheets });
|
|
222
|
+
for (const f of [PAGE, ERR]) {
|
|
223
|
+
assert.ok(r.violations.some((v) => v.includes(`colours in ${f} regressed`)), `${f} colours: ${r.violations.join('\n')}`);
|
|
224
|
+
assert.ok(r.violations.some((v) => v.includes(`font_sizes in ${f} regressed`)), `${f} sizes: ${r.violations.join('\n')}`);
|
|
225
|
+
}
|
|
226
|
+
// Text outside a <style> block is not CSS: a hex in a page's prose is not counted.
|
|
227
|
+
const prose = scan.scanCssLiterals({ read: (f) => readReal(f) + (f === PAGE ? '\n<p>color: #123456;</p>\n' : '') });
|
|
228
|
+
assert.deepEqual(prose.sheets[PAGE], sheets[PAGE]);
|
|
229
|
+
});
|