create-ailk 0.27.0 → 0.28.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/README.md +84 -7
- package/dist/add-module-core.js +2 -1
- package/dist/add-module.d.ts +2 -1
- package/dist/add-module.js +9 -4
- package/dist/add-route.js +46 -19
- package/dist/body-heading.d.ts +56 -0
- package/dist/body-heading.js +394 -0
- package/dist/cli.js +28 -1
- package/dist/content-payloads.js +2 -2
- package/dist/derive-architecture.js +8 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -1
- package/dist/lib/auth-secret-file.d.ts +6 -2
- package/dist/lib/auth-secret-file.js +46 -20
- package/dist/lib/env-catalog.js +20 -0
- package/dist/lib/site-modules.d.ts +8 -0
- package/dist/lib/site-modules.js +34 -0
- package/dist/module-architecture.js +23 -14
- package/dist/paid-sections.d.ts +17 -0
- package/dist/paid-sections.js +39 -0
- package/dist/programmatic.d.ts +16 -0
- package/dist/programmatic.js +73 -4
- package/dist/remove-module.d.ts +98 -0
- package/dist/remove-module.js +620 -0
- package/dist/seed-flows.d.ts +2 -2
- package/dist/seed-flows.js +16 -9
- package/dist/site-roster.js +4 -1
- package/dist/starter-grid.d.ts +26 -0
- package/dist/starter-grid.js +34 -0
- package/dist/surfaces.d.ts +26 -0
- package/dist/surfaces.js +105 -9
- package/dist/sync-routes.d.ts +7 -0
- package/dist/sync-routes.js +53 -6
- package/dist/template-pages.d.ts +36 -0
- package/dist/template-pages.js +129 -0
- package/dist/topology.d.ts +2 -1
- package/dist/topology.js +4 -2
- package/dist/update.d.ts +3 -1
- package/dist/update.js +38 -4
- package/package.json +3 -3
- package/release-scope.json +2 -1
- package/site-templates/aliases/hub.TEMPLATE-MANIFEST.json +115 -0
- package/templates/.env.example +5 -2
- package/templates/apps/api/.env.example +9 -5
- package/templates/apps/api/CLAUDE.md +14 -9
- package/templates/apps/api/src/__tests__/vercel-handler.test.ts +37 -0
- package/templates/apps/api/src/vercel-handler.ts +8 -1
- package/templates/apps/mcp/CLAUDE.md +18 -10
- package/templates/apps/web/.env.example +25 -11
- package/templates/apps/web/CLAUDE.md +45 -4
- package/templates/apps/web/__tests__/setup.ts +7 -0
- package/templates/apps/web/app/.well-known/mcp.json/route.ts +7 -7
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/[pieceId]/versions/[version]/file/route.ts +61 -2
- package/templates/apps/web/app/[locale]/(authed)/account/artifacts/__tests__/file-route.test.ts +81 -0
- package/templates/apps/web/app/[locale]/(authed)/account/connect/__tests__/page.test.tsx +197 -0
- package/templates/apps/web/app/[locale]/(authed)/account/connect/page.tsx +152 -0
- package/templates/apps/web/app/[locale]/about/__tests__/about-routes.test.tsx +2 -2
- package/templates/apps/web/app/[locale]/careers/[slug]/[action]/page.tsx +76 -21
- package/templates/apps/web/app/[locale]/careers/[slug]/apply.actions.ts +46 -16
- package/templates/apps/web/app/[locale]/careers/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/careers/__tests__/apply.actions.test.ts +180 -2
- package/templates/apps/web/app/[locale]/careers/__tests__/careers-set.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/careers/__tests__/job-apply-page.test.tsx +122 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/case-studies/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/hub-children.test.tsx +65 -0
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/case-studies/page.tsx +23 -11
- package/templates/apps/web/app/[locale]/checkout/__tests__/thank-you-account.test.tsx +11 -1
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/consent.actions.ts +44 -6
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +4 -4
- package/templates/apps/web/app/[locale]/contact/__tests__/contact.actions.test.ts +269 -42
- package/templates/apps/web/app/[locale]/contact/contact.actions.ts +88 -30
- package/templates/apps/web/app/[locale]/courses/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/courses/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/courses/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/events/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/events/page.tsx +23 -7
- package/templates/apps/web/app/[locale]/features/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/features/page.tsx +23 -25
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/flow.actions.test.ts +95 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/save.actions.test.ts +193 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/email-moment.ts +92 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/flow.actions.ts +10 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsClient.tsx +513 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/ResultsEmailStep.tsx +122 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/ResultsClient.no-commerce.test.tsx +195 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/landing.test.tsx +293 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/__tests__/page.test.tsx +397 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/checkout-seam.ts +74 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/results/[payload]/page.tsx +264 -0
- package/templates/apps/web/app/[locale]/flows/[slug]/save.actions.ts +75 -0
- package/templates/apps/web/app/[locale]/for/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +5 -1
- package/templates/apps/web/app/[locale]/for/page.tsx +23 -25
- package/templates/apps/web/app/[locale]/how-to/[slug]/page.tsx +20 -3
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/integrations/[slug]/page.tsx +25 -4
- package/templates/apps/web/app/[locale]/integrations/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/integrations/page.tsx +23 -11
- package/templates/apps/web/app/[locale]/layout.tsx +157 -139
- package/templates/apps/web/app/[locale]/page.tsx +51 -3
- package/templates/apps/web/app/[locale]/partners/[slug]/page.tsx +16 -3
- package/templates/apps/web/app/[locale]/partners/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/partners/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/portfolio/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/portfolio/page.tsx +25 -24
- package/templates/apps/web/app/[locale]/pricing/[slug]/__tests__/page.test.tsx +321 -0
- package/templates/apps/web/app/[locale]/pricing/[slug]/page.tsx +87 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/offerings.test.tsx +3 -0
- package/templates/apps/web/app/[locale]/pricing/__tests__/pricing-pages.test.tsx +215 -0
- package/templates/apps/web/app/[locale]/pricing/page.tsx +56 -341
- package/templates/apps/web/app/[locale]/products/[slug]/page.tsx +21 -4
- package/templates/apps/web/app/[locale]/products/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +8 -2
- package/templates/apps/web/app/[locale]/products/page.tsx +22 -6
- package/templates/apps/web/app/[locale]/projects/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -0
- package/templates/apps/web/app/[locale]/projects/page.tsx +18 -11
- package/templates/apps/web/app/[locale]/resources/[slug]/page.tsx +25 -4
- package/templates/apps/web/app/[locale]/resources/__tests__/hub-children.test.tsx +38 -0
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +6 -2
- package/templates/apps/web/app/[locale]/resources/page.tsx +20 -10
- package/templates/apps/web/app/[locale]/services/[slug]/[child]/page.tsx +68 -0
- package/templates/apps/web/app/[locale]/services/[slug]/page.tsx +64 -9
- package/templates/apps/web/app/[locale]/services/__tests__/hub-children.test.tsx +158 -0
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +12 -2
- package/templates/apps/web/app/[locale]/services/__tests__/service-detail-sections.test.tsx +223 -0
- package/templates/apps/web/app/[locale]/services/__tests__/service-families.test.tsx +368 -0
- package/templates/apps/web/app/[locale]/services/page.tsx +45 -19
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/team/__tests__/hub-children.test.tsx +32 -0
- package/templates/apps/web/app/[locale]/team/page.tsx +25 -24
- package/templates/apps/web/app/[locale]/tech-articles/[slug]/page.tsx +24 -3
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/templates/[slug]/__tests__/page.test.tsx +203 -0
- package/templates/apps/web/app/[locale]/templates/[slug]/page.tsx +81 -0
- package/templates/apps/web/app/[locale]/templates/__tests__/page.test.tsx +236 -0
- package/templates/apps/web/app/[locale]/templates/page.tsx +108 -0
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/[slug]/page.tsx +24 -3
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/api/auth/hub/authorize/__tests__/route.test.ts +204 -2
- package/templates/apps/web/app/api/auth/hub/authorize/route.ts +151 -33
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/__tests__/route.test.ts +233 -7
- package/templates/apps/web/app/api/offerings/checkout/[lookupKey]/route.ts +92 -41
- package/templates/apps/web/app/api/offerings/checkout/__tests__/route.test.ts +77 -39
- package/templates/apps/web/app/llms.txt/route.ts +41 -3
- package/templates/apps/web/app/robots.txt/route.ts +5 -1
- package/templates/apps/web/app/sitemap.xml/route.ts +45 -3
- package/templates/apps/web/auth.ts +102 -50
- package/templates/apps/web/components/signed-in-nav.tsx +8 -1
- package/templates/apps/web/lib/__tests__/bot-check.test.ts +254 -0
- package/templates/apps/web/lib/__tests__/consent-visitor.test.ts +212 -13
- package/templates/apps/web/lib/__tests__/deck-page.test.ts +13 -1
- package/templates/apps/web/lib/__tests__/docs-index.test.ts +93 -0
- package/templates/apps/web/lib/__tests__/docs-path.test.ts +168 -0
- package/templates/apps/web/lib/__tests__/hub-children.fixtures.ts +278 -0
- package/templates/apps/web/lib/__tests__/nav-label-keys.test.ts +138 -0
- package/templates/apps/web/lib/__tests__/newsletter.actions.test.ts +178 -2
- package/templates/apps/web/lib/__tests__/offerings-pricing.test.ts +183 -0
- package/templates/apps/web/lib/__tests__/page-chrome.test.ts +412 -0
- package/templates/apps/web/lib/__tests__/pricing-page.test.ts +142 -0
- package/templates/apps/web/lib/__tests__/relay.test.ts +202 -10
- package/templates/apps/web/lib/__tests__/route-family.test.ts +57 -0
- package/templates/apps/web/lib/__tests__/service-listing.test.ts +111 -0
- package/templates/apps/web/lib/__tests__/services-path.test.ts +96 -0
- package/templates/apps/web/lib/__tests__/site-header.test.ts +151 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +148 -6
- package/templates/apps/web/lib/__tests__/telemetry-scrub.test.ts +84 -0
- package/templates/apps/web/lib/__tests__/template-listing.test.ts +158 -0
- package/templates/apps/web/lib/auth-secret.ts +57 -0
- package/templates/apps/web/lib/bot-check.ts +138 -0
- package/templates/apps/web/lib/consent-visitor.ts +81 -7
- package/templates/apps/web/lib/deck-page.ts +6 -3
- package/templates/apps/web/lib/docs-index.ts +100 -0
- package/templates/apps/web/lib/docs-path.ts +94 -0
- package/templates/apps/web/lib/hub-children.tsx +318 -0
- package/templates/apps/web/lib/modules.generated.ts +9 -0
- package/templates/apps/web/lib/nav-label-check.ts +61 -0
- package/templates/apps/web/lib/nav-label-keys.ts +78 -0
- package/templates/apps/web/lib/newsletter.actions.ts +31 -6
- package/templates/apps/web/lib/offerings-pricing.ts +135 -6
- package/templates/apps/web/lib/page-chrome.ts +367 -0
- package/templates/apps/web/lib/posting-title.ts +26 -4
- package/templates/apps/web/lib/pricing-page.ts +430 -0
- package/templates/apps/web/lib/relay.ts +31 -106
- package/templates/apps/web/lib/route-family.ts +69 -0
- package/templates/apps/web/lib/service-listing.ts +89 -0
- package/templates/apps/web/lib/services-path.ts +97 -0
- package/templates/apps/web/lib/site-header.ts +94 -0
- package/templates/apps/web/lib/telemetry-scrub.ts +68 -3
- package/templates/apps/web/lib/template-listing.ts +169 -0
- package/templates/apps/web/messages/en.json +86 -30
- package/templates/apps/web/middleware.ts +171 -11
- package/templates/apps/web/next.config.mjs +211 -2
- package/templates/apps/web/package.json +1 -1
- package/templates/apps/web/scripts/assert-content-bundled.ts +28 -6
- package/templates/apps/web/scripts/check-nav-labels.ts +39 -0
- package/templates/apps/web/sentry.server.config.ts +30 -2
- package/templates/apps/web/src/lib/__tests__/rate-limit.test.ts +67 -9
- package/templates/apps/web/src/lib/rate-limit.ts +68 -27
- package/templates/apps/web/src/lib/routes.ts +15 -1
- package/versions.json +48 -48
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hub child cards (#10121) for /services. The shared cases live in
|
|
3
|
+
* `lib/__tests__/hub-children.fixtures.ts`; this file sits beside the route so
|
|
4
|
+
* a scaffold without /services drops it with the route (#10157).
|
|
5
|
+
*/
|
|
6
|
+
const mockRenderPage = jest.fn(async (..._args: unknown[]) => "<rendered/>");
|
|
7
|
+
|
|
8
|
+
jest.mock("@working-theory/page-renderer", () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
renderPage: (...args: unknown[]) => mockRenderPage(...args),
|
|
11
|
+
renderPageMetadata: jest.fn(),
|
|
12
|
+
// Never reached — every call in this suite passes its loaders via DI.
|
|
13
|
+
getMdxSlugs: jest.fn(),
|
|
14
|
+
getMdxSource: jest.fn(),
|
|
15
|
+
getPageSections: jest.fn(),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
jest.mock("@working-theory/ui/sections", () => ({
|
|
19
|
+
__esModule: true,
|
|
20
|
+
SectionRenderer: () => null,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// The services hub reads its card labels from the `services` namespace.
|
|
24
|
+
const SERVICE_MESSAGES: Record<string, string> = {
|
|
25
|
+
childrenHeading: "Services in this area",
|
|
26
|
+
viewService: "View service",
|
|
27
|
+
};
|
|
28
|
+
jest.mock("next-intl/server", () => ({
|
|
29
|
+
__esModule: true,
|
|
30
|
+
getTranslations: () =>
|
|
31
|
+
Promise.resolve((key: string) => SERVICE_MESSAGES[key] ?? key),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
import ServicesPage from "../page";
|
|
35
|
+
import {
|
|
36
|
+
CLOSING,
|
|
37
|
+
describeHub,
|
|
38
|
+
HERO,
|
|
39
|
+
type HubPage,
|
|
40
|
+
loaders,
|
|
41
|
+
renderInput,
|
|
42
|
+
type Section,
|
|
43
|
+
} from "../../../../lib/__tests__/hub-children.fixtures";
|
|
44
|
+
|
|
45
|
+
describeHub(
|
|
46
|
+
["services", ServicesPage as HubPage, "ServicesList", "Service", "composed"],
|
|
47
|
+
mockRenderPage,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
describe("/services hub — the #10043 model", () => {
|
|
51
|
+
const services = {
|
|
52
|
+
consulting: {
|
|
53
|
+
title: "Consulting",
|
|
54
|
+
summary: "Short.",
|
|
55
|
+
description: "Long.",
|
|
56
|
+
},
|
|
57
|
+
audit: { title: "Audit", parent: "consulting", description: "Child." },
|
|
58
|
+
training: { title: "Training", description: "Flat." },
|
|
59
|
+
// A blank parent reads as no category, as the category listing reads it.
|
|
60
|
+
workshop: { title: "Workshop", parent: "", description: "Blank parent." },
|
|
61
|
+
};
|
|
62
|
+
const serviceLoaders = (declared: Section[] = [HERO, CLOSING]) => {
|
|
63
|
+
const l = loaders("ServicesList", "Service", {}, declared);
|
|
64
|
+
l.getMdxSlugs.mockResolvedValue(Object.keys(services));
|
|
65
|
+
l.getMdxSource.mockImplementation(async (type: string, slug: string) =>
|
|
66
|
+
type === "ServicesList"
|
|
67
|
+
? {
|
|
68
|
+
frontmatter: { type, slug, title: "Hub", sections: declared },
|
|
69
|
+
body: "",
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
frontmatter: {
|
|
73
|
+
type,
|
|
74
|
+
slug,
|
|
75
|
+
...services[slug as keyof typeof services],
|
|
76
|
+
},
|
|
77
|
+
body: "",
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
return l;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
it("lists categories and uncategorized services, never a service with a parent", async () => {
|
|
84
|
+
const input = await renderInput(
|
|
85
|
+
mockRenderPage,
|
|
86
|
+
ServicesPage as HubPage,
|
|
87
|
+
serviceLoaders(),
|
|
88
|
+
);
|
|
89
|
+
const sections = await input.getPageSections({});
|
|
90
|
+
expect(sections.map((s) => s.type)).toEqual(["hero", "items", "cta"]);
|
|
91
|
+
expect((sections[1]?.data as { items: unknown[] }).items).toEqual([
|
|
92
|
+
// The category page's card shape: `summary` over `description`.
|
|
93
|
+
{
|
|
94
|
+
title: "Consulting",
|
|
95
|
+
description: "Short.",
|
|
96
|
+
link: { label: "View service", href: "/services/consulting" },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
title: "Training",
|
|
100
|
+
description: "Flat.",
|
|
101
|
+
link: { label: "View service", href: "/services/training" },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
title: "Workshop",
|
|
105
|
+
description: "Blank parent.",
|
|
106
|
+
link: { label: "View service", href: "/services/workshop" },
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("links the cards at the site's services base and re-points the hub's own links", async () => {
|
|
112
|
+
const previous = process.env.NEXT_PUBLIC_AILK_SERVICES_PATH;
|
|
113
|
+
process.env.NEXT_PUBLIC_AILK_SERVICES_PATH = "/capabilities";
|
|
114
|
+
try {
|
|
115
|
+
let MovedPage: HubPage | undefined;
|
|
116
|
+
jest.isolateModules(() => {
|
|
117
|
+
MovedPage = (require("../page") as { default: HubPage }).default;
|
|
118
|
+
});
|
|
119
|
+
const authored: Section = {
|
|
120
|
+
type: "items",
|
|
121
|
+
id: "authored",
|
|
122
|
+
data: {
|
|
123
|
+
items: [
|
|
124
|
+
{
|
|
125
|
+
title: "Consulting (hand-written)",
|
|
126
|
+
link: { label: "Go", href: "/services/consulting" },
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
const input = await renderInput(
|
|
132
|
+
mockRenderPage,
|
|
133
|
+
MovedPage as HubPage,
|
|
134
|
+
serviceLoaders([HERO, authored, CLOSING]),
|
|
135
|
+
);
|
|
136
|
+
const sections = await input.getPageSections({});
|
|
137
|
+
// Consulting keeps its hand-written card; Training continues the grid.
|
|
138
|
+
expect(sections.map((s) => s.id)).toEqual([
|
|
139
|
+
"hero",
|
|
140
|
+
"authored",
|
|
141
|
+
"closing",
|
|
142
|
+
]);
|
|
143
|
+
expect(
|
|
144
|
+
(
|
|
145
|
+
sections[1]?.data as { items: { link: { href: string } }[] }
|
|
146
|
+
).items.map((item) => item.link.href),
|
|
147
|
+
).toEqual([
|
|
148
|
+
"/capabilities/consulting",
|
|
149
|
+
"/capabilities/training",
|
|
150
|
+
"/capabilities/workshop",
|
|
151
|
+
]);
|
|
152
|
+
} finally {
|
|
153
|
+
if (previous === undefined)
|
|
154
|
+
delete process.env.NEXT_PUBLIC_AILK_SERVICES_PATH;
|
|
155
|
+
else process.env.NEXT_PUBLIC_AILK_SERVICES_PATH = previous;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -70,6 +70,8 @@ describe("ServicesPage — snapshot regression", () => {
|
|
|
70
70
|
const getMdxSourceEmpty = jest.fn().mockResolvedValue(null);
|
|
71
71
|
const element = await ServicesPage({
|
|
72
72
|
params: Promise.resolve({ locale: "en" }),
|
|
73
|
+
// No child pages: this snapshot pins the hub's own render (#10121).
|
|
74
|
+
getMdxSlugs: async () => [],
|
|
73
75
|
getMdxSource: getMdxSourceEmpty,
|
|
74
76
|
});
|
|
75
77
|
const { container } = render(element);
|
|
@@ -83,11 +85,14 @@ describe("ServicesPage — snapshot regression", () => {
|
|
|
83
85
|
const getMdxSourcePopulated = jest.fn().mockResolvedValue(source);
|
|
84
86
|
const element = await ServicesPage({
|
|
85
87
|
params: Promise.resolve({ locale: "en" }),
|
|
88
|
+
// No child pages: this snapshot pins the hub's own render (#10121).
|
|
89
|
+
getMdxSlugs: async () => [],
|
|
86
90
|
getMdxSource: getMdxSourcePopulated,
|
|
87
91
|
});
|
|
88
92
|
const { container } = render(element);
|
|
89
93
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
90
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Services","description":"All services.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Service"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><
|
|
94
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Services","description":"All services.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Service"}]}}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium motion-safe:transition motion-safe:duration-(--button-hover-duration) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium motion-safe:transition motion-safe:duration-(--button-hover-duration) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-text-muted) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-remote" data-source="# Services
|
|
95
|
+
|
|
91
96
|
Body."></div></div></article></div></div></div></main>"
|
|
92
97
|
`);
|
|
93
98
|
});
|
|
@@ -113,6 +118,8 @@ describe("ServicesPage — authored sections (#7745)", () => {
|
|
|
113
118
|
};
|
|
114
119
|
const element = await ServicesPage({
|
|
115
120
|
params: Promise.resolve({ locale: "en" }),
|
|
121
|
+
// No child pages: this snapshot pins the hub's own render (#10121).
|
|
122
|
+
getMdxSlugs: async () => [],
|
|
116
123
|
getMdxSource: jest.fn().mockResolvedValue(source),
|
|
117
124
|
});
|
|
118
125
|
const { container } = render(element);
|
|
@@ -125,6 +132,8 @@ describe("ServicesPage — authored sections (#7745)", () => {
|
|
|
125
132
|
it("keeps the mdx-body render for a prose-only page", async () => {
|
|
126
133
|
const element = await ServicesPage({
|
|
127
134
|
params: Promise.resolve({ locale: "en" }),
|
|
135
|
+
// No child pages: this snapshot pins the hub's own render (#10121).
|
|
136
|
+
getMdxSlugs: async () => [],
|
|
128
137
|
getMdxSource: jest.fn().mockResolvedValue(makeServicesListSource()),
|
|
129
138
|
});
|
|
130
139
|
const { container } = render(element);
|
|
@@ -149,7 +158,8 @@ describe("ServiceSlugPage — snapshot regression", () => {
|
|
|
149
158
|
});
|
|
150
159
|
const { container } = render(element);
|
|
151
160
|
expect(container.innerHTML).toMatchInlineSnapshot(`
|
|
152
|
-
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Implementation consulting","description":"Help adapting AI Launch Kit.","mainEntity":{"@context":"https://schema.org","@type":"Service","name":"Implementation consulting","description":"Help adapting AI Launch Kit."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Services","item":"http://localhost:3000/en/services"},{"@type":"ListItem","position":3,"name":"Implementation consulting","item":"http://localhost:3000/en/services/consulting"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><
|
|
161
|
+
"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Implementation consulting","description":"Help adapting AI Launch Kit.","mainEntity":{"@context":"https://schema.org","@type":"Service","name":"Implementation consulting","description":"Help adapting AI Launch Kit."}},{"@context":"https://schema.org","@type":"BreadcrumbList","inLanguage":"en","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http://localhost:3000/en/"},{"@type":"ListItem","position":2,"name":"Services","item":"http://localhost:3000/en/services"},{"@type":"ListItem","position":3,"name":"Implementation consulting","item":"http://localhost:3000/en/services/consulting"}]}]}</script><main id="main"><div class="mx-auto w-full max-w-container-xl px-4 md:px-6 lg:px-8 py-section"><div data-testid="markdown-view-toggle" class="ailk-markdown-view-toggle"><div class="flex justify-end mb-3"><div role="radiogroup" aria-label="Page view" data-testid="markdown-view-toggle" class="inline-flex items-center gap-0.5 rounded-lg p-1 bg-(--color-surface-secondary)"><button class="inline-flex items-center justify-center gap-2 font-medium motion-safe:transition motion-safe:duration-(--button-hover-duration) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) bg-(--color-bg) text-(--color-fg) shadow-sm" type="button" role="radio" aria-checked="true" aria-label="Rendered" tabindex="0" data-testid="markdown-view-toggle-rendered">Rendered</button><button class="inline-flex items-center justify-center gap-2 font-medium motion-safe:transition motion-safe:duration-(--button-hover-duration) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&>svg]:shrink-0 text-(--color-fg) not-disabled:hover:bg-(--button-hover-quiet-bg) not-disabled:hover:text-(--button-hover-quiet-fg) not-disabled:hover:opacity-(--button-hover-opacity) not-disabled:hover:transform-(--button-hover-transform) not-disabled:hover:shadow-(--button-hover-shadow) not-disabled:active:bg-gray-200 not-disabled:active:text-(--color-fg) rounded-control px-control-x h-(--control-height-thin) text-caption leading-none rounded-md px-3 text-body-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--color-accent) focus-visible:ring-offset-1 min-w-(--control-height-thin) min-h-(--control-height-big) text-(--color-text-muted) hover:text-(--color-fg)" type="button" role="radio" aria-checked="false" aria-label="Markdown" tabindex="-1" data-testid="markdown-view-toggle-markdown">Markdown</button></div></div><div role="status" aria-live="polite" aria-atomic="true" class="sr-only" data-testid="markdown-view-toggle-status">Showing Rendered view</div><div data-testid="markdown-view-rendered"><article data-testid="mdx-body"><div class="ailk-prose max-w-none text-(--color-text-muted) text-body-sm leading-relaxed [&>*:first-child]:mt-0 [&_h2]:mt-block-loose [&_h2]:scroll-mt-24 [&_h2]:text-heading-3 [&_h2]:font-display [&_h2]:text-(--color-fg) [&_h3]:mt-block-loose [&_h3]:scroll-mt-24 [&_h3]:text-heading-4 [&_h3]:font-display [&_h3]:text-(--color-fg) [&_h4]:mt-block-tight [&_h4]:scroll-mt-24 [&_h4]:text-heading-4 [&_h4]:font-display [&_h4]:text-(--color-fg) [&_h5]:mt-block-tight [&_h5]:scroll-mt-24 [&_h5]:text-heading-4 [&_h5]:font-display [&_h5]:text-(--color-fg) [&_h6]:mt-block-tight [&_h6]:scroll-mt-24 [&_h6]:text-heading-4 [&_h6]:font-display [&_h6]:text-(--color-fg) [&_p]:mt-paragraph [&_strong]:font-semibold [&_strong]:text-(--color-fg) [&_em]:italic [&_ul]:mt-paragraph [&_ul]:list-disc [&_ul]:ps-6 [&_ol]:mt-paragraph [&_ol]:list-decimal [&_ol]:ps-6 [&_li]:mt-1 [&_li]:ps-1 [&_li]:marker:text-(--color-text-muted) [&_li>ul]:mt-1 [&_li>ol]:mt-1 [&_blockquote]:mt-paragraph [&_blockquote]:border-s-4 [&_blockquote]:border-(--color-border-strong) [&_blockquote]:ps-4 [&_blockquote]:italic [&_blockquote]:text-(--color-text-muted) [&_hr]:my-block-loose [&_hr]:border-(--color-border) [&_a]:text-(--color-accent) [&_a]:underline [&_a]:underline-offset-4 [&_:not(pre)>code]:rounded [&_:not(pre)>code]:bg-(--color-surface-secondary) [&_:not(pre)>code]:px-1.5 [&_:not(pre)>code]:py-0.5 [&_:not(pre)>code]:text-code [&_:not(pre)>code]:font-mono [&_:not(pre)>code]:text-(--color-fg) [&_pre]:mt-paragraph [&_pre]:overflow-auto [&_pre]:rounded-md [&_pre]:border-hairline [&_pre]:border-(--color-border) [&_pre]:bg-(--color-bg) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-body-sm [&_img]:mt-paragraph [&_img]:h-auto [&_img]:max-w-full [&_img]:rounded-lg [&_table]:mt-paragraph [&_table]:w-full [&_table]:border-collapse [&_table]:text-body-sm [&_th]:border-b [&_th]:border-(--color-border) [&_th]:bg-(--color-surface-secondary) [&_th]:px-3 [&_th]:py-2 [&_th]:text-start [&_th]:font-semibold [&_th]:text-(--color-fg) [&_td]:border-b [&_td]:border-(--color-border) [&_td]:px-3 [&_td]:py-2 [&_kbd]:rounded [&_kbd]:border-hairline [&_kbd]:border-(--color-border) [&_kbd]:bg-(--color-surface-secondary) [&_kbd]:px-1.5 [&_kbd]:py-0.5 [&_kbd]:text-caption [&_kbd]:font-mono" data-testid="mdx-content"><div data-testid="mdx-remote" data-source="# Implementation consulting
|
|
162
|
+
|
|
153
163
|
Body."></div></div></article></div></div></div></main>"
|
|
154
164
|
`);
|
|
155
165
|
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The services detail routes render composed `sections[]` (#10122) — the rule
|
|
3
|
+
* /vs/[slug] (#6878 D10) and the eleven detail routes of PR #10125 apply,
|
|
4
|
+
* here on /services/[slug] and #10043's /services/[slug]/[child]: a page
|
|
5
|
+
* whose frontmatter carries a non-empty `sections[]` renders through
|
|
6
|
+
* renderPage in sections mode (so its JSON-LD is built from the same
|
|
7
|
+
* sections); any other page keeps its MDX body exactly as before. #10043's
|
|
8
|
+
* parent redirect, category children list and one-URL 404 are unchanged.
|
|
9
|
+
*
|
|
10
|
+
* `renderPage` is mocked at the `@working-theory/page-renderer` boundary;
|
|
11
|
+
* `getMdxSource` is supplied per call through each route's DI prop, and the
|
|
12
|
+
* category's child list through a mocked `listDetailPages`.
|
|
13
|
+
*/
|
|
14
|
+
import { isValidElement } from "react";
|
|
15
|
+
|
|
16
|
+
const mockRenderPage = jest.fn(async (..._args: unknown[]) => "<rendered/>");
|
|
17
|
+
const mockListDetailPages = jest.fn(
|
|
18
|
+
async (..._args: unknown[]) => [] as unknown[],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
jest.mock("@working-theory/page-renderer", () => ({
|
|
22
|
+
__esModule: true,
|
|
23
|
+
renderPage: (...args: unknown[]) => mockRenderPage(...args),
|
|
24
|
+
renderPageMetadata: jest.fn(),
|
|
25
|
+
getMdxSource: jest.fn(),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
jest.mock("../../../../lib/route-family", () => ({
|
|
29
|
+
__esModule: true,
|
|
30
|
+
listDetailPages: (...args: unknown[]) => mockListDetailPages(...args),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.mock("next-intl/server", () => ({
|
|
34
|
+
__esModule: true,
|
|
35
|
+
getTranslations: jest.fn(async () => (key: string) => key),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
jest.mock("next/navigation", () => ({
|
|
39
|
+
__esModule: true,
|
|
40
|
+
notFound: jest.fn(() => {
|
|
41
|
+
throw new Error("NEXT_NOT_FOUND");
|
|
42
|
+
}),
|
|
43
|
+
permanentRedirect: jest.fn(() => {
|
|
44
|
+
throw new Error("NEXT_REDIRECT");
|
|
45
|
+
}),
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
import ServiceChildPage from "../[slug]/[child]/page";
|
|
49
|
+
import ServiceSlugPage from "../[slug]/page";
|
|
50
|
+
|
|
51
|
+
const nav = jest.requireMock("next/navigation") as {
|
|
52
|
+
notFound: jest.Mock;
|
|
53
|
+
permanentRedirect: jest.Mock;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const SECTIONS = [
|
|
57
|
+
{ id: "hero", type: "hero", data: {} },
|
|
58
|
+
{ id: "cta", type: "cta", data: {} },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
type Frontmatter = Record<string, unknown>;
|
|
62
|
+
|
|
63
|
+
function service(slug: string, extra: Frontmatter = {}) {
|
|
64
|
+
return {
|
|
65
|
+
frontmatter: {
|
|
66
|
+
type: "Service",
|
|
67
|
+
slug,
|
|
68
|
+
title: slug,
|
|
69
|
+
description: `${slug} description.`,
|
|
70
|
+
...extra,
|
|
71
|
+
},
|
|
72
|
+
body: "Prose body.",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A `getMdxSource` over a slug → source table; anything else is null. */
|
|
77
|
+
function sources(table: Record<string, ReturnType<typeof service>>): jest.Mock {
|
|
78
|
+
return jest.fn(async (_type: string, slug: string) => table[slug] ?? null);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function passedInput(): Record<string, unknown> {
|
|
82
|
+
return mockRenderPage.mock.calls[0]?.[0] as Record<string, unknown>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
jest.clearAllMocks();
|
|
87
|
+
mockListDetailPages.mockResolvedValue([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("/services/[slug]", () => {
|
|
91
|
+
const params = Promise.resolve({ locale: "en", slug: "positioning" });
|
|
92
|
+
|
|
93
|
+
it("renders a page with a non-empty sections[] as sections", async () => {
|
|
94
|
+
const getMdxSource = sources({
|
|
95
|
+
positioning: service("positioning", { sections: SECTIONS }),
|
|
96
|
+
});
|
|
97
|
+
await ServiceSlugPage({ params, getMdxSource });
|
|
98
|
+
|
|
99
|
+
const input = passedInput();
|
|
100
|
+
expect(input.type).toBe("Service");
|
|
101
|
+
expect(input.slug).toBe("positioning");
|
|
102
|
+
expect(input.bodyMode).toBeUndefined();
|
|
103
|
+
expect(input.populatedDescriptionTestId).toBeUndefined();
|
|
104
|
+
expect(input.getMdxSource).toBe(getMdxSource);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("renders a page without sections as its MDX body, as before", async () => {
|
|
108
|
+
await ServiceSlugPage({
|
|
109
|
+
params,
|
|
110
|
+
getMdxSource: sources({ positioning: service("positioning") }),
|
|
111
|
+
});
|
|
112
|
+
expect(passedInput().bodyMode).toBe("mdx-body");
|
|
113
|
+
expect(passedInput().populatedDescriptionTestId).toBe(
|
|
114
|
+
"service-description",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("treats an empty sections[] as a prose page", async () => {
|
|
119
|
+
await ServiceSlugPage({
|
|
120
|
+
params,
|
|
121
|
+
getMdxSource: sources({
|
|
122
|
+
positioning: service("positioning", { sections: [] }),
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
expect(passedInput().bodyMode).toBe("mdx-body");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("leaves an unknown slug to renderPage's 404 (mdx-body mode)", async () => {
|
|
129
|
+
await ServiceSlugPage({ params, getMdxSource: sources({}) });
|
|
130
|
+
expect(passedInput().bodyMode).toBe("mdx-body");
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("a sections category still lists its child services after its sections", async () => {
|
|
134
|
+
mockListDetailPages.mockResolvedValue([
|
|
135
|
+
{
|
|
136
|
+
slug: "audit",
|
|
137
|
+
frontmatter: { ...service("audit").frontmatter, parent: "positioning" },
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
await ServiceSlugPage({
|
|
141
|
+
params,
|
|
142
|
+
getMdxSource: sources({
|
|
143
|
+
positioning: service("positioning", { sections: SECTIONS }),
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
expect(passedInput().bodyMode).toBeUndefined();
|
|
147
|
+
expect(isValidElement(passedInput().trailing)).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("a sections service with a parent still redirects to its category URL", async () => {
|
|
151
|
+
await expect(
|
|
152
|
+
ServiceSlugPage({
|
|
153
|
+
params,
|
|
154
|
+
getMdxSource: sources({
|
|
155
|
+
positioning: service("positioning", {
|
|
156
|
+
parent: "strategy",
|
|
157
|
+
sections: SECTIONS,
|
|
158
|
+
}),
|
|
159
|
+
}),
|
|
160
|
+
}),
|
|
161
|
+
).rejects.toThrow("NEXT_REDIRECT");
|
|
162
|
+
expect(nav.permanentRedirect).toHaveBeenCalledWith(
|
|
163
|
+
"/services/strategy/positioning",
|
|
164
|
+
);
|
|
165
|
+
expect(mockRenderPage).not.toHaveBeenCalled();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("/services/[slug]/[child]", () => {
|
|
170
|
+
const params = Promise.resolve({
|
|
171
|
+
locale: "en",
|
|
172
|
+
slug: "strategy",
|
|
173
|
+
child: "positioning",
|
|
174
|
+
});
|
|
175
|
+
const category = service("strategy");
|
|
176
|
+
|
|
177
|
+
it("renders a child with a non-empty sections[] as sections", async () => {
|
|
178
|
+
const getMdxSource = sources({
|
|
179
|
+
strategy: category,
|
|
180
|
+
positioning: service("positioning", {
|
|
181
|
+
parent: "strategy",
|
|
182
|
+
sections: SECTIONS,
|
|
183
|
+
}),
|
|
184
|
+
});
|
|
185
|
+
await ServiceChildPage({ params, getMdxSource });
|
|
186
|
+
|
|
187
|
+
const input = passedInput();
|
|
188
|
+
expect(input.type).toBe("Service");
|
|
189
|
+
expect(input.slug).toBe("positioning");
|
|
190
|
+
expect(input.bodyMode).toBeUndefined();
|
|
191
|
+
expect(input.populatedDescriptionTestId).toBeUndefined();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("renders a child without sections as its MDX body, as before", async () => {
|
|
195
|
+
await ServiceChildPage({
|
|
196
|
+
params,
|
|
197
|
+
getMdxSource: sources({
|
|
198
|
+
strategy: category,
|
|
199
|
+
positioning: service("positioning", { parent: "strategy" }),
|
|
200
|
+
}),
|
|
201
|
+
});
|
|
202
|
+
expect(passedInput().bodyMode).toBe("mdx-body");
|
|
203
|
+
expect(passedInput().populatedDescriptionTestId).toBe(
|
|
204
|
+
"service-description",
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("a sections child under the wrong category is still a 404", async () => {
|
|
209
|
+
await expect(
|
|
210
|
+
ServiceChildPage({
|
|
211
|
+
params,
|
|
212
|
+
getMdxSource: sources({
|
|
213
|
+
strategy: category,
|
|
214
|
+
positioning: service("positioning", {
|
|
215
|
+
parent: "other",
|
|
216
|
+
sections: SECTIONS,
|
|
217
|
+
}),
|
|
218
|
+
}),
|
|
219
|
+
}),
|
|
220
|
+
).rejects.toThrow("NEXT_NOT_FOUND");
|
|
221
|
+
expect(mockRenderPage).not.toHaveBeenCalled();
|
|
222
|
+
});
|
|
223
|
+
});
|