Pitch takes the verified profile, metrics and public cases off Home so `/` can speak to small companies in plain language. The Systems Map now sits on the services overview, and both locales stay table-driven. Co-authored-by: Cursor <cursoragent@cursor.com>
119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
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.pitch).toBe(site.pitch);
|
|
expect(site.pages.services).toBe(site.servicesOverview);
|
|
expect(site.pages.contact).toBe(site.contact);
|
|
expect(site.pages.projects).toBe(site.projects);
|
|
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([]);
|
|
}
|
|
}
|
|
});
|
|
});
|