create-ailk 0.6.1 → 0.7.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 +43 -4
- package/component-catalog.json +146 -21
- package/component-catalog.md +9 -6
- package/dist/add-module-core.d.ts +22 -0
- package/dist/add-module-core.js +50 -0
- package/dist/add-module.d.ts +89 -0
- package/dist/add-module.js +334 -0
- package/dist/category-profile.d.ts +126 -0
- package/dist/category-profile.js +262 -0
- package/dist/cli.js +73 -11
- package/dist/index.d.ts +4 -2
- package/dist/index.js +6 -1
- package/dist/lib/extract-module-bundle.d.ts +56 -1
- package/dist/lib/extract-module-bundle.js +165 -13
- package/dist/lib/license-key-files.d.ts +15 -0
- package/dist/lib/license-key-files.js +102 -0
- package/dist/lib/pro-callout.d.ts +16 -10
- package/dist/lib/pro-callout.js +26 -3
- package/dist/license-set.d.ts +34 -0
- package/dist/license-set.js +194 -0
- package/dist/module-architecture.d.ts +20 -0
- package/dist/module-architecture.js +77 -19
- package/dist/modules/commerce/0.6.1.tar.gz +0 -0
- package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
- package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
- package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
- package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
- package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
- package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
- package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
- package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
- package/dist/modules/workspace/0.6.1.tar.gz +0 -0
- package/dist/parse-args.d.ts +7 -0
- package/dist/parse-args.js +23 -1
- package/dist/programmatic.d.ts +46 -0
- package/dist/programmatic.js +130 -9
- package/dist/site-chrome.d.ts +18 -7
- package/dist/site-chrome.js +32 -14
- package/dist/site-roster.d.ts +127 -0
- package/dist/site-roster.js +494 -0
- package/dist/surfaces.d.ts +20 -7
- package/dist/surfaces.js +149 -28
- package/dist/sync-routes.d.ts +9 -0
- package/dist/sync-routes.js +2 -2
- package/dist/template-manifest.d.ts +58 -2
- package/dist/template-manifest.js +196 -69
- package/package.json +6 -4
- package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
- package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
- package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
- package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
- package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
- package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
- package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
- package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
- package/site-templates/designs/minimalism/DESIGN.md +162 -0
- package/site-templates/designs/standard/DESIGN.md +162 -0
- package/site-templates/marketing-site/DESIGN.md +55 -41
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
- package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
- package/site-templates/marketing-site/ailk-template.json +1 -0
- package/site-templates/marketing-site-free/DESIGN.md +55 -41
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
- package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
- package/site-templates/marketing-site-free/ailk-template.json +3 -2
- package/site-templates/minimalism-site/DESIGN.md +54 -40
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
- package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
- package/site-templates/minimalism-site/ailk-template.json +3 -2
- package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
- package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
- package/site-templates/saas-product/DESIGN.md +55 -41
- package/site-templates/simple-service/DESIGN.md +55 -41
- package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
- package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
- package/site-templates/simple-service/ailk-template.json +4 -3
- package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
- package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
- package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
- package/templates/.claude/settings.json +15 -0
- package/templates/.claude/skills/README.md +8 -8
- package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
- package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
- package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
- package/templates/.claude/skills/add-page/references/page-types.md +111 -0
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
- package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
- package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
- package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
- package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
- package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
- package/templates/.claude/skills/fill-page/SKILL.md +180 -0
- package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
- package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
- package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
- package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
- package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
- package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
- package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
- package/templates/.github/workflows/ci.yml +5 -0
- package/templates/CLAUDE.md +8 -2
- package/templates/DESIGN.md +52 -45
- package/templates/README.md +108 -46
- package/templates/apps/api/.env.example +2 -1
- package/templates/apps/api/package.json +1 -1
- package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
- package/templates/apps/api/src/config/index.ts +19 -6
- package/templates/apps/api/src/lib/license-key-store.ts +45 -0
- package/templates/apps/api/src/lib/site.ts +5 -3
- package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
- package/templates/apps/api/src/middleware/auth.ts +9 -0
- package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
- package/templates/apps/api/src/routes/content/get-page.ts +86 -2
- package/templates/apps/api/src/server.ts +6 -0
- package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
- package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
- package/templates/apps/mcp/package.json +2 -1
- package/templates/apps/mcp/src/index.ts +7 -0
- package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
- package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
- package/templates/apps/web/__tests__/setup.ts +18 -0
- package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
- package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
- package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
- package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
- package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
- package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
- package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
- package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
- package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
- package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
- package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
- package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
- package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
- package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
- package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/layout.tsx +113 -51
- package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
- package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
- package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
- package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
- package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
- package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
- package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
- package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
- package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
- package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
- package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
- package/templates/apps/web/app/error-fallback.tsx +17 -45
- package/templates/apps/web/app/not-found.tsx +14 -38
- package/templates/apps/web/app/og/route.tsx +6 -0
- package/templates/apps/web/eslint.config.mjs +3 -0
- package/templates/apps/web/instrumentation.ts +14 -0
- package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
- package/templates/apps/web/lib/license-key-store.ts +52 -0
- package/templates/apps/web/lib/nav-config.ts +64 -12
- package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
- package/templates/apps/web/lib/site-brand.tsx +1 -1
- package/templates/apps/web/lib/site-chrome.ts +3 -3
- package/templates/apps/web/lib/site-identity.ts +134 -0
- package/templates/apps/web/lib/site-theme.ts +11 -0
- package/templates/apps/web/messages/en.json +52 -37
- package/templates/apps/web/public/android-chrome-192x192.png +0 -0
- package/templates/apps/web/public/android-chrome-512x512.png +0 -0
- package/templates/apps/web/public/apple-touch-icon.png +0 -0
- package/templates/apps/web/public/favicon-16x16.png +0 -0
- package/templates/apps/web/public/favicon-32x32.png +0 -0
- package/templates/apps/web/public/favicon.ico +0 -0
- package/templates/apps/web/public/favicon.svg +3 -3
- package/templates/apps/web/public/logomark.svg +6 -3
- package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
- package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
- package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
- package/templates/apps/web/public/logos/salessmyth.svg +4 -0
- package/templates/apps/web/public/site.webmanifest +4 -4
- package/templates/apps/web/public/wordmark-light.svg +4 -0
- package/templates/apps/web/public/wordmark.svg +3 -3
- package/templates/apps/web/security-headers.config.ts +5 -1
- package/templates/content/_site.mdx +23 -0
- package/templates/content/en/AboutPage/about.mdx +150 -144
- package/templates/content/en/BlogList/blog.mdx +105 -0
- package/templates/content/en/ContactPage/contact.mdx +44 -85
- package/templates/content/en/FAQPage/faq.mdx +86 -57
- package/templates/content/en/HomePage/home.mdx +276 -217
- package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
- package/templates/content/en/SoftwareProduct/product.mdx +175 -0
- package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
- package/templates/database/CHANGELOG.md +7 -0
- package/templates/database/package.json +1 -1
- package/templates/package.json +3 -1
- package/templates/tools/lint-architecture.ts +315 -0
- package/templates/tools/tsconfig.json +14 -0
- package/versions.json +33 -33
- package/templates/.claude/skills/add-schema/SKILL.md +0 -277
- package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
- package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
- package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
- package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
- package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
- package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
- package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
- package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
- package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
- package/templates/content/en/LandingPage/blog.mdx +0 -83
- package/templates/content/en/LandingPage/features.mdx +0 -103
- package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
- package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
- package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
|
@@ -50,7 +50,7 @@ describe("careers/page — snapshot regression", () => {
|
|
|
50
50
|
});
|
|
51
51
|
const { container } = render(element);
|
|
52
52
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
53
|
-
`"<main><h1 class="text-heading-1
|
|
53
|
+
`"<main id="main"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Careers</h1><p data-testid="section-empty-state">Career listings coming soon.</p></main>"`,
|
|
54
54
|
);
|
|
55
55
|
});
|
|
56
56
|
|
|
@@ -71,7 +71,7 @@ describe("careers/page — snapshot regression", () => {
|
|
|
71
71
|
});
|
|
72
72
|
const { container } = render(element);
|
|
73
73
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
74
|
-
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Careers at AILK","description":"Join the team building the AX-first platform.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"JobPosting"}]}}</script><main><h1>Careers at AILK</h1><p data-testid="careers-description">Join the team building the AX-first platform.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
74
|
+
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Careers at AILK","description":"Join the team building the AX-first platform.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"JobPosting"}]}}</script><main id="main"><h1>Careers at AILK</h1><p data-testid="careers-description">Join the team building the AX-first platform.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
75
75
|
);
|
|
76
76
|
});
|
|
77
77
|
});
|
|
@@ -96,7 +96,7 @@ describe("careers/[slug]/page — snapshot regression", () => {
|
|
|
96
96
|
});
|
|
97
97
|
const { container } = render(element);
|
|
98
98
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
99
|
-
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Senior Engineer","description":"Build the AILK platform.","mainEntity":{"@context":"https://schema.org","@type":"JobPosting","title":"Senior Engineer","description":"Build the AILK platform."}},{"@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":"Careers","item":"http://localhost:3000/en/careers"},{"@type":"ListItem","position":3,"name":"Senior Engineer","item":"http://localhost:3000/en/careers/senior-engineer"}]}]}</script><main><h1>Senior Engineer</h1><p data-testid="senior-engineer-description">Build the AILK platform.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
99
|
+
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"Senior Engineer","description":"Build the AILK platform.","mainEntity":{"@context":"https://schema.org","@type":"JobPosting","title":"Senior Engineer","description":"Build the AILK platform."}},{"@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":"Careers","item":"http://localhost:3000/en/careers"},{"@type":"ListItem","position":3,"name":"Senior Engineer","item":"http://localhost:3000/en/careers/senior-engineer"}]}]}</script><main id="main"><h1>Senior Engineer</h1><p data-testid="senior-engineer-description">Build the AILK platform.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
100
100
|
);
|
|
101
101
|
});
|
|
102
102
|
|
package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx
CHANGED
|
@@ -46,7 +46,7 @@ describe("case-studies/page — snapshot regression", () => {
|
|
|
46
46
|
});
|
|
47
47
|
const { container } = render(element);
|
|
48
48
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
49
|
-
`"<main><h1 class="text-heading-1
|
|
49
|
+
`"<main id="main"><h1 class="text-heading-1 lg:text-display-sm font-display text-fg hero-type-display">Case Studies</h1><p data-testid="section-empty-state">Case studies listing coming soon.</p></main>"`,
|
|
50
50
|
);
|
|
51
51
|
});
|
|
52
52
|
|
|
@@ -67,7 +67,7 @@ describe("case-studies/page — snapshot regression", () => {
|
|
|
67
67
|
});
|
|
68
68
|
const { container } = render(element);
|
|
69
69
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
70
|
-
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Case Studies","description":"Real-world accounts of teams using AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Article"}]}}</script><main><h1>Case Studies</h1><p data-testid="case-studies-description">Real-world accounts of teams using AI Launch Kit.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
70
|
+
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Case Studies","description":"Real-world accounts of teams using AI Launch Kit.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Article"}]}}</script><main id="main"><h1>Case Studies</h1><p data-testid="case-studies-description">Real-world accounts of teams using AI Launch Kit.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
71
71
|
);
|
|
72
72
|
});
|
|
73
73
|
});
|
|
@@ -92,7 +92,7 @@ describe("case-studies/[slug]/page — snapshot regression", () => {
|
|
|
92
92
|
});
|
|
93
93
|
const { container } = render(element);
|
|
94
94
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
95
|
-
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AILK AEO Rollout","description":"How a team shipped AEO with AI Launch Kit.","mainEntity":{"@context":"https://schema.org","@type":"Article","headline":"AILK AEO Rollout","description":"How a team shipped AEO with 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":"Case Studies","item":"http://localhost:3000/en/case-studies"},{"@type":"ListItem","position":3,"name":"AILK AEO Rollout","item":"http://localhost:3000/en/case-studies/ailk-aeo-rollout"}]}]}</script><main><h1>AILK AEO Rollout</h1><p data-testid="ailk-aeo-rollout-description">How a team shipped AEO with AI Launch Kit.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
95
|
+
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@graph":[{"@context":"https://schema.org","@type":"WebPage","inLanguage":"en","name":"AILK AEO Rollout","description":"How a team shipped AEO with AI Launch Kit.","mainEntity":{"@context":"https://schema.org","@type":"Article","headline":"AILK AEO Rollout","description":"How a team shipped AEO with 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":"Case Studies","item":"http://localhost:3000/en/case-studies"},{"@type":"ListItem","position":3,"name":"AILK AEO Rollout","item":"http://localhost:3000/en/case-studies/ailk-aeo-rollout"}]}]}</script><main id="main"><h1>AILK AEO Rollout</h1><p data-testid="ailk-aeo-rollout-description">How a team shipped AEO with AI Launch Kit.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-rendered">mdx-body-content</div></article></div></div></main>"`,
|
|
96
96
|
);
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -24,8 +24,7 @@ jest.mock("@working-theory/ui", () => ({
|
|
|
24
24
|
}: {
|
|
25
25
|
children: React.ReactNode;
|
|
26
26
|
level: number;
|
|
27
|
-
}): React.ReactElement =>
|
|
28
|
-
React.createElement(`h${level}`, null, children),
|
|
27
|
+
}): React.ReactElement => React.createElement(`h${level}`, null, children),
|
|
29
28
|
SectionRenderer: ({ sections }: { sections: unknown[] }) =>
|
|
30
29
|
React.createElement("div", {
|
|
31
30
|
"data-testid": "section-renderer",
|
|
@@ -45,7 +44,7 @@ describe("ClientsPage — snapshot regression", () => {
|
|
|
45
44
|
});
|
|
46
45
|
const { container } = render(element);
|
|
47
46
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
48
|
-
`"<main><h1>Clients</h1><p data-testid="section-empty-state">Clients listing coming soon.</p></main>"`,
|
|
47
|
+
`"<main id="main"><h1>Clients</h1><p data-testid="section-empty-state">Clients listing coming soon.</p></main>"`,
|
|
49
48
|
);
|
|
50
49
|
});
|
|
51
50
|
|
|
@@ -67,7 +66,7 @@ describe("ClientsPage — snapshot regression", () => {
|
|
|
67
66
|
});
|
|
68
67
|
const { container } = render(element);
|
|
69
68
|
expect(container.innerHTML).toMatchInlineSnapshot(
|
|
70
|
-
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Our Clients","description":"Companies that trust us.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Organization"}]}}</script><main><h1>Our Clients</h1><p data-testid="clients-description">Companies that trust us.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-remote" data-source=""></div></article></div></div></main>"`,
|
|
69
|
+
`"<script type="application/ld+json" data-testid="json-ld">{"@context":"https://schema.org","@type":"CollectionPage","inLanguage":"en","name":"Our Clients","description":"Companies that trust us.","mainEntity":{"@type":"ItemList","itemListElement":[{"@type":"Organization"}]}}</script><main id="main"><h1>Our Clients</h1><p data-testid="clients-description">Companies that trust us.</p><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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 transition-colors 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) hover:bg-(--color-surface-secondary) active:bg-gray-200 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 data-testid="mdx-remote" data-source=""></div></article></div></div></main>"`,
|
|
71
70
|
);
|
|
72
71
|
});
|
|
73
72
|
});
|
|
@@ -16,9 +16,36 @@
|
|
|
16
16
|
* `ConversationalIntakeWrapper` carry over whatever the visitor already
|
|
17
17
|
* typed into the conversation before Skip (or a failed auto-submit)
|
|
18
18
|
* dropped them here, instead of asking them to retype it.
|
|
19
|
+
*
|
|
20
|
+
* Consent (#6466): a real checkbox named by a `ConsentStatement` whose
|
|
21
|
+
* "Terms" links to the locale-aware `/terms` route. It starts unticked and
|
|
22
|
+
* the submit button stays disabled until it is ticked — the form is
|
|
23
|
+
* `noValidate`, so `required` alone would gate nothing. The statement is
|
|
24
|
+
* wired by `aria-labelledby`, never a wrapping `<label>`, so clicking the
|
|
25
|
+
* link follows it instead of toggling the box (the ConsentGroup rule).
|
|
26
|
+
*
|
|
27
|
+
* Composition (#6514): every control and message is a `@working-theory/ui`
|
|
28
|
+
* primitive — `Label`/`Input`/`Textarea` for the fields, `Alert` for the
|
|
29
|
+
* success and form-level error, `InlineMessage` for the per-field messages
|
|
30
|
+
* (its `error` variant carries `role="alert"`), `Button` for submit. The honeypot hides behind
|
|
31
|
+
* the `hidden` attribute on its wrapper rather than an inline style object.
|
|
19
32
|
*/
|
|
20
33
|
|
|
21
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
Alert,
|
|
36
|
+
AlertDescription,
|
|
37
|
+
Button,
|
|
38
|
+
Checkbox,
|
|
39
|
+
type CheckboxProps,
|
|
40
|
+
ConsentStatement,
|
|
41
|
+
InlineMessage,
|
|
42
|
+
Input,
|
|
43
|
+
Label,
|
|
44
|
+
localeHref,
|
|
45
|
+
Textarea,
|
|
46
|
+
} from "@working-theory/ui";
|
|
47
|
+
import { useLocale, useTranslations } from "next-intl";
|
|
48
|
+
import { useCallback, useState } from "react";
|
|
22
49
|
import { useFormState, useFormStatus } from "react-dom";
|
|
23
50
|
|
|
24
51
|
import { captureLeadAction } from "./contact.actions";
|
|
@@ -29,29 +56,40 @@ export interface ContactFormInitialValues {
|
|
|
29
56
|
message?: string;
|
|
30
57
|
}
|
|
31
58
|
|
|
59
|
+
type CheckedState = Parameters<NonNullable<CheckboxProps["onCheckedChange"]>>[0];
|
|
60
|
+
|
|
32
61
|
interface ContactFormProps {
|
|
33
62
|
initialValues?: ContactFormInitialValues;
|
|
34
63
|
}
|
|
35
64
|
|
|
36
|
-
function SubmitButton() {
|
|
65
|
+
function SubmitButton({ disabled }: { disabled: boolean }) {
|
|
37
66
|
const t = useTranslations("contact");
|
|
38
67
|
const { pending } = useFormStatus();
|
|
39
68
|
return (
|
|
40
|
-
<
|
|
69
|
+
<Button
|
|
70
|
+
type="submit"
|
|
71
|
+
disabled={pending || disabled}
|
|
72
|
+
data-testid="contact-submit"
|
|
73
|
+
>
|
|
41
74
|
{pending ? t("sending") : t("sendMessage")}
|
|
42
|
-
</
|
|
75
|
+
</Button>
|
|
43
76
|
);
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
export function ContactForm({ initialValues }: ContactFormProps = {}) {
|
|
47
80
|
const t = useTranslations("contact");
|
|
81
|
+
const locale = useLocale();
|
|
48
82
|
const [state, formAction] = useFormState(captureLeadAction, null);
|
|
83
|
+
const [consented, setConsented] = useState(false);
|
|
84
|
+
const handleConsentChange = useCallback((checked: CheckedState) => {
|
|
85
|
+
setConsented(checked === true);
|
|
86
|
+
}, []);
|
|
49
87
|
|
|
50
88
|
if (state?.kind === "success") {
|
|
51
89
|
return (
|
|
52
|
-
<
|
|
53
|
-
{t("successMessage")}
|
|
54
|
-
</
|
|
90
|
+
<Alert variant="success" data-testid="contact-success">
|
|
91
|
+
<AlertDescription>{t("successMessage")}</AlertDescription>
|
|
92
|
+
</Alert>
|
|
55
93
|
);
|
|
56
94
|
}
|
|
57
95
|
|
|
@@ -62,49 +100,53 @@ export function ContactForm({ initialValues }: ContactFormProps = {}) {
|
|
|
62
100
|
return (
|
|
63
101
|
<form action={formAction} noValidate>
|
|
64
102
|
{formError && (
|
|
65
|
-
<
|
|
66
|
-
{formError}
|
|
67
|
-
</
|
|
103
|
+
<Alert variant="error" data-testid="contact-form-error">
|
|
104
|
+
<AlertDescription>{formError}</AlertDescription>
|
|
105
|
+
</Alert>
|
|
68
106
|
)}
|
|
69
107
|
|
|
70
108
|
{/* Honeypot — hidden from users; bots fill it; API rejects non-empty */}
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
109
|
+
<div hidden>
|
|
110
|
+
<Input
|
|
111
|
+
type="text"
|
|
112
|
+
name="website"
|
|
113
|
+
tabIndex={-1}
|
|
114
|
+
aria-hidden="true"
|
|
115
|
+
data-testid="contact-honeypot"
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
78
118
|
|
|
79
119
|
<div>
|
|
80
|
-
<
|
|
81
|
-
<
|
|
120
|
+
<Label htmlFor="contact-name">{t("nameLabel")}</Label>
|
|
121
|
+
<Input
|
|
82
122
|
id="contact-name"
|
|
83
123
|
name="name"
|
|
84
124
|
type="text"
|
|
85
125
|
autoComplete="name"
|
|
126
|
+
data-testid="contact-name-input"
|
|
86
127
|
required
|
|
87
128
|
defaultValue={initialValues?.name}
|
|
88
129
|
aria-describedby={fieldErrors.name ? "contact-name-error" : undefined}
|
|
89
130
|
/>
|
|
90
131
|
{fieldErrors.name && (
|
|
91
|
-
<
|
|
132
|
+
<InlineMessage
|
|
133
|
+
variant="error"
|
|
92
134
|
id="contact-name-error"
|
|
93
|
-
role="alert"
|
|
94
135
|
data-testid="contact-name-error"
|
|
95
136
|
>
|
|
96
137
|
{fieldErrors.name}
|
|
97
|
-
</
|
|
138
|
+
</InlineMessage>
|
|
98
139
|
)}
|
|
99
140
|
</div>
|
|
100
141
|
|
|
101
142
|
<div>
|
|
102
|
-
<
|
|
103
|
-
<
|
|
143
|
+
<Label htmlFor="contact-email">{t("emailLabel")}</Label>
|
|
144
|
+
<Input
|
|
104
145
|
id="contact-email"
|
|
105
146
|
name="email"
|
|
106
147
|
type="email"
|
|
107
148
|
autoComplete="email"
|
|
149
|
+
data-testid="contact-email-input"
|
|
108
150
|
required
|
|
109
151
|
defaultValue={initialValues?.email}
|
|
110
152
|
aria-describedby={
|
|
@@ -112,39 +154,58 @@ export function ContactForm({ initialValues }: ContactFormProps = {}) {
|
|
|
112
154
|
}
|
|
113
155
|
/>
|
|
114
156
|
{fieldErrors.email && (
|
|
115
|
-
<
|
|
157
|
+
<InlineMessage
|
|
158
|
+
variant="error"
|
|
116
159
|
id="contact-email-error"
|
|
117
|
-
role="alert"
|
|
118
160
|
data-testid="contact-email-error"
|
|
119
161
|
>
|
|
120
162
|
{fieldErrors.email}
|
|
121
|
-
</
|
|
163
|
+
</InlineMessage>
|
|
122
164
|
)}
|
|
123
165
|
</div>
|
|
124
166
|
|
|
125
167
|
<div>
|
|
126
|
-
<
|
|
127
|
-
<
|
|
168
|
+
<Label htmlFor="contact-message">{t("messageLabel")}</Label>
|
|
169
|
+
<Textarea
|
|
128
170
|
id="contact-message"
|
|
129
171
|
name="message"
|
|
130
172
|
rows={5}
|
|
173
|
+
data-testid="contact-message-input"
|
|
131
174
|
defaultValue={initialValues?.message}
|
|
132
175
|
aria-describedby={
|
|
133
176
|
fieldErrors.message ? "contact-message-error" : undefined
|
|
134
177
|
}
|
|
135
178
|
/>
|
|
136
179
|
{fieldErrors.message && (
|
|
137
|
-
<
|
|
180
|
+
<InlineMessage
|
|
181
|
+
variant="error"
|
|
138
182
|
id="contact-message-error"
|
|
139
|
-
role="alert"
|
|
140
183
|
data-testid="contact-message-error"
|
|
141
184
|
>
|
|
142
185
|
{fieldErrors.message}
|
|
143
|
-
</
|
|
186
|
+
</InlineMessage>
|
|
144
187
|
)}
|
|
145
188
|
</div>
|
|
146
189
|
|
|
147
|
-
<
|
|
190
|
+
<div className="flex items-start gap-3">
|
|
191
|
+
<Checkbox
|
|
192
|
+
id="contact-consent"
|
|
193
|
+
name="consent"
|
|
194
|
+
value="true"
|
|
195
|
+
checked={consented}
|
|
196
|
+
onCheckedChange={handleConsentChange}
|
|
197
|
+
aria-labelledby="contact-consent-statement"
|
|
198
|
+
className="mt-0.5"
|
|
199
|
+
data-testid="contact-consent"
|
|
200
|
+
/>
|
|
201
|
+
<ConsentStatement
|
|
202
|
+
id="contact-consent-statement"
|
|
203
|
+
statement={t("consentStatement")}
|
|
204
|
+
policies={{ terms: localeHref(locale, "/terms") }}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<SubmitButton disabled={!consented} />
|
|
148
209
|
</form>
|
|
149
210
|
);
|
|
150
211
|
}
|
|
@@ -39,7 +39,10 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
import {
|
|
42
|
+
Container,
|
|
42
43
|
ConversationalIntake,
|
|
44
|
+
type ConversationalIntakeProps,
|
|
45
|
+
InlineMessage,
|
|
43
46
|
THINKING_DELAY_MAX_MS,
|
|
44
47
|
THINKING_DELAY_MIN_MS,
|
|
45
48
|
} from "@working-theory/ui";
|
|
@@ -63,6 +66,18 @@ function pickThinkingDelayMs(): number {
|
|
|
63
66
|
return THINKING_DELAY_MIN_MS + Math.floor(Math.random() * (span + 1));
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Fires `recordIntakeTurn` on a later microtask, swallowing any failure — a
|
|
71
|
+
* synchronous throw from the Server Action reference or a rejected promise
|
|
72
|
+
* alike — so analytics can never stall a turn, the Skip fallback, or the lead
|
|
73
|
+
* submit (#6465).
|
|
74
|
+
*/
|
|
75
|
+
function fireIntakeTurn(input: Parameters<typeof recordIntakeTurn>[0]): void {
|
|
76
|
+
Promise.resolve()
|
|
77
|
+
.then(() => recordIntakeTurn(input))
|
|
78
|
+
.catch(() => {});
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
/** Builds the FormData captureLeadAction expects from the engine's answers. */
|
|
67
82
|
function answersToFormData(answers: Record<string, string>): FormData {
|
|
68
83
|
const formData = new FormData();
|
|
@@ -76,7 +91,17 @@ function answersToFormData(answers: Record<string, string>): FormData {
|
|
|
76
91
|
|
|
77
92
|
type Surface = "intake" | "form";
|
|
78
93
|
|
|
79
|
-
|
|
94
|
+
/** The terminal engine state `ConversationalIntake` hands `onComplete`/`onSkip`. */
|
|
95
|
+
type IntakeState = Parameters<
|
|
96
|
+
NonNullable<ConversationalIntakeProps["onComplete"]>
|
|
97
|
+
>[0];
|
|
98
|
+
|
|
99
|
+
// NO_HAND_IMPLEMENT_OK: operator-directed live Free-template reference pass.
|
|
100
|
+
// `inline` drops the page-level Container so the intake can sit inside the
|
|
101
|
+
// hero's right column (the /contact two-column layout).
|
|
102
|
+
export function ConversationalIntakeWrapper({
|
|
103
|
+
inline = false,
|
|
104
|
+
}: { inline?: boolean } = {}) {
|
|
80
105
|
const t = useTranslations("contact");
|
|
81
106
|
const locale = useLocale();
|
|
82
107
|
const script = useMemo(
|
|
@@ -105,7 +130,7 @@ export function ConversationalIntakeWrapper() {
|
|
|
105
130
|
|
|
106
131
|
const getThinkingDelayMs = useCallback(() => {
|
|
107
132
|
turnIndexRef.current += 1;
|
|
108
|
-
|
|
133
|
+
fireIntakeTurn({
|
|
109
134
|
turnIndex: turnIndexRef.current,
|
|
110
135
|
status: "in_progress",
|
|
111
136
|
locale,
|
|
@@ -128,8 +153,8 @@ export function ConversationalIntakeWrapper() {
|
|
|
128
153
|
}, []);
|
|
129
154
|
|
|
130
155
|
const handleComplete = useCallback(
|
|
131
|
-
(answers:
|
|
132
|
-
|
|
156
|
+
({ answers }: IntakeState) => {
|
|
157
|
+
fireIntakeTurn({
|
|
133
158
|
turnIndex: turnIndexRef.current,
|
|
134
159
|
status: "completed",
|
|
135
160
|
locale,
|
|
@@ -146,8 +171,8 @@ export function ConversationalIntakeWrapper() {
|
|
|
146
171
|
);
|
|
147
172
|
|
|
148
173
|
const handleSkip = useCallback(
|
|
149
|
-
(answers:
|
|
150
|
-
|
|
174
|
+
({ answers }: IntakeState) => {
|
|
175
|
+
fireIntakeTurn({
|
|
151
176
|
turnIndex: turnIndexRef.current,
|
|
152
177
|
status: "skipped",
|
|
153
178
|
locale,
|
|
@@ -159,30 +184,35 @@ export function ConversationalIntakeWrapper() {
|
|
|
159
184
|
|
|
160
185
|
if (surface === "form") {
|
|
161
186
|
return (
|
|
162
|
-
|
|
187
|
+
<Container className={inline ? "p-0" : "py-section"}>
|
|
163
188
|
{submitError ? (
|
|
164
|
-
<
|
|
189
|
+
<InlineMessage
|
|
190
|
+
variant="error"
|
|
191
|
+
data-testid="contact-intake-submit-error"
|
|
192
|
+
>
|
|
165
193
|
{submitError}
|
|
166
|
-
</
|
|
194
|
+
</InlineMessage>
|
|
167
195
|
) : null}
|
|
168
196
|
<ContactForm initialValues={initialValues} />
|
|
169
|
-
|
|
197
|
+
</Container>
|
|
170
198
|
);
|
|
171
199
|
}
|
|
172
200
|
|
|
173
201
|
return (
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
202
|
+
<Container className={inline ? "p-0" : "py-section"}>
|
|
203
|
+
<ConversationalIntake
|
|
204
|
+
script={script}
|
|
205
|
+
onComplete={handleComplete}
|
|
206
|
+
onSkip={handleSkip}
|
|
207
|
+
getThinkingDelayMs={getThinkingDelayMs}
|
|
208
|
+
labels={{
|
|
209
|
+
guidedBadge: t("intake.guidedBadge"),
|
|
210
|
+
skip: t("intake.skip"),
|
|
211
|
+
send: t("intake.send"),
|
|
212
|
+
freeTextPlaceholder: t("intake.freeTextPlaceholder"),
|
|
213
|
+
}}
|
|
214
|
+
data-testid="contact-conversational-intake"
|
|
215
|
+
/>
|
|
216
|
+
</Container>
|
|
187
217
|
);
|
|
188
218
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ContactForm's consent row (#6466, spec AC-2 / AC-3).
|
|
3
|
+
*
|
|
4
|
+
* ContactForm is the only form a /contact visitor can submit — the fallback
|
|
5
|
+
* `ConversationalIntakeWrapper` swaps in on Skip or a failed auto-submit. Its
|
|
6
|
+
* consent line must be a real control: a visible, unticked checkbox named by
|
|
7
|
+
* a statement whose "Terms" is a link, and a submit button that stays
|
|
8
|
+
* disabled until the box is ticked (the form is `noValidate`, so `required`
|
|
9
|
+
* alone would gate nothing).
|
|
10
|
+
*
|
|
11
|
+
* jsdom lacks `useFormState`/`useFormStatus` (see
|
|
12
|
+
* page-renderer-migration.test.tsx), so `react-dom` is extended with inert
|
|
13
|
+
* stand-ins; the outermost network boundary (`@working-theory/api-client`)
|
|
14
|
+
* is mocked so the real server action module loads without a network.
|
|
15
|
+
*/
|
|
16
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
17
|
+
import { localeHref } from "@working-theory/ui";
|
|
18
|
+
|
|
19
|
+
const MESSAGES: Record<string, string> = {
|
|
20
|
+
consentStatement: "I accept the [Terms](terms).",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
jest.mock("next-intl", () => ({
|
|
24
|
+
useTranslations: () => (key: string) => MESSAGES[key] ?? key,
|
|
25
|
+
useLocale: () => "en",
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
jest.mock("react-dom", () => ({
|
|
29
|
+
...jest.requireActual("react-dom"),
|
|
30
|
+
useFormState: (_action: unknown, initial: unknown) => [initial, jest.fn()],
|
|
31
|
+
useFormStatus: () => ({ pending: false }),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
jest.mock("@working-theory/api-client", () => ({
|
|
35
|
+
leads: { submit: jest.fn() },
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
import { ContactForm } from "../ContactForm.js";
|
|
39
|
+
|
|
40
|
+
// jsdom does not implement ResizeObserver — the Radix Checkbox renders a
|
|
41
|
+
// hidden form-bubble input that observes size. Polyfill with a no-op.
|
|
42
|
+
beforeAll(() => {
|
|
43
|
+
global.ResizeObserver = class ResizeObserver {
|
|
44
|
+
observe() {}
|
|
45
|
+
unobserve() {}
|
|
46
|
+
disconnect() {}
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("ContactForm consent row (#6466)", () => {
|
|
51
|
+
it("renders a visible, unticked checkbox (AC-2 i)", () => {
|
|
52
|
+
render(<ContactForm />);
|
|
53
|
+
const checkbox = screen.getByRole("checkbox");
|
|
54
|
+
expect(checkbox).toBeVisible();
|
|
55
|
+
expect(checkbox).toHaveAttribute("aria-checked", "false");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("links 'Terms' to the locale-aware /terms route (AC-2 ii)", () => {
|
|
59
|
+
render(<ContactForm />);
|
|
60
|
+
const link = screen.getByRole("link", { name: "Terms" });
|
|
61
|
+
expect(link).toHaveAttribute("href", localeHref("en", "/terms"));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("names the checkbox by the consent statement (AC-2 iii)", () => {
|
|
65
|
+
render(<ContactForm />);
|
|
66
|
+
// jsdom's accname pads the inline link with a space ("Terms .").
|
|
67
|
+
expect(screen.getByRole("checkbox")).toHaveAccessibleName(
|
|
68
|
+
/^I accept the Terms ?\.$/,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("clicking the Terms link does not tick the box (AC-2 iv)", () => {
|
|
73
|
+
render(<ContactForm />);
|
|
74
|
+
const link = screen.getByRole("link", { name: "Terms" });
|
|
75
|
+
// A wrapping <label> is what would forward the click to the box.
|
|
76
|
+
expect(link.closest("label")).toBeNull();
|
|
77
|
+
link.addEventListener("click", (event) => event.preventDefault());
|
|
78
|
+
fireEvent.click(link);
|
|
79
|
+
expect(screen.getByRole("checkbox")).toHaveAttribute(
|
|
80
|
+
"aria-checked",
|
|
81
|
+
"false",
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("keeps submit disabled until the box is ticked (AC-3)", () => {
|
|
86
|
+
render(<ContactForm />);
|
|
87
|
+
const submit = screen.getByTestId("contact-submit");
|
|
88
|
+
expect(submit).toBeDisabled();
|
|
89
|
+
fireEvent.click(screen.getByRole("checkbox"));
|
|
90
|
+
expect(screen.getByRole("checkbox")).toHaveAttribute(
|
|
91
|
+
"aria-checked",
|
|
92
|
+
"true",
|
|
93
|
+
);
|
|
94
|
+
expect(submit).toBeEnabled();
|
|
95
|
+
});
|
|
96
|
+
});
|