@bongos/core 1.24.58 → 1.24.59

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,111 @@
1
+ // tests/wordmark_and_faces.mjs — the logo wordmark is the pack's name in lowercase, and
2
+ // no served page loads a retired face (task 1005135, goal 1000138).
3
+ //
4
+ // DESIGN.md "Naming": beside the mark the name is lowercase ("cloud bongos"), and
5
+ // platform code never hardcodes either spelling; it reads the pack. The UI check found
6
+ // the status page, the 404/500 pages and the API docs showing "Cloud Bongos" beside the
7
+ // mark, and /brand baking the neutral pack's name into its generated HTML. The sign-in
8
+ // error and confirming-access pages still loaded Cinzel and EB Garamond. This pins:
9
+ // 1. every wordmark beside the mark is the {{productName}} hole (stamped from the pack
10
+ // at serve time), never a typed-in name, and the rule that styles it lowercases it;
11
+ // 2. no served page source (the auth pages, the sign-in shell, the error page, the API
12
+ // docs, the status page, the hall, the landing) names Cinzel or Garamond, and the
13
+ // rendered auth pages carry the pack's own font links instead;
14
+ // 3. the error page declares no colour of its own: it reads the hall sheet's tier.
15
+ import { test } from 'node:test';
16
+ import assert from 'node:assert/strict';
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { createRequire } from 'node:module';
21
+
22
+ const require = createRequire(import.meta.url);
23
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
24
+ const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
25
+ const stripComments = (s) => s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/<!--[\s\S]*?-->/g, '').replace(/(^|[^:'"`\\])\/\/[^\n]*/g, '$1');
26
+
27
+ const { errorPageTemplate } = require('../src/error-page.js');
28
+ const { clientBranding } = require('../src/branding.js');
29
+ const authRoutes = require('../src/bongos/routes/auth.js');
30
+ const signIn = require('../src/bongos/sign-in-pages.js');
31
+
32
+ // The CSS rule for a selector, from a sheet or an inline <style>. Throws when missing.
33
+ function ruleOf(css, selector) {
34
+ const esc = selector.replace(/[.*+?^${}()|[\]\\#]/g, '\\$&');
35
+ const m = stripComments(css).match(new RegExp(`(?:^|[}\\s,])${esc}\\s*\\{([^}]*)\\}`));
36
+ assert.ok(m, `a rule for ${selector}`);
37
+ return m[1];
38
+ }
39
+ const lowercased = (body) => /text-transform\s*:\s*lowercase/.test(body);
40
+
41
+ // Each wordmark beside the mark: [where, the markup that must carry the pack's hole, the
42
+ // sheet that styles it, the selector of its rule].
43
+ const WORDMARKS = [
44
+ ['the status page', read('modules/status-ui/public/index.html'), /<span class="topbar__name">\{\{productName\}\}<\/span>/, read('modules/status-ui/public/style.css'), '.topbar__name'],
45
+ ['the 404 page', errorPageTemplate(404), /<a class="name" href="\/">\{\{productName\}\}<\/a>/, errorPageTemplate(404), '.name'],
46
+ ['the 500 page', errorPageTemplate(500), /<a class="name" href="\/">\{\{productName\}\}<\/a>/, errorPageTemplate(500), '.name'],
47
+ ['the API docs', read('public-docs/index.html'), /<span class="wordmark">\{\{productName\}\}<\/span>/, read('public-docs/index.html'), '#topbar .wordmark'],
48
+ ['/brand (bar)', read('modules/ui-design/public/brand.html'), /<a class="ref-head__brand"[^>]*>.*<span>\{\{productName\}\}<\/span><\/a>/, read('modules/ui-design/public/brand.css'), '.ref-head__brand'],
49
+ ['/brand (lockup)', read('modules/ui-design/public/brand.html'), /<span class="ref-lockup__name">\{\{productName\}\}<\/span>/, read('modules/ui-design/public/brand.css'), '.ref-lockup'],
50
+ ['the hall', read('modules/hall-ui/public/style.css'), /\.brand-mark\s*\{/, read('modules/hall-ui/public/style.css'), '.brand-mark'],
51
+ ];
52
+
53
+ test('every wordmark beside the mark is the pack\'s name, lowercased by its rule', () => {
54
+ for (const [where, markup, hole, css, selector] of WORDMARKS) {
55
+ assert.match(markup, hole, `${where}: the wordmark is the {{productName}} hole`);
56
+ assert.ok(lowercased(ruleOf(css, selector)), `${where}: ${selector} sets text-transform: lowercase`);
57
+ }
58
+ });
59
+
60
+ test('no typed-in product name beside the mark', () => {
61
+ // A capitalised (or any) literal in a wordmark element is exactly the regression.
62
+ const literal = /class="(?:topbar__name|name|wordmark|ref-lockup__name)"[^>]*>(?!\{\{)[^<]+</;
63
+ for (const [where, markup] of WORDMARKS) assert.doesNotMatch(markup, literal, `${where}: no literal name in the wordmark`);
64
+ assert.doesNotMatch(read('modules/ui-design/public/brand.html'), /ref-head__brand[^>]*>.*<span>(?!\{\{)[^<]+<\/span><\/a>/, '/brand bar: no literal name');
65
+ // the generator writes the hole, never the pack's name it happened to read
66
+ assert.doesNotMatch(read('modules/ui-design/scripts/gen-design-reference.js'), /productName\)?\.toLowerCase\(\)|product\.toLowerCase\(\)/);
67
+ });
68
+
69
+ const RETIRED = /Cinzel|Garamond/i;
70
+ const SERVED = [
71
+ 'src/bongos/routes/auth.js', 'src/bongos/sign-in-pages.js', 'src/bongos/routes/auth-google.js', 'src/error-page.js',
72
+ 'public-docs/index.html', 'modules/status-ui/public/index.html', 'modules/status-ui/public/style.css',
73
+ 'modules/ui-design/public/brand.html', 'modules/ui-design/public/brand.css',
74
+ 'modules/hall-ui/public/style.css', 'modules/hall-ui/public/controls.css',
75
+ ];
76
+
77
+ test('no served page source loads a retired face', () => {
78
+ for (const rel of SERVED) {
79
+ if (!fs.existsSync(path.join(ROOT, rel))) continue;
80
+ // read raw: a comment stripper over JS eats template strings (a `/*` in a URL glob),
81
+ // so these files name the retired faces nowhere at all, comments included
82
+ assert.doesNotMatch(read(rel), RETIRED, `${rel} names a retired face`);
83
+ }
84
+ });
85
+
86
+ test('the auth pages carry the pack\'s faces and the shared sheet, and nothing else', () => {
87
+ const links = (clientBranding().theme && clientBranding().theme.fonts && clientBranding().theme.fonts.links) || [];
88
+ const pages = [
89
+ ['sign-in error', authRoutes.renderAuthErrorPage('Sign-in failed', 'The sign-in did not complete.')],
90
+ ['confirming access', authRoutes.renderAccessPendingPage('someone', '/api/bongos/auth/web/start')],
91
+ ['sign-in chooser', signIn.renderSignInChooser({ githubHref: '/g', googleHref: '/o' })],
92
+ ];
93
+ for (const [name, html] of pages) {
94
+ assert.ok(html.includes('<link rel="stylesheet" href="/builders/style.css">'), `${name}: the hall sheet`);
95
+ for (const href of links) assert.ok(html.includes(href.replace(/&/g, '&amp;')), `${name}: the pack's font link ${href}`);
96
+ const own = html.match(/https:\/\/fonts\.googleapis\.com\/css2\?[^"]*/g) || [];
97
+ for (const href of own) assert.ok(links.some((l) => l.replace(/&/g, '&amp;') === href || l === href), `${name}: ${href} is not the pack's`);
98
+ }
99
+ });
100
+
101
+ test('the error page declares no colour of its own', () => {
102
+ for (const status of [404, 500]) {
103
+ const html = errorPageTemplate(status);
104
+ const style = (html.match(/<style>([\s\S]*?)<\/style>/) || [])[1] || '';
105
+ assert.ok(style, 'the page has its layout rules');
106
+ const declarations = style.replace(/--[\w-]+\s*:[^;}]*/g, '');
107
+ assert.doesNotMatch(style, /--(bg|ink|accent|rule)[\w-]*\s*:/, 'no colour token is defined on the page');
108
+ assert.doesNotMatch(declarations, /#[0-9a-f]{3,8}\b|rgba?\(/i, 'no typed-in colour');
109
+ assert.ok(html.includes('<link rel="stylesheet" href="/modules/hall-ui/public/style.css">'), 'the colours come from the shared sheet');
110
+ }
111
+ });