@cosmicdrift/kumiko-headless 0.328.0 → 0.329.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/dist/apex/css.d.ts +3 -0
- package/{src/apex/css.ts → dist/apex/css.js} +11 -24
- package/dist/apex/index.d.ts +229 -0
- package/dist/apex/index.js +360 -0
- package/dist/apex/lightbox.d.ts +4 -0
- package/{src/apex/lightbox.ts → dist/apex/lightbox.js} +0 -4
- package/dist/contracts/asset.d.ts +15 -0
- package/dist/contracts/asset.js +17 -0
- package/dist/contracts/index.d.ts +3 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/locale.d.ts +7 -0
- package/dist/contracts/locale.js +11 -0
- package/dist/contracts/primitives.d.ts +93 -0
- package/dist/contracts/primitives.js +21 -0
- package/dist/dispatcher/index.d.ts +2 -0
- package/dist/dispatcher/index.js +1 -0
- package/dist/dispatcher/types.d.ts +86 -0
- package/dist/dispatcher/types.js +34 -0
- package/dist/form/form-controller.d.ts +2 -0
- package/dist/form/form-controller.js +404 -0
- package/dist/form/index.d.ts +3 -0
- package/dist/form/index.js +2 -0
- package/dist/form/types.d.ts +71 -0
- package/dist/form/types.js +1 -0
- package/dist/form/zod-bridge.d.ts +5 -0
- package/{src/form/zod-bridge.ts → dist/form/zod-bridge.js} +52 -58
- package/dist/format/escape.d.ts +5 -0
- package/{src/format/escape.ts → dist/format/escape.js} +43 -47
- package/dist/format/html-template.d.ts +9 -0
- package/dist/format/html-template.js +39 -0
- package/dist/format/index.d.ts +10 -0
- package/dist/format/index.js +192 -0
- package/dist/format/money.d.ts +1 -0
- package/{src/format/money.ts → dist/format/money.js} +6 -5
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/locale-routing/index.d.ts +39 -0
- package/dist/locale-routing/index.js +143 -0
- package/dist/nav/index.d.ts +2 -0
- package/dist/nav/index.js +1 -0
- package/dist/nav/resolve.d.ts +2 -0
- package/dist/nav/resolve.js +58 -0
- package/dist/nav/types.d.ts +26 -0
- package/dist/nav/types.js +1 -0
- package/dist/store/create-store.d.ts +2 -0
- package/{src/store/create-store.ts → dist/store/create-store.js} +21 -23
- package/dist/store/equality.d.ts +1 -0
- package/{src/store/equality.ts → dist/store/equality.js} +19 -17
- package/dist/store/index.d.ts +3 -0
- package/dist/store/index.js +2 -0
- package/dist/store/types.d.ts +7 -0
- package/dist/store/types.js +10 -0
- package/dist/view-model/edit.d.ts +10 -0
- package/dist/view-model/edit.js +392 -0
- package/dist/view-model/embedded-list.d.ts +27 -0
- package/dist/view-model/embedded-list.js +63 -0
- package/dist/view-model/index.d.ts +7 -0
- package/dist/view-model/index.js +3 -0
- package/dist/view-model/list.d.ts +16 -0
- package/dist/view-model/list.js +201 -0
- package/dist/view-model/types.d.ts +300 -0
- package/dist/view-model/types.js +1 -0
- package/package.json +31 -12
- package/src/apex/__tests__/lightbox.test.ts +0 -20
- package/src/apex/__tests__/page-head.test.ts +0 -63
- package/src/apex/__tests__/render.test.ts +0 -401
- package/src/apex/index.ts +0 -667
- package/src/contracts/__tests__/contracts.test.ts +0 -123
- package/src/contracts/__tests__/field-issue-compat.test.ts +0 -19
- package/src/contracts/asset.ts +0 -48
- package/src/contracts/index.ts +0 -23
- package/src/contracts/locale.ts +0 -33
- package/src/contracts/primitives.ts +0 -158
- package/src/dispatcher/__tests__/contract.test.ts +0 -163
- package/src/dispatcher/index.ts +0 -16
- package/src/dispatcher/types.ts +0 -217
- package/src/form/__tests__/conditional-fields.test.ts +0 -177
- package/src/form/__tests__/form-controller.test.ts +0 -231
- package/src/form/__tests__/submit.test.ts +0 -575
- package/src/form/__tests__/validation.test.ts +0 -274
- package/src/form/form-controller.ts +0 -458
- package/src/form/index.ts +0 -15
- package/src/form/types.ts +0 -297
- package/src/format/__tests__/escape.test.ts +0 -79
- package/src/format/__tests__/format.test.ts +0 -285
- package/src/format/__tests__/html-template.test.ts +0 -47
- package/src/format/html-template.ts +0 -49
- package/src/format/index.ts +0 -223
- package/src/index.ts +0 -118
- package/src/locale-routing/__tests__/locale-routing.test.ts +0 -276
- package/src/locale-routing/index.ts +0 -218
- package/src/nav/__tests__/resolve.test.ts +0 -279
- package/src/nav/index.ts +0 -8
- package/src/nav/resolve.ts +0 -60
- package/src/nav/types.ts +0 -70
- package/src/store/__tests__/create-store.test.ts +0 -139
- package/src/store/__tests__/equality.test.ts +0 -66
- package/src/store/index.ts +0 -3
- package/src/store/types.ts +0 -27
- package/src/view-model/__tests__/edit.test.ts +0 -1072
- package/src/view-model/__tests__/embedded-list.test.ts +0 -96
- package/src/view-model/__tests__/field-labels.test.ts +0 -62
- package/src/view-model/__tests__/list.test.ts +0 -439
- package/src/view-model/edit.ts +0 -645
- package/src/view-model/embedded-list.ts +0 -89
- package/src/view-model/index.ts +0 -43
- package/src/view-model/list.ts +0 -280
- package/src/view-model/types.ts +0 -400
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { APEX_NAV_MENU_CSS, APEX_NAV_TOGGLE_RESPONSIVE_CSS, APEX_STRUCTURAL_CSS } from "../css";
|
|
3
|
-
import { type ApexPage, renderApexPage } from "../index";
|
|
4
|
-
|
|
5
|
-
const brand = { tokensCss: ":root{--primary:#123;--primary-fg:#fff;--bg:#fff;--fg:#000;}" };
|
|
6
|
-
|
|
7
|
-
function page(overrides: Partial<ApexPage> = {}): ApexPage {
|
|
8
|
-
return {
|
|
9
|
-
brand,
|
|
10
|
-
head: { lang: "de", title: "T", description: "D" },
|
|
11
|
-
header: { brand: { href: "/", label: "Acme" } },
|
|
12
|
-
sections: [],
|
|
13
|
-
footer: { brand: { label: "Acme" } },
|
|
14
|
-
...overrides,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe("renderApexPage", () => {
|
|
19
|
-
test("theme dark sets body class, light (default) does not", () => {
|
|
20
|
-
expect(renderApexPage(page({ theme: "dark" }))).toContain('<body class="apex-dark">');
|
|
21
|
-
// light is default — both CSS sets ship, only the body class toggles
|
|
22
|
-
expect(renderApexPage(page())).toContain("<body>");
|
|
23
|
-
expect(renderApexPage(page())).not.toContain('<body class="apex-dark">');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("escapes user-provided text in title and content", () => {
|
|
27
|
-
const html = renderApexPage(
|
|
28
|
-
page({
|
|
29
|
-
head: { lang: "de", title: "<script>x</script>", description: "a & b" },
|
|
30
|
-
sections: [{ kind: "info-grid", items: [{ title: "<b>t</b>", desc: "d" }] }],
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
33
|
-
expect(html).not.toContain("<script>x</script>");
|
|
34
|
-
expect(html).toContain("<script>");
|
|
35
|
-
expect(html).toContain("a & b");
|
|
36
|
-
expect(html).toContain("<b>t</b>");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("metaHtml and feature icon are passed through unescaped (app-authored)", () => {
|
|
40
|
-
const html = renderApexPage(
|
|
41
|
-
page({
|
|
42
|
-
sections: [
|
|
43
|
-
{ kind: "hero", title: "h", tagline: "t", metaHtml: "<strong>ok</strong>" },
|
|
44
|
-
{
|
|
45
|
-
kind: "feature-grid",
|
|
46
|
-
heading: "F",
|
|
47
|
-
items: [{ icon: "<rect/>", title: "x", desc: "d" }],
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
}),
|
|
51
|
-
);
|
|
52
|
-
expect(html).toContain("<strong>ok</strong>");
|
|
53
|
-
expect(html).toContain("<svg"); // icon wrapped in standard svg
|
|
54
|
-
expect(html).toContain("<rect/>");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("nav menu entry renders a dropdown; plain links stay anchors", () => {
|
|
58
|
-
const html = renderApexPage(
|
|
59
|
-
page({
|
|
60
|
-
header: {
|
|
61
|
-
brand: { href: "/", label: "Acme" },
|
|
62
|
-
navLinks: [
|
|
63
|
-
{
|
|
64
|
-
kind: "menu",
|
|
65
|
-
label: "Product",
|
|
66
|
-
items: [
|
|
67
|
-
{ icon: "<rect/>", title: "Planner", desc: "Plan it", href: "/p" },
|
|
68
|
-
{ title: "<b>Reports</b>", href: "/r" },
|
|
69
|
-
],
|
|
70
|
-
footer: { label: "See all", href: "/all" },
|
|
71
|
-
},
|
|
72
|
-
{ label: "Pricing", href: "#pricing" },
|
|
73
|
-
],
|
|
74
|
-
},
|
|
75
|
-
}),
|
|
76
|
-
);
|
|
77
|
-
// Menu chrome + trigger label.
|
|
78
|
-
expect(html).toContain('class="nav-menu"');
|
|
79
|
-
expect(html).toContain('class="nav-menu__trigger"');
|
|
80
|
-
expect(html).toContain("Product");
|
|
81
|
-
// Items: title, description, wrapped icon; missing desc/icon simply omitted.
|
|
82
|
-
expect(html).toContain('class="nav-menu__item" href="/p"');
|
|
83
|
-
expect(html).toContain("Planner");
|
|
84
|
-
expect(html).toContain("Plan it");
|
|
85
|
-
expect(html).toContain("<rect/>");
|
|
86
|
-
// Footer link under the divider.
|
|
87
|
-
expect(html).toContain('class="nav-menu__more" href="/all"');
|
|
88
|
-
expect(html).toContain("See all");
|
|
89
|
-
// Item titles are escaped (app-data, not trusted HTML).
|
|
90
|
-
expect(html).toContain("<b>Reports</b>");
|
|
91
|
-
expect(html).not.toContain("<b>Reports</b>");
|
|
92
|
-
// The sibling plain link still renders as a normal anchor.
|
|
93
|
-
expect(html).toContain('<a href="#pricing">Pricing</a>');
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("nav-links wrap in a mobile hamburger toggle; no navLinks omits it", () => {
|
|
97
|
-
const withNav = renderApexPage(
|
|
98
|
-
page({
|
|
99
|
-
header: {
|
|
100
|
-
brand: { href: "/", label: "Acme" },
|
|
101
|
-
navLinks: [{ label: "Pricing", href: "#pricing" }],
|
|
102
|
-
},
|
|
103
|
-
}),
|
|
104
|
-
);
|
|
105
|
-
expect(withNav).toContain('<details class="nav-toggle">');
|
|
106
|
-
expect(withNav).toContain('<summary class="nav-toggle__trigger" aria-label="Menu">');
|
|
107
|
-
expect(withNav).toContain('<nav class="nav-links">');
|
|
108
|
-
|
|
109
|
-
const customLabel = renderApexPage(
|
|
110
|
-
page({
|
|
111
|
-
header: {
|
|
112
|
-
brand: { href: "/", label: "Acme" },
|
|
113
|
-
navLinks: [{ label: "Pricing", href: "#pricing" }],
|
|
114
|
-
menuLabel: "Menü",
|
|
115
|
-
},
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
expect(customLabel).toContain('aria-label="Menü"');
|
|
119
|
-
|
|
120
|
-
const withoutNav = renderApexPage(page());
|
|
121
|
-
expect(withoutNav).not.toContain('<details class="nav-toggle">');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("mobile-open .nav-toggle rule wins the cascade over the desktop-hidden default", () => {
|
|
125
|
-
// APEX_NAV_MENU_CSS sets `.nav-toggle { display: none; }` unconditionally;
|
|
126
|
-
// APEX_NAV_TOGGLE_RESPONSIVE_CSS re-declares it `.nav .nav-toggle { display:
|
|
127
|
-
// inline-flex; }` inside the 640px query. A markup-only assertion can't catch
|
|
128
|
-
// a reordering that silently restores fw#2047 (nav-links hidden on mobile
|
|
129
|
-
// with no way to reopen them).
|
|
130
|
-
const hiddenDefault = APEX_STRUCTURAL_CSS.indexOf(".nav-toggle { display: none; }");
|
|
131
|
-
const mobileOpen = APEX_STRUCTURAL_CSS.indexOf(".nav .nav-toggle { display: inline-flex; }");
|
|
132
|
-
expect(hiddenDefault).toBeGreaterThan(-1);
|
|
133
|
-
expect(mobileOpen).toBeGreaterThan(-1);
|
|
134
|
-
expect(mobileOpen).toBeGreaterThan(hiddenDefault);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("APEX_NAV_TOGGLE_RESPONSIVE_CSS's mobile-open rules outrank APEX_NAV_MENU_CSS's hidden default regardless of concatenation order (fw#2125)", () => {
|
|
138
|
-
// money-horse/show-pony/the docs sample call renderApexHeader() standalone and
|
|
139
|
-
// concatenate APEX_NAV_MENU_CSS with their own page CSS in whatever order they
|
|
140
|
-
// choose — they don't control ordering relative to APEX_STRUCTURAL_CSS's
|
|
141
|
-
// internal layout. The "show" rules are therefore specificity-hardened
|
|
142
|
-
// (`.nav .nav-toggle`, 2 classes) rather than relying on source order to beat
|
|
143
|
-
// APEX_NAV_MENU_CSS's bare `.nav-toggle` default (1 class) — verify both carry
|
|
144
|
-
// that extra `.nav ` prefix so the fix holds even when a consumer emits
|
|
145
|
-
// APEX_NAV_TOGGLE_RESPONSIVE_CSS BEFORE APEX_NAV_MENU_CSS.
|
|
146
|
-
expect(APEX_NAV_MENU_CSS).toContain(".nav-toggle { display: none; }");
|
|
147
|
-
expect(APEX_NAV_MENU_CSS).toContain(".nav-toggle__trigger { display: none;");
|
|
148
|
-
expect(APEX_NAV_MENU_CSS).not.toContain(".nav-toggle { display: inline-flex");
|
|
149
|
-
expect(APEX_NAV_MENU_CSS).not.toContain(".nav-toggle__trigger { display: inline-flex");
|
|
150
|
-
|
|
151
|
-
expect(APEX_NAV_TOGGLE_RESPONSIVE_CSS).toContain(".nav .nav-toggle { display: inline-flex; }");
|
|
152
|
-
expect(APEX_NAV_TOGGLE_RESPONSIVE_CSS).toContain(
|
|
153
|
-
".nav .nav-toggle__trigger { display: inline-flex; }",
|
|
154
|
-
);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("APEX_NAV_TOGGLE_RESPONSIVE_CSS declares .nav as the positioned ancestor for the open dropdown", () => {
|
|
158
|
-
// `.nav-toggle[open] > .nav-links` is `position: absolute; top: calc(100% +
|
|
159
|
-
// 0.5rem); left: 0; right: 0` — it needs `.nav { position: relative; }` as
|
|
160
|
-
// its containing block. renderApexPage gets that from CHROME_LIGHT, but a
|
|
161
|
-
// standalone renderApexHeader() consumer's own header CSS typically doesn't
|
|
162
|
-
// declare it, and without it the dropdown opens off-screen against the
|
|
163
|
-
// viewport instead of under the header (fw#2125 follow-up: verified none of
|
|
164
|
-
// money-horse/show-pony/the docs sample's HEADER_CSS set it).
|
|
165
|
-
expect(APEX_NAV_TOGGLE_RESPONSIVE_CSS).toContain(".nav { position: relative; }");
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
test("pricing: featured card gets badge + featured class, cap line precedes benefits", () => {
|
|
169
|
-
const html = renderApexPage(
|
|
170
|
-
page({
|
|
171
|
-
sections: [
|
|
172
|
-
{
|
|
173
|
-
kind: "pricing-grid",
|
|
174
|
-
heading: "P",
|
|
175
|
-
tiers: [
|
|
176
|
-
{
|
|
177
|
-
name: "Pro",
|
|
178
|
-
amount: "9 €",
|
|
179
|
-
priceSuffix: "/Monat",
|
|
180
|
-
featured: true,
|
|
181
|
-
badge: "Beliebt",
|
|
182
|
-
capLine: "50 X",
|
|
183
|
-
benefits: ["b1"],
|
|
184
|
-
cta: { label: "Go", href: "/s" },
|
|
185
|
-
},
|
|
186
|
-
],
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
}),
|
|
190
|
-
);
|
|
191
|
-
expect(html).toContain("price-card--featured");
|
|
192
|
-
expect(html).toContain('class="price-badge">Beliebt');
|
|
193
|
-
expect(html).toContain("/Monat");
|
|
194
|
-
// cap line must render before the first benefit
|
|
195
|
-
expect(html.indexOf("price-cap")).toBeLessThan(html.indexOf("b1"));
|
|
196
|
-
// featured tier without explicit cta variant → primary button
|
|
197
|
-
expect(html).toContain('class="btn btn-primary" href="/s"');
|
|
198
|
-
|
|
199
|
-
expect(html).not.toContain("btn-link");
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
test("cta variant link renders a plain anchor, default renders a button", () => {
|
|
203
|
-
const html = renderApexPage(
|
|
204
|
-
page({
|
|
205
|
-
header: {
|
|
206
|
-
brand: { href: "/", label: "Acme" },
|
|
207
|
-
actions: [
|
|
208
|
-
{ label: "Login", href: "/login", variant: "link" },
|
|
209
|
-
{ label: "Start", href: "/signup" },
|
|
210
|
-
],
|
|
211
|
-
},
|
|
212
|
-
}),
|
|
213
|
-
);
|
|
214
|
-
expect(html).toContain('<a href="/login">Login</a>'); // link → no class
|
|
215
|
-
expect(html).toContain('<a class="btn btn-primary" href="/signup">Start</a>');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("pricing-tier cta variant link renders a plain anchor via renderCta, not a btn-link class", () => {
|
|
219
|
-
const html = renderApexPage(
|
|
220
|
-
page({
|
|
221
|
-
sections: [
|
|
222
|
-
{
|
|
223
|
-
kind: "pricing-grid",
|
|
224
|
-
heading: "Preise",
|
|
225
|
-
tiers: [
|
|
226
|
-
{
|
|
227
|
-
name: "Free",
|
|
228
|
-
amount: "0 €",
|
|
229
|
-
benefits: ["b1"],
|
|
230
|
-
cta: { label: "Los", href: "/free", variant: "link" },
|
|
231
|
-
},
|
|
232
|
-
],
|
|
233
|
-
},
|
|
234
|
-
],
|
|
235
|
-
}),
|
|
236
|
-
);
|
|
237
|
-
expect(html).toContain('<a href="/free">Los</a>');
|
|
238
|
-
expect(html).not.toContain("btn-link");
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test("footer --footer-cols reflects column count and survives without columns", () => {
|
|
242
|
-
const twoCols = renderApexPage(
|
|
243
|
-
page({
|
|
244
|
-
footer: {
|
|
245
|
-
brand: { label: "Acme" },
|
|
246
|
-
columns: [
|
|
247
|
-
{ heading: "A", links: [{ href: "/a", label: "a" }] },
|
|
248
|
-
{ heading: "B", links: [{ href: "/b", label: "b" }] },
|
|
249
|
-
],
|
|
250
|
-
},
|
|
251
|
-
}),
|
|
252
|
-
);
|
|
253
|
-
expect(twoCols).toContain("--footer-cols:2");
|
|
254
|
-
// 502/2: --footer-cols:0 makes grid-template-columns's repeat(0, 1fr)
|
|
255
|
-
// invalid CSS — the browser drops the whole declaration. Clamped to a
|
|
256
|
-
// minimum of 1 so a footer with no columns still gets a valid grid.
|
|
257
|
-
expect(renderApexPage(page())).toContain("--footer-cols:1");
|
|
258
|
-
expect(renderApexPage(page())).not.toContain("--footer-cols:0");
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test("renders every section kind without throwing or leaking undefined", () => {
|
|
262
|
-
const html = renderApexPage(
|
|
263
|
-
page({
|
|
264
|
-
sections: [
|
|
265
|
-
{ kind: "hero", title: "h", tagline: "t" },
|
|
266
|
-
{ kind: "feature-grid", heading: "F", items: [{ title: "x", desc: "d" }] },
|
|
267
|
-
{
|
|
268
|
-
kind: "pricing-grid",
|
|
269
|
-
heading: "P",
|
|
270
|
-
tiers: [
|
|
271
|
-
{ name: "Free", amount: "0 €", benefits: ["b"], cta: { label: "g", href: "/s" } },
|
|
272
|
-
],
|
|
273
|
-
},
|
|
274
|
-
{ kind: "info-grid", items: [{ title: "t", desc: "d" }] },
|
|
275
|
-
{ kind: "final-cta", heading: "c", cta: { label: "g", href: "/s" } },
|
|
276
|
-
{ kind: "html", html: "<div id='raw'></div>" },
|
|
277
|
-
],
|
|
278
|
-
}),
|
|
279
|
-
);
|
|
280
|
-
expect(html).not.toContain("undefined");
|
|
281
|
-
expect(html).not.toContain("[object Object]");
|
|
282
|
-
expect(html).toContain("id='raw'");
|
|
283
|
-
expect(html).toContain("trust-item");
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("renders robots meta when set, absent when omitted", () => {
|
|
287
|
-
const without = renderApexPage(page());
|
|
288
|
-
expect(without).not.toContain("robots");
|
|
289
|
-
const withRobots = renderApexPage(
|
|
290
|
-
page({ head: { lang: "de", title: "T", description: "D", robots: "noindex, nofollow" } }),
|
|
291
|
-
);
|
|
292
|
-
expect(withRobots).toContain('<meta name="robots" content="noindex, nofollow" />');
|
|
293
|
-
});
|
|
294
|
-
test("omits both description meta tags when description is empty", () => {
|
|
295
|
-
const html = renderApexPage(page({ head: { lang: "de", title: "T", description: "" } }));
|
|
296
|
-
expect(html).not.toContain('<meta name="description"');
|
|
297
|
-
expect(html).not.toContain('<meta property="og:description"');
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test("renders og:site_name and og:locale", () => {
|
|
301
|
-
const html = renderApexPage(
|
|
302
|
-
page({
|
|
303
|
-
head: {
|
|
304
|
-
lang: "de",
|
|
305
|
-
title: "T",
|
|
306
|
-
description: "D",
|
|
307
|
-
siteName: "Acme",
|
|
308
|
-
locale: "de_DE",
|
|
309
|
-
},
|
|
310
|
-
}),
|
|
311
|
-
);
|
|
312
|
-
expect(html).toContain('<meta property="og:site_name" content="Acme" />');
|
|
313
|
-
expect(html).toContain('<meta property="og:locale" content="de_DE" />');
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test("renders twitter:card when ogImage is set, plus twitter:site", () => {
|
|
317
|
-
const html = renderApexPage(
|
|
318
|
-
page({
|
|
319
|
-
head: {
|
|
320
|
-
lang: "de",
|
|
321
|
-
title: "T",
|
|
322
|
-
description: "D",
|
|
323
|
-
ogImage: "https://example.com/image.png",
|
|
324
|
-
twitterSite: "@acme",
|
|
325
|
-
},
|
|
326
|
-
}),
|
|
327
|
-
);
|
|
328
|
-
expect(html).toContain('<meta name="twitter:card" content="summary_large_image" />');
|
|
329
|
-
expect(html).toContain('<meta name="twitter:site" content="@acme" />');
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
test("does not render twitter:card without ogImage", () => {
|
|
333
|
-
const html = renderApexPage(
|
|
334
|
-
page({
|
|
335
|
-
head: { lang: "de", title: "T", description: "D", twitterSite: "@acme" },
|
|
336
|
-
}),
|
|
337
|
-
);
|
|
338
|
-
expect(html).not.toContain("twitter:card");
|
|
339
|
-
expect(html).toContain("twitter:site");
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
test("renders preconnect links", () => {
|
|
343
|
-
const html = renderApexPage(
|
|
344
|
-
page({
|
|
345
|
-
head: {
|
|
346
|
-
lang: "de",
|
|
347
|
-
title: "T",
|
|
348
|
-
description: "D",
|
|
349
|
-
preconnects: ["https://fonts.example.com", "https://api.example.com"],
|
|
350
|
-
},
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
expect(html).toContain('<link rel="preconnect" href="https://fonts.example.com" />');
|
|
354
|
-
expect(html).toContain('<link rel="preconnect" href="https://api.example.com" />');
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
test("hero screenshot includes apex lightbox chrome", () => {
|
|
358
|
-
const html = renderApexPage(
|
|
359
|
-
page({
|
|
360
|
-
sections: [
|
|
361
|
-
{
|
|
362
|
-
kind: "hero",
|
|
363
|
-
title: "h",
|
|
364
|
-
tagline: "t",
|
|
365
|
-
screenshot: { src: "/shots/demo.png", alt: "Dashboard" },
|
|
366
|
-
},
|
|
367
|
-
],
|
|
368
|
-
}),
|
|
369
|
-
);
|
|
370
|
-
expect(html).toContain('class="shot-frame"');
|
|
371
|
-
expect(html).toContain('<dialog id="apex-lightbox"');
|
|
372
|
-
expect(html).toContain("apex-lightbox");
|
|
373
|
-
expect(html).toContain(".shot-frame img");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("renders schemaJson as json-ld script tag", () => {
|
|
377
|
-
const html = renderApexPage(
|
|
378
|
-
page({
|
|
379
|
-
head: {
|
|
380
|
-
lang: "de",
|
|
381
|
-
title: "T",
|
|
382
|
-
description: "D",
|
|
383
|
-
schemaJson: { "@context": "https://schema.org", "@type": "WebSite", name: "Acme" },
|
|
384
|
-
},
|
|
385
|
-
}),
|
|
386
|
-
);
|
|
387
|
-
expect(html).toContain('<script type="application/ld+json">');
|
|
388
|
-
expect(html).toContain('{"@context":"https://schema.org","@type":"WebSite","name":"Acme"}');
|
|
389
|
-
expect(html).toContain("</script>");
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
test("header .nav rule doesn't reset the .container's horizontal padding (#2038)", () => {
|
|
393
|
-
const html = renderApexPage(page());
|
|
394
|
-
const navRule = html.match(/\.nav\s*\{[^}]*\}/)?.[0];
|
|
395
|
-
expect(navRule).toBeDefined();
|
|
396
|
-
// A `padding`/`padding-inline`/`padding-left`/`padding-right` shorthand or
|
|
397
|
-
// longhand here would win the cascade over .container's horizontal padding
|
|
398
|
-
// (same specificity, .nav declared later) and zero it out on <div class="container nav">.
|
|
399
|
-
expect(navRule).not.toMatch(/padding(-inline|-left|-right)?\s*:/);
|
|
400
|
-
});
|
|
401
|
-
});
|