@conduction/docusaurus-preset 3.37.0 → 3.39.0
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/MISSING_COMPONENTS.md +1 -0
- package/package.json +1 -1
- package/src/__tests__/no-icu-messages.test.js +57 -0
- package/src/components/AiDisclosure/AiDisclosure.jsx +45 -21
- package/src/components/AiDisclosure/AiDisclosure.module.css +38 -3
- package/src/components/AiDisclosure/__tests__/AiDisclosure.render.test.js +25 -13
- package/src/components/AiDisclosure/__tests__/disclosure.test.js +60 -0
- package/src/components/AiDisclosure/disclosure.js +60 -4
- package/src/components/BlueprintRush/BlueprintRush.jsx +238 -0
- package/src/components/BlueprintRush/BlueprintRush.module.css +186 -0
- package/src/components/BlueprintRush/__tests__/engine.test.js +154 -0
- package/src/components/BlueprintRush/engine.js +172 -0
- package/src/components/DeadlineDefender/DeadlineDefender.jsx +237 -0
- package/src/components/DeadlineDefender/DeadlineDefender.module.css +193 -0
- package/src/components/DeadlineDefender/__tests__/engine.test.js +163 -0
- package/src/components/DeadlineDefender/engine.js +188 -0
- package/src/components/DetailHero/DetailHero.jsx +11 -4
- package/src/components/DetailHero/__tests__/DetailHero.downloads.test.js +160 -0
- package/src/components/FeaturedCard/FeaturedCard.jsx +14 -1
- package/src/components/FeaturedCard/FeaturedCard.module.css +10 -0
- package/src/components/FeaturedCard/__tests__/FeaturedCard.visual.test.js +110 -0
- package/src/components/GameModal/GameModal.jsx +255 -50
- package/src/components/GameModal/GameModal.module.css +103 -0
- package/src/components/GameModal/__tests__/scores.test.js +123 -0
- package/src/components/GameModal/__tests__/share.test.js +83 -0
- package/src/components/GameModal/scores.js +149 -0
- package/src/components/GameModal/share.js +97 -0
- package/src/components/RecordRun/RecordRun.jsx +245 -0
- package/src/components/RecordRun/RecordRun.module.css +208 -0
- package/src/components/RecordRun/__tests__/engine.test.js +191 -0
- package/src/components/RecordRun/engine.js +180 -0
- package/src/components/StampRush/StampRush.jsx +232 -0
- package/src/components/StampRush/StampRush.module.css +188 -0
- package/src/components/StampRush/__tests__/engine.test.js +182 -0
- package/src/components/StampRush/engine.js +185 -0
- package/src/components/ThemeSeamMock/ThemeSeamMock.jsx +79 -0
- package/src/components/ThemeSeamMock/ThemeSeamMock.module.css +178 -0
- package/src/components/ThemeSeamMock/__tests__/ThemeSeamMock.render.test.js +122 -0
- package/src/components/index.js +5 -0
- package/src/data/app-downloads.js +21 -0
- package/src/index.js +10 -0
- package/src/theme/Footer/index.jsx +10 -1
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ThemeSeamMock.render.test.js — renders the real <ThemeSeamMock> JSX
|
|
3
|
+
* to static markup and asserts on the output.
|
|
4
|
+
*
|
|
5
|
+
* What matters about this mock is that both layers are the same
|
|
6
|
+
* application frame and that the themed layer differs only by the
|
|
7
|
+
* wrapper that redefines the tokens. A test that let the two layers
|
|
8
|
+
* drift apart would be checking a screen change, not a finish change,
|
|
9
|
+
* which is the opposite of the claim the component makes.
|
|
10
|
+
*
|
|
11
|
+
* Same esbuild-bundle-then-renderToStaticMarkup technique as
|
|
12
|
+
* AppMock.render.test.js; CSS modules are stubbed with an identity
|
|
13
|
+
* proxy, so class assertions read as the source class names.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const test = require('node:test');
|
|
19
|
+
const {before, after} = test;
|
|
20
|
+
const assert = require('node:assert/strict');
|
|
21
|
+
const path = require('node:path');
|
|
22
|
+
const fs = require('node:fs/promises');
|
|
23
|
+
const {build} = require('esbuild');
|
|
24
|
+
const React = require('react');
|
|
25
|
+
const {renderToStaticMarkup} = require('react-dom/server');
|
|
26
|
+
|
|
27
|
+
const COMPONENT = path.resolve(__dirname, '..', 'ThemeSeamMock.jsx');
|
|
28
|
+
const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
29
|
+
const SCRATCH = path.join(PRESET_ROOT, '.tmp-theme-seam-test');
|
|
30
|
+
|
|
31
|
+
const cssModuleStub = {
|
|
32
|
+
name: 'css-module-stub',
|
|
33
|
+
setup(b) {
|
|
34
|
+
b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
|
|
35
|
+
b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
|
|
36
|
+
contents: 'export default new Proxy({}, {get: (_, p) => p});',
|
|
37
|
+
loader: 'js',
|
|
38
|
+
}));
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
let ThemeSeamMock;
|
|
43
|
+
|
|
44
|
+
before(async () => {
|
|
45
|
+
await fs.mkdir(SCRATCH, {recursive: true});
|
|
46
|
+
const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
|
|
47
|
+
const outFile = path.join(tmpDir, 'bundle.cjs');
|
|
48
|
+
await build({
|
|
49
|
+
entryPoints: [COMPONENT],
|
|
50
|
+
outfile: outFile,
|
|
51
|
+
bundle: true,
|
|
52
|
+
format: 'cjs',
|
|
53
|
+
jsx: 'automatic',
|
|
54
|
+
jsxImportSource: 'react',
|
|
55
|
+
tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
|
|
56
|
+
platform: 'node',
|
|
57
|
+
external: ['react'],
|
|
58
|
+
plugins: [cssModuleStub],
|
|
59
|
+
logLevel: 'warning',
|
|
60
|
+
});
|
|
61
|
+
delete require.cache[require.resolve(outFile)];
|
|
62
|
+
ThemeSeamMock = require(outFile).default;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
after(async () => {
|
|
66
|
+
await fs.rm(SCRATCH, {recursive: true, force: true});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function render(props) {
|
|
70
|
+
return renderToStaticMarkup(React.createElement(ThemeSeamMock, props));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function frames(html) {
|
|
74
|
+
return html.match(/class="frame size-\w+[^"]*"/g) || [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test('renders two layers of the same app frame, plus the seam', () => {
|
|
78
|
+
const html = render({app: 'openregister'});
|
|
79
|
+
assert.equal(frames(html).length, 2, 'expected a stock layer and a themed layer');
|
|
80
|
+
assert.match(html, /class="layer themed lasuite"/);
|
|
81
|
+
assert.match(html, /class="seam"/);
|
|
82
|
+
assert.doesNotMatch(html, /Unknown app/);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('both layers render the identical frame, so the wipe changes finish and nothing else', () => {
|
|
86
|
+
const html = render({app: 'procest'});
|
|
87
|
+
/* Split on the themed wrapper: what follows must repeat what came
|
|
88
|
+
before it, or the two layers are showing different screens. */
|
|
89
|
+
const [stock, themed] = html.split('class="layer themed lasuite"');
|
|
90
|
+
const bodyOf = (s) => (s.match(/<div class="body[^]*$/) || [''])[0].replace(/\s+/g, '');
|
|
91
|
+
assert.ok(bodyOf(stock).length > 0, 'no stock layer body rendered');
|
|
92
|
+
assert.equal(
|
|
93
|
+
bodyOf(stock).slice(0, 400),
|
|
94
|
+
bodyOf(themed).slice(0, 400),
|
|
95
|
+
'the themed layer is not the same frame as the stock layer',
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('the inner AppMocks never run their own loop, which would compete with the seam', () => {
|
|
100
|
+
const html = render({app: 'openregister'});
|
|
101
|
+
assert.equal(frames(html).filter((c) => c.includes('static')).length, 2);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('running={false} freezes the scene on the themed end state', () => {
|
|
105
|
+
assert.match(render({app: 'openregister', running: false}), /class="seamScene size-md static"/);
|
|
106
|
+
assert.doesNotMatch(render({app: 'openregister'}), /seamScene size-md static/);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('an unknown theme falls back to lasuite rather than rendering unthemed', () => {
|
|
110
|
+
assert.match(render({app: 'openregister', theme: 'nope'}), /class="layer themed lasuite"/);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('size is forwarded to both frames', () => {
|
|
114
|
+
const html = render({app: 'openregister', size: 'sm'});
|
|
115
|
+
assert.match(html, /class="seamScene size-sm"/);
|
|
116
|
+
assert.equal(frames(html).filter((c) => c.includes('size-sm')).length, 2);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('a label renders as the caption, and is absent otherwise', () => {
|
|
120
|
+
assert.match(render({app: 'openregister', label: 'La Suite'}), /<figcaption class="caption">La Suite</);
|
|
121
|
+
assert.doesNotMatch(render({app: 'openregister'}), /figcaption/);
|
|
122
|
+
});
|
package/src/components/index.js
CHANGED
|
@@ -60,6 +60,10 @@ export {default as Pipeline, PipelineStep, IconList} from './Pipeline/Pipeline.j
|
|
|
60
60
|
export {default as FacetedFilters, FilterChip} from './FacetedFilters/FacetedFilters.jsx';
|
|
61
61
|
export {default as CookieCli} from './CookieCli/CookieCli.jsx';
|
|
62
62
|
export {default as GameModal} from './GameModal/GameModal.jsx';
|
|
63
|
+
export {default as StampRush} from './StampRush/StampRush.jsx';
|
|
64
|
+
export {default as DeadlineDefender} from './DeadlineDefender/DeadlineDefender.jsx';
|
|
65
|
+
export {default as BlueprintRush} from './BlueprintRush/BlueprintRush.jsx';
|
|
66
|
+
export {default as RecordRun} from './RecordRun/RecordRun.jsx';
|
|
63
67
|
|
|
64
68
|
/* Diagram-set web-component React wrappers (cn-hex, cn-platform,
|
|
65
69
|
cn-domain-tree, cn-pipeline, cn-side-box, cn-honeycomb-bg, cn-pair,
|
|
@@ -77,6 +81,7 @@ export {default as ComposeBlock} from './ComposeBlock/ComposeBlock.jsx';
|
|
|
77
81
|
export {default as AppsGrid} from './AppsGrid/AppsGrid.jsx';
|
|
78
82
|
export {default as AppMock} from './AppMock/AppMock.jsx';
|
|
79
83
|
export {default as WidgetMock} from './WidgetMock/WidgetMock.jsx';
|
|
84
|
+
export {default as ThemeSeamMock} from './ThemeSeamMock/ThemeSeamMock.jsx';
|
|
80
85
|
export {default as FlowMock} from './FlowMock/FlowMock.jsx';
|
|
81
86
|
export {default as LeafMock} from './LeafMock/LeafMock.jsx';
|
|
82
87
|
export {default as BuildMock} from './BuildMock/BuildMock.jsx';
|
|
@@ -65,3 +65,24 @@ export function downloadsForApp(appId) {
|
|
|
65
65
|
export function formatDownloads(n, locale = 'en') {
|
|
66
66
|
return Number(n || 0).toLocaleString(locale);
|
|
67
67
|
}
|
|
68
|
+
|
|
69
|
+
/* Below this many downloads an app shows no counter at all.
|
|
70
|
+
A freshly published app sits on a handful of downloads for weeks,
|
|
71
|
+
and printing "5 downloads" next to it argues against the app. The
|
|
72
|
+
counter is there to show traction, so it appears once there is
|
|
73
|
+
traction to show. Aggregate figures (totalDownloads on the
|
|
74
|
+
Connext and Common Ground pages) are not subject to this: the
|
|
75
|
+
fleet total is a real number on its own. */
|
|
76
|
+
export const MIN_DISPLAYED_DOWNLOADS = 1000;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Whether a download count is worth putting on screen.
|
|
80
|
+
*
|
|
81
|
+
* Used for the per-app counter on DetailHero, including its
|
|
82
|
+
* schema.org InteractionCounter: a number we hide from the page does
|
|
83
|
+
* not belong in the structured data either, or search results quote
|
|
84
|
+
* back the figure the page declines to show.
|
|
85
|
+
*/
|
|
86
|
+
export function showDownloads(n) {
|
|
87
|
+
return Number(n || 0) >= MIN_DISPLAYED_DOWNLOADS;
|
|
88
|
+
}
|
package/src/index.js
CHANGED
|
@@ -439,6 +439,14 @@ const baseFooter = () => ({
|
|
|
439
439
|
* footer (per-property fallback: any of style/links/copyright the
|
|
440
440
|
* site omits keeps its brand default — pass `footer: { links: [...] }`
|
|
441
441
|
* to swap columns while inheriting the KvK/BTW copyright),
|
|
442
|
+
* minigamesRoster (optional; [{id, label}] of the games this site
|
|
443
|
+
* ships, when they are not the preset's default five. A label may
|
|
444
|
+
* be a per-locale map, since themeConfig is never translated.)
|
|
445
|
+
* minigamesShare (optional; {hashtag, url, prize, prizeHref,
|
|
446
|
+
* prizeLinkLabel} passed to
|
|
447
|
+
* the game-over dialog's share block. The hashtag defaults to
|
|
448
|
+
* #IReadTheKit and the url to siteConfig.url; prize renders a line
|
|
449
|
+
* under the share buttons, linked when prizeHref is given.)
|
|
442
450
|
* minigames (default true; set false to drop the brand canal-footer's
|
|
443
451
|
* boat-sinking + kade-cyclist mini-games on product pages while
|
|
444
452
|
* keeping the static skyline + canal decoration),
|
|
@@ -574,6 +582,8 @@ function createConfig(opts) {
|
|
|
574
582
|
site opts out, while still keeping the static skyline +
|
|
575
583
|
canal decoration. Default true preserves prior behaviour. */
|
|
576
584
|
minigames: opts.minigames !== false,
|
|
585
|
+
minigamesShare: opts.minigamesShare,
|
|
586
|
+
minigamesRoster: opts.minigamesRoster,
|
|
577
587
|
/* Footer brand block (the wordmark + tagline + triad + socials
|
|
578
588
|
on the left of the canal-footer grid).
|
|
579
589
|
undefined -> wordmark = 'Conduction' (product-page default;
|
|
@@ -52,6 +52,15 @@ export default function Footer() {
|
|
|
52
52
|
surfaced through createConfig() opts. See createConfig() in
|
|
53
53
|
../../index.js for the option semantics. */
|
|
54
54
|
const minigamesOn = themeConfig.minigames !== false;
|
|
55
|
+
/* Campaign copy for the game-over dialog's share block (hashtag, a
|
|
56
|
+
prize line and where its rules live). A campaign is per site and
|
|
57
|
+
time-bound, so it lives in themeConfig rather than in the
|
|
58
|
+
component: the preset ships the mechanism, the site ships the
|
|
59
|
+
giveaway. */
|
|
60
|
+
const minigamesShare = themeConfig.minigamesShare || undefined;
|
|
61
|
+
/* The roster of games this site ships, when it is not the preset's
|
|
62
|
+
default five. Labels may be per-locale maps. */
|
|
63
|
+
const minigamesRoster = themeConfig.minigamesRoster || undefined;
|
|
55
64
|
const footerBrand = themeConfig.footerBrand || null;
|
|
56
65
|
/* legalLinks: opt-in/out of the Privacy / Terms / ISO links inside
|
|
57
66
|
the legal-bar, and the two ISO 9001/27001 certification badges on
|
|
@@ -479,7 +488,7 @@ export default function Footer() {
|
|
|
479
488
|
Only mounted when minigames are on so a product page doesn't
|
|
480
489
|
carry the dialog DOM + listeners for an interaction it can't
|
|
481
490
|
trigger. */}
|
|
482
|
-
{minigamesOn && <GameModal />}
|
|
491
|
+
{minigamesOn && <GameModal share={minigamesShare} games={minigamesRoster} />}
|
|
483
492
|
</>
|
|
484
493
|
);
|
|
485
494
|
}
|