content: replace scaffolding with bilingual pages and case studies
Ship the final German and English copy, compose every route from shared page primitives, and cover claims, parity and contact briefing in tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
115
src/app/core/content/content-completeness.spec.ts
Normal file
115
src/app/core/content/content-completeness.spec.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { APP_LOCALES } from '../i18n/locale';
|
||||
import { ROUTE_IDS } from '../routing/route-ids';
|
||||
import { type SiteContent } from './content.contracts';
|
||||
import { SITE_CONTENT_DATA } from './site-content';
|
||||
|
||||
const TITLE_SUFFIX = ' | Antonio Ledebuhr';
|
||||
const FORBIDDEN =
|
||||
/lorem|todo|tbd|platzhaltertext|placeholder text|wird derzeit aufgebaut|being built|coming soon|xxx/i;
|
||||
|
||||
function walkStrings(
|
||||
value: unknown,
|
||||
visit: (text: string, path: string) => void,
|
||||
path = '',
|
||||
skipKeys: ReadonlySet<string> = new Set(),
|
||||
): void {
|
||||
if (typeof value === 'string') {
|
||||
visit(value, path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => walkStrings(item, visit, `${path}[${index}]`, skipKeys));
|
||||
return;
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
for (const [key, child] of Object.entries(value)) {
|
||||
if (skipKeys.has(key)) {
|
||||
continue;
|
||||
}
|
||||
walkStrings(child, visit, path ? `${path}.${key}` : key, skipKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('content completeness', () => {
|
||||
it('covers every route in both locales with real page copy', () => {
|
||||
for (const locale of APP_LOCALES) {
|
||||
const site = SITE_CONTENT_DATA[locale];
|
||||
|
||||
expect(site.pages.home).toBe(site.home);
|
||||
expect(site.pages.contact).toBe(site.contact);
|
||||
expect(site.pages.stack).toBe(site.stack);
|
||||
expect(site.pages.imprint).toBe(site.legal.imprint);
|
||||
expect(site.pages.privacy).toBe(site.legal.privacy);
|
||||
expect(site.pages.servicesAi).toBe(site.services.servicesAi);
|
||||
|
||||
for (const routeId of ROUTE_IDS) {
|
||||
const page = site.pages[routeId];
|
||||
expect(page, `${locale}.${routeId}`).toBeTruthy();
|
||||
expect(page.title.length).toBeGreaterThan(0);
|
||||
expect(page.title.endsWith(TITLE_SUFFIX)).toBe(true);
|
||||
expect(page.description.length).toBeGreaterThanOrEqual(60);
|
||||
expect(page.hero.headline.length).toBeGreaterThan(0);
|
||||
expect(page.hero.headline.includes(TITLE_SUFFIX)).toBe(false);
|
||||
|
||||
if (routeId !== 'notFound') {
|
||||
const filledSections = page.sections.filter((section) =>
|
||||
(section.body ?? []).some((paragraph) => paragraph.trim().length > 0),
|
||||
);
|
||||
expect(filledSections.length, `${locale}.${routeId} sections`).toBeGreaterThanOrEqual(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps the content tree free of scaffolding, except legal review fields', () => {
|
||||
const skipped = new Set(['reviewNotice', 'reviewTodos']);
|
||||
|
||||
for (const locale of APP_LOCALES) {
|
||||
const site = SITE_CONTENT_DATA[locale];
|
||||
const hits: string[] = [];
|
||||
|
||||
walkStrings(
|
||||
site,
|
||||
(text, path) => {
|
||||
if (FORBIDDEN.test(text)) {
|
||||
hits.push(`${path}: ${text}`);
|
||||
}
|
||||
},
|
||||
locale,
|
||||
skipped,
|
||||
);
|
||||
|
||||
expect(hits).toEqual([]);
|
||||
|
||||
for (const legalId of ['imprint', 'privacy'] as const) {
|
||||
const legal = site.legal[legalId];
|
||||
expect(legal.reviewNotice.trim().length).toBeGreaterThan(20);
|
||||
expect(legal.reviewTodos.length).toBeGreaterThan(0);
|
||||
expect(legal.reviewTodos.every((item) => item.trim().length > 0)).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('does not treat legal review fields as an implicit skip of the rest of the legal pages', () => {
|
||||
for (const locale of APP_LOCALES) {
|
||||
const site: SiteContent = SITE_CONTENT_DATA[locale];
|
||||
for (const legalId of ['imprint', 'privacy'] as const) {
|
||||
const legal = site.legal[legalId];
|
||||
const remainderHits: string[] = [];
|
||||
walkStrings(
|
||||
{ hero: legal.hero, sections: legal.sections, legalSections: legal.legalSections },
|
||||
(text, path) => {
|
||||
if (FORBIDDEN.test(text)) {
|
||||
remainderHits.push(`${path}: ${text}`);
|
||||
}
|
||||
},
|
||||
`${locale}.${legalId}`,
|
||||
);
|
||||
expect(remainderHits).toEqual([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user