Rewrite the constraint specs and docs for the service-led information architecture.
Assertions now follow the eighteen prerenderable paths, the gist dialog, and the six-case evidence set. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,7 +9,7 @@ describe('server routes', () => {
|
||||
expect(catchAll && 'status' in catchAll ? catchAll.status : undefined).toBe(404);
|
||||
|
||||
const prerendered = serverRoutes.filter((route) => route.path !== '**');
|
||||
expect(prerendered).toHaveLength(26);
|
||||
expect(prerendered).toHaveLength(18);
|
||||
expect(prerendered.map((route) => route.path)).toEqual(
|
||||
expect.arrayContaining(['pitch', 'en/pitch']),
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ function allMetrics(site: SiteContent): readonly MetricCopy[] {
|
||||
}
|
||||
|
||||
function allOfferings(site: SiteContent): readonly OfferingCopy[] {
|
||||
return Object.values(site.services).flatMap((page) => page.offerings);
|
||||
return site.services.serviceSections.flatMap((section) => section.offerings);
|
||||
}
|
||||
|
||||
function caseText(caseStudy: CaseStudyCopy): string {
|
||||
@@ -33,11 +33,18 @@ function caseText(caseStudy: CaseStudyCopy): string {
|
||||
}
|
||||
|
||||
describe('content claims and attribution', () => {
|
||||
it('keeps the four public cases and attributes verified facts to the right ones', () => {
|
||||
it('keeps the six public cases and attributes verified facts to the right ones', () => {
|
||||
for (const locale of APP_LOCALES) {
|
||||
const site = SITE_CONTENT_DATA[locale];
|
||||
expect(Object.keys(site.cases)).toEqual([...CASE_STUDY_IDS]);
|
||||
expect(CASE_STUDY_IDS).toEqual(['innofocus', 'roesterei', 'myspa', 'hdi']);
|
||||
expect(CASE_STUDY_IDS).toEqual([
|
||||
'innofocus',
|
||||
'roesterei',
|
||||
'bannier',
|
||||
'elbwaerme',
|
||||
'hdi',
|
||||
'devdays',
|
||||
]);
|
||||
|
||||
const matchesRuntime = (metric: MetricCopy) =>
|
||||
/8/.test(metric.value) && /90/.test(metric.value);
|
||||
@@ -49,14 +56,6 @@ describe('content claims and attribution', () => {
|
||||
expect(site.cases[caseId].metrics.filter(matchesRuntime)).toHaveLength(0);
|
||||
}
|
||||
|
||||
const leadershipMetrics = allMetrics(site).filter(
|
||||
(metric) =>
|
||||
metric.caseId === 'myspa' &&
|
||||
/4/.test(`${metric.value} ${metric.label}`) &&
|
||||
/2/.test(`${metric.value} ${metric.label}`),
|
||||
);
|
||||
expect(leadershipMetrics.length).toBeGreaterThan(0);
|
||||
|
||||
const hdiText = caseText(site.cases.hdi);
|
||||
expect(hdiText).toMatch(/Azure AKS/);
|
||||
expect(hdiText).toMatch(/Azure App Services/);
|
||||
@@ -70,16 +69,34 @@ describe('content claims and attribution', () => {
|
||||
it('keeps AI delivery claims limited to the two Rösterei tools', () => {
|
||||
for (const locale of APP_LOCALES) {
|
||||
const site = SITE_CONTENT_DATA[locale];
|
||||
const aiOfferings = site.services.servicesAi.offerings;
|
||||
const delivered = aiOfferings.filter((offering) => offering.status === 'delivered');
|
||||
const sections = site.services.serviceSections;
|
||||
|
||||
expect(delivered).toHaveLength(2);
|
||||
expect(delivered.every((offering) => offering.referenceCaseId === 'roesterei')).toBe(true);
|
||||
const ai = sections.find((section) => section.id === 'ai-workflows');
|
||||
expect(ai, `${locale} ai-workflows section`).toBeTruthy();
|
||||
|
||||
const otherOfferings = allOfferings(site).filter((offering) => !delivered.includes(offering));
|
||||
expect(otherOfferings.every((offering) => offering.status === 'offer')).toBe(true);
|
||||
const aiDelivered = ai!.offerings.filter((offering) => offering.status === 'delivered');
|
||||
expect(aiDelivered).toHaveLength(2);
|
||||
expect(aiDelivered.every((offering) => offering.referenceCaseId === 'roesterei')).toBe(true);
|
||||
expect(
|
||||
otherOfferings.some((offering) =>
|
||||
ai!.offerings
|
||||
.filter((offering) => offering.status !== 'delivered')
|
||||
.every((offering) => offering.status === 'offer'),
|
||||
).toBe(true);
|
||||
|
||||
const everyDelivered = allOfferings(site).filter(
|
||||
(offering) => offering.status === 'delivered',
|
||||
);
|
||||
expect(
|
||||
everyDelivered.every(
|
||||
(offering) =>
|
||||
!!offering.referenceCaseId && CASE_STUDY_IDS.includes(offering.referenceCaseId),
|
||||
),
|
||||
`${locale}: every delivered offering names a public case`,
|
||||
).toBe(true);
|
||||
|
||||
const offered = allOfferings(site).filter((offering) => offering.status === 'offer');
|
||||
expect(
|
||||
offered.some((offering) =>
|
||||
DELIVERED_OFFER_WORDING.test(`${offering.title} ${offering.body}`),
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
@@ -40,13 +40,12 @@ describe('content completeness', () => {
|
||||
|
||||
expect(site.pages.home).toBe(site.home);
|
||||
expect(site.pages.pitch).toBe(site.pitch);
|
||||
expect(site.pages.services).toBe(site.servicesOverview);
|
||||
expect(site.pages.services).toBe(site.services);
|
||||
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];
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SHELL_COPY } from './shell-copy';
|
||||
import { FOOTER_NAV, PRIMARY_NAV } from '../navigation/navigation';
|
||||
|
||||
const EXCLUDED = new RegExp(
|
||||
`\\b(${['hu' + 'p', 'bit' + 'wiz', 'cyber' + 'trading'].join('|')})\\b`,
|
||||
`\\b(${['hu' + 'p', 'bit' + 'wiz', 'cyber' + 'trading', 'ara' + 'com', 'my' + 'spa'].join('|')})\\b`,
|
||||
'i',
|
||||
);
|
||||
|
||||
|
||||
115
src/app/core/content/content-service-led.spec.ts
Normal file
115
src/app/core/content/content-service-led.spec.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { APP_LOCALES } from '../i18n/locale';
|
||||
import { CASE_STUDY_IDS, SERVICE_SECTION_IDS, type CaseStudyCopy } from './content.contracts';
|
||||
import { SITE_CONTENT_DATA } from './site-content';
|
||||
|
||||
const DEVICE_IN_HEADING = /arbeitsplatz|workstation|gerät(?:e|esupport)?|device support|endgerät/i;
|
||||
|
||||
function caseBlob(study: CaseStudyCopy): string {
|
||||
return [
|
||||
study.period,
|
||||
study.domain,
|
||||
study.headline,
|
||||
study.summary,
|
||||
study.responsibility,
|
||||
study.situation,
|
||||
...study.approach,
|
||||
...study.outcome,
|
||||
study.teaser,
|
||||
study.externalUrl ?? '',
|
||||
study.externalLabel ?? '',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
describe('service-led content contracts', () => {
|
||||
it('keeps service sections infrastructure-first in the fixed order', () => {
|
||||
expect(SERVICE_SECTION_IDS).toEqual([
|
||||
'infrastructure-network',
|
||||
'platform-operations',
|
||||
'software-data',
|
||||
'ai-workflows',
|
||||
]);
|
||||
|
||||
for (const locale of APP_LOCALES) {
|
||||
const ids = SITE_CONTENT_DATA[locale].services.serviceSections.map((section) => section.id);
|
||||
expect(ids, locale).toEqual([...SERVICE_SECTION_IDS]);
|
||||
expect(SITE_CONTENT_DATA[locale].home.serviceAreas.map((area) => area.id)).toEqual([
|
||||
...SERVICE_SECTION_IDS,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
it('keeps Bannier and Elbwärme dates, scopes, externals and the support-versus-development line', () => {
|
||||
const de = SITE_CONTENT_DATA.de.cases;
|
||||
const en = SITE_CONTENT_DATA.en.cases;
|
||||
|
||||
expect(de.bannier.period).toBe('seit etwa 2018, laufend');
|
||||
expect(en.bannier.period).toBe('since roughly 2018, ongoing');
|
||||
expect(de.elbwaerme.period).toBe('seit 11/2025, laufend');
|
||||
expect(en.elbwaerme.period).toBe('since November 2025, ongoing');
|
||||
|
||||
expect(de.bannier.externalUrl).toBe('https://fahrschule-stendal.de');
|
||||
expect(en.bannier.externalUrl).toBe('https://fahrschule-stendal.de');
|
||||
expect(de.elbwaerme.externalUrl).toBe('https://elbwaerme.de');
|
||||
expect(en.elbwaerme.externalUrl).toBe('https://elbwaerme.de');
|
||||
expect(de.bannier.externalLabel).toBe('fahrschule-stendal.de öffnen');
|
||||
expect(en.bannier.externalLabel).toBe('Visit fahrschule-stendal.de');
|
||||
expect(de.elbwaerme.externalLabel).toBe('elbwaerme.de öffnen');
|
||||
expect(en.elbwaerme.externalLabel).toBe('Visit elbwaerme.de');
|
||||
|
||||
expect(caseBlob(de.bannier)).toMatch(/nicht entwickelt/);
|
||||
expect(caseBlob(de.bannier)).toMatch(/betreue und betreibe/);
|
||||
expect(caseBlob(en.bannier)).toMatch(/did not develop/);
|
||||
expect(caseBlob(en.bannier)).toMatch(/support and operate/);
|
||||
|
||||
for (const caseId of CASE_STUDY_IDS.filter((id) => id !== 'bannier' && id !== 'elbwaerme')) {
|
||||
expect(Object.hasOwn(de[caseId], 'externalUrl')).toBe(false);
|
||||
expect(Object.hasOwn(en[caseId], 'externalUrl')).toBe(false);
|
||||
expect(Object.hasOwn(de[caseId], 'externalLabel')).toBe(false);
|
||||
expect(Object.hasOwn(en[caseId], 'externalLabel')).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('states the personal contact, two-person team, organisational support and specialist network', () => {
|
||||
const de = SITE_CONTENT_DATA.de;
|
||||
const en = SITE_CONTENT_DATA.en;
|
||||
|
||||
expect(de.home.sections.find((section) => section.id === 'team')?.body?.join('\n')).toContain(
|
||||
'Der Kern sind zwei Personen zwischen Tangermünde und Magdeburg',
|
||||
);
|
||||
expect(de.home.sections.find((section) => section.id === 'team')?.body?.join('\n')).toContain(
|
||||
'ausgesuchtes Netzwerk',
|
||||
);
|
||||
expect(de.services.sections[1].body?.join('\n')).toContain('technischer Ansprechpartner');
|
||||
expect(de.services.sections[1].body?.join('\n')).toContain('Kaufmännisches und Organisation');
|
||||
expect(en.services.sections[1].body?.join('\n')).toContain('technical point of contact');
|
||||
expect(en.services.sections[1].body?.join('\n')).toContain('two people');
|
||||
expect(en.home.sections.find((section) => section.id === 'team')?.body?.join('\n')).toContain(
|
||||
'two people',
|
||||
);
|
||||
expect(
|
||||
en.pages.about.sections
|
||||
.find((section) => section.id === 'contact-and-team')
|
||||
?.body?.join('\n'),
|
||||
).toMatch(/curated/);
|
||||
expect(en.pitch.profile.join('\n')).toContain('curated network of specialists');
|
||||
expect(de.pitch.profile.join('\n')).toContain('ausgesuchtes Fachnetzwerk');
|
||||
});
|
||||
|
||||
it('keeps device and workstation wording out of headings, nav labels and offering titles', () => {
|
||||
for (const locale of APP_LOCALES) {
|
||||
const site = SITE_CONTENT_DATA[locale];
|
||||
const headings = [
|
||||
...site.home.serviceAreas.map((area) => area.title),
|
||||
...site.services.serviceSections.map((section) => section.title),
|
||||
...site.services.serviceSections.flatMap((section) =>
|
||||
section.offerings.map((offering) => offering.title),
|
||||
),
|
||||
...site.services.sections.map((section) => section.headline),
|
||||
];
|
||||
|
||||
for (const heading of headings) {
|
||||
expect(DEVICE_IN_HEADING.test(heading), `${locale}: ${heading}`).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,10 @@ import { prerenderablePaths, routePath } from '../routing/route-paths';
|
||||
import { absoluteUrl } from './route-metadata';
|
||||
|
||||
const PUBLIC = join(process.cwd(), 'public');
|
||||
const EXCLUDED = /HUP|BitWiz|Cybertrading/;
|
||||
const EXCLUDED = new RegExp(
|
||||
`\\b(${['hu' + 'p', 'bit' + 'wiz', 'cyber' + 'trading', 'ara' + 'com', 'my' + 'spa'].join('|')})\\b`,
|
||||
'i',
|
||||
);
|
||||
|
||||
function readPublic(name: 'robots.txt' | 'sitemap.xml' | 'llms.txt'): string {
|
||||
return readFileSync(join(PUBLIC, name), 'utf8');
|
||||
@@ -119,14 +122,6 @@ describe('crawl assets', () => {
|
||||
absoluteUrl(routePath('about', 'en')),
|
||||
absoluteUrl(routePath('services', 'de')),
|
||||
absoluteUrl(routePath('services', 'en')),
|
||||
absoluteUrl(routePath('servicesSoftware', 'de')),
|
||||
absoluteUrl(routePath('servicesSoftware', 'en')),
|
||||
absoluteUrl(routePath('servicesHardwareNetwork', 'de')),
|
||||
absoluteUrl(routePath('servicesHardwareNetwork', 'en')),
|
||||
absoluteUrl(routePath('servicesClusters', 'de')),
|
||||
absoluteUrl(routePath('servicesClusters', 'en')),
|
||||
absoluteUrl(routePath('servicesAi', 'de')),
|
||||
absoluteUrl(routePath('servicesAi', 'en')),
|
||||
absoluteUrl(routePath('projects', 'de')),
|
||||
absoluteUrl(routePath('projects', 'en')),
|
||||
...CASE_STUDY_IDS.flatMap((id) => [
|
||||
@@ -166,6 +161,10 @@ describe('crawl assets', () => {
|
||||
[],
|
||||
);
|
||||
expect(EXCLUDED.test(text), `${name} contains an excluded station name`).toBe(false);
|
||||
expect(text).not.toContain('/leistungen/software');
|
||||
expect(text).not.toContain('/en/services/software');
|
||||
expect(text).not.toContain('/leistungen/ai-integration');
|
||||
expect(text).not.toContain('/en/services/ai-integration');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,13 +20,7 @@ const FORBIDDEN_FIELDS = [
|
||||
'numberOfEmployees',
|
||||
] as const;
|
||||
|
||||
const SERVICE_ROUTES: readonly RouteId[] = [
|
||||
'services',
|
||||
'servicesSoftware',
|
||||
'servicesHardwareNetwork',
|
||||
'servicesClusters',
|
||||
'servicesAi',
|
||||
];
|
||||
const SERVICE_ROUTES: readonly RouteId[] = ['services'];
|
||||
|
||||
function collectKeys(value: unknown, keys = new Set<string>()): Set<string> {
|
||||
if (Array.isArray(value)) {
|
||||
@@ -63,15 +57,20 @@ describe('JSON-LD builders', () => {
|
||||
expect(graphTypes('home', locale)).toEqual(['Person', 'ProfessionalService']);
|
||||
expect(graphTypes('pitch', locale), `${locale}.pitch`).toEqual(['WebPage']);
|
||||
|
||||
for (const routeId of SERVICE_ROUTES) {
|
||||
expect(graphTypes(routeId, locale), `${locale}.${routeId}`).toEqual(['Service']);
|
||||
}
|
||||
expect(graphTypes('services', locale), `${locale}.services`).toEqual([
|
||||
'Service',
|
||||
'Service',
|
||||
'Service',
|
||||
'Service',
|
||||
]);
|
||||
|
||||
expect(graphTypes('projects', locale)).toEqual([
|
||||
'CreativeWork',
|
||||
'CreativeWork',
|
||||
'CreativeWork',
|
||||
'CreativeWork',
|
||||
'CreativeWork',
|
||||
'CreativeWork',
|
||||
]);
|
||||
|
||||
for (const routeId of ROUTE_IDS.filter(
|
||||
@@ -105,7 +104,12 @@ describe('JSON-LD builders', () => {
|
||||
}
|
||||
|
||||
const serialized = serializeJsonLd(graph);
|
||||
expect(serialized).not.toMatch(/HUP|BitWiz|Cybertrading/);
|
||||
expect(serialized).not.toMatch(
|
||||
new RegExp(
|
||||
`\\b(${['hu' + 'p', 'bit' + 'wiz', 'cyber' + 'trading', 'ara' + 'com', 'my' + 'spa'].join('|')})\\b`,
|
||||
'i',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -68,8 +68,8 @@ describe('page rendering, semantics and accessibility', () => {
|
||||
'/en',
|
||||
'/pitch',
|
||||
'/en/pitch',
|
||||
'/leistungen/software',
|
||||
'/en/services/software',
|
||||
'/leistungen',
|
||||
'/en/services',
|
||||
'/projekte',
|
||||
'/en/projects',
|
||||
'/missing-route',
|
||||
@@ -96,8 +96,8 @@ describe('page rendering, semantics and accessibility', () => {
|
||||
'/en',
|
||||
'/projekte',
|
||||
'/en/projects',
|
||||
'/leistungen/ai-integration',
|
||||
'/en/services/ai-integration',
|
||||
'/leistungen',
|
||||
'/en/services',
|
||||
'/kontakt',
|
||||
'/en/contact',
|
||||
'/stack',
|
||||
@@ -139,6 +139,37 @@ describe('page rendering, semantics and accessibility', () => {
|
||||
expect(SITE_CONTENT_DATA.de.pages.notFound.hero.headline).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders six cases with Bannier and Elbwärme externals and no excluded station names', async () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideRouter(routes), { provide: SITE_CONTENT, useValue: SITE_CONTENT_DATA }],
|
||||
});
|
||||
|
||||
const harness = await RouterTestingHarness.create();
|
||||
const excluded = new RegExp(
|
||||
`\\b(${['hu' + 'p', 'bit' + 'wiz', 'cyber' + 'trading', 'ara' + 'com', 'my' + 'spa'].join('|')})\\b`,
|
||||
'i',
|
||||
);
|
||||
|
||||
for (const path of ['/projekte', '/en/projects']) {
|
||||
await harness.navigateByUrl(path);
|
||||
const root = harness.routeNativeElement as HTMLElement;
|
||||
const ids = [...root.querySelectorAll('article.case-study')].map((article) => article.id);
|
||||
expect(ids).toEqual([...CASE_STUDY_IDS]);
|
||||
expect(excluded.test(root.textContent ?? '')).toBe(false);
|
||||
|
||||
const bannier = root.querySelector('#bannier a.external-link') as HTMLAnchorElement | null;
|
||||
const elbwaerme = root.querySelector(
|
||||
'#elbwaerme a.external-link',
|
||||
) as HTMLAnchorElement | null;
|
||||
expect(bannier?.getAttribute('href')).toBe('https://fahrschule-stendal.de');
|
||||
expect(elbwaerme?.getAttribute('href')).toBe('https://elbwaerme.de');
|
||||
expect(bannier?.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
expect(elbwaerme?.getAttribute('rel')).toBe('noopener noreferrer');
|
||||
expect(bannier?.getAttribute('target')).toBe('_blank');
|
||||
expect(root.querySelectorAll('a.external-link')).toHaveLength(2);
|
||||
}
|
||||
});
|
||||
|
||||
it('places the Systems Map on the services overview', async () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideRouter(routes), { provide: SITE_CONTENT, useValue: SITE_CONTENT_DATA }],
|
||||
@@ -197,7 +228,7 @@ describe('page rendering, semantics and accessibility', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('sends the software service case CTA to the innofocus project anchor', async () => {
|
||||
it('exposes the four service section anchors and unique ids on the services page', async () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [provideRouter(routes), { provide: SITE_CONTENT, useValue: SITE_CONTENT_DATA }],
|
||||
});
|
||||
@@ -205,13 +236,22 @@ describe('page rendering, semantics and accessibility', () => {
|
||||
const harness = await RouterTestingHarness.create();
|
||||
|
||||
for (const locale of APP_LOCALES) {
|
||||
await harness.navigateByUrl(routePath('servicesSoftware', locale));
|
||||
await harness.navigateByUrl(routePath('services', locale));
|
||||
const root = harness.routeNativeElement as HTMLElement;
|
||||
const expected = `${routePath('projects', locale)}#innofocus`;
|
||||
const hrefs = [...root.querySelectorAll('a')].map(
|
||||
(anchor) => anchor.getAttribute('href') ?? '',
|
||||
);
|
||||
expect(hrefs.some((href) => href.endsWith(expected))).toBe(true);
|
||||
expect(root.querySelectorAll('h1')).toHaveLength(1);
|
||||
const ids = [
|
||||
'infrastructure-network',
|
||||
'platform-operations',
|
||||
'software-data',
|
||||
'ai-workflows',
|
||||
];
|
||||
for (const id of ids) {
|
||||
expect(root.querySelectorAll(`#${id}`)).toHaveLength(1);
|
||||
}
|
||||
|
||||
const allIds = [...root.querySelectorAll('[id]')].map((element) => element.id);
|
||||
expect(new Set(allIds).size, `${locale} duplicate ids: ${allIds}`).toBe(allIds.length);
|
||||
assertNoDanglingReferences(root);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
33
src/app/shared/interactive-lift.spec.ts
Normal file
33
src/app/shared/interactive-lift.spec.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const ROOT = process.cwd();
|
||||
|
||||
function read(path: string): string {
|
||||
return readFileSync(join(ROOT, path), 'utf8');
|
||||
}
|
||||
|
||||
describe('interactive lift primitive', () => {
|
||||
it('lives in the global stylesheet, hover-only, and drops transform under reduced motion', () => {
|
||||
const css = read('src/styles.scss');
|
||||
expect(css).toContain('.interactive-lift');
|
||||
expect(css).toMatch(/@media \(hover: hover\) and \(pointer: fine\)/);
|
||||
expect(css).toMatch(/prefers-reduced-motion:\s*reduce/);
|
||||
|
||||
const reduceBlock = css.slice(css.indexOf('prefers-reduced-motion'));
|
||||
expect(reduceBlock).toMatch(/\.interactive-lift\s*\{[^}]*transform:\s*none/);
|
||||
expect(reduceBlock).toMatch(/transition:\s*none/);
|
||||
});
|
||||
|
||||
it('is applied to clickable case cards and home service links, not to prose sections', () => {
|
||||
expect(read('src/app/shared/case-card/case-card.html')).toContain('interactive-lift');
|
||||
expect(read('src/app/features/home/home.html')).toContain('home-service-card interactive-lift');
|
||||
expect(read('src/app/shared/content-section/content-section.html')).not.toContain(
|
||||
'interactive-lift',
|
||||
);
|
||||
expect(read('src/app/shared/page-hero/page-hero.html')).not.toContain('interactive-lift');
|
||||
expect(read('src/app/components/pages/skills/skill-card/skill-card.html')).not.toContain(
|
||||
'interactive-lift',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,15 +0,0 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const APP_SCSS = readFileSync(join(process.cwd(), 'src/app/app.scss'), 'utf8');
|
||||
|
||||
describe('primary nav submenu surface', () => {
|
||||
it('uses the opaque menu token and not the glass surface', () => {
|
||||
const submenu = APP_SCSS.slice(APP_SCSS.indexOf('.primary-nav > li > .submenu'));
|
||||
|
||||
expect(submenu).toMatch(/background:\s*var\(--color-surface-menu\)/);
|
||||
expect(submenu).not.toMatch(/var\(--surface-glass\)/);
|
||||
expect(submenu).toMatch(/box-shadow:/);
|
||||
expect(submenu).toMatch(/border-radius:/);
|
||||
});
|
||||
});
|
||||
52
src/legacy-service-redirects.spec.ts
Normal file
52
src/legacy-service-redirects.spec.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { LEGACY_SERVICE_REDIRECTS, normalizeRequestPath } from './legacy-service-redirects';
|
||||
import { prerenderablePaths } from './app/core/routing/route-paths';
|
||||
|
||||
describe('legacy service redirects', () => {
|
||||
it('maps exactly eight retired service URLs onto services fragments', () => {
|
||||
expect(Object.keys(LEGACY_SERVICE_REDIRECTS)).toEqual([
|
||||
'/leistungen/software',
|
||||
'/leistungen/hardware-netzwerk',
|
||||
'/leistungen/cluster',
|
||||
'/leistungen/ai-integration',
|
||||
'/en/services/software',
|
||||
'/en/services/hardware-network',
|
||||
'/en/services/clusters',
|
||||
'/en/services/ai-integration',
|
||||
]);
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/leistungen/software']).toBe('/leistungen#software-data');
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/leistungen/hardware-netzwerk']).toBe(
|
||||
'/leistungen#infrastructure-network',
|
||||
);
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/leistungen/cluster']).toBe('/leistungen#platform-operations');
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/leistungen/ai-integration']).toBe('/leistungen#ai-workflows');
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/en/services/software']).toBe('/en/services#software-data');
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/en/services/hardware-network']).toBe(
|
||||
'/en/services#infrastructure-network',
|
||||
);
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/en/services/clusters']).toBe(
|
||||
'/en/services#platform-operations',
|
||||
);
|
||||
expect(LEGACY_SERVICE_REDIRECTS['/en/services/ai-integration']).toBe(
|
||||
'/en/services#ai-workflows',
|
||||
);
|
||||
});
|
||||
|
||||
it('treats an optional trailing slash as the same path', () => {
|
||||
expect(normalizeRequestPath('/leistungen/software/')).toBe('/leistungen/software');
|
||||
expect(normalizeRequestPath('/en/services/ai-integration/')).toBe(
|
||||
'/en/services/ai-integration',
|
||||
);
|
||||
expect(normalizeRequestPath('/')).toBe('/');
|
||||
expect(LEGACY_SERVICE_REDIRECTS[normalizeRequestPath('/leistungen/cluster/')]).toBe(
|
||||
'/leistungen#platform-operations',
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the eight legacy paths out of prerenderablePaths', () => {
|
||||
const paths = prerenderablePaths();
|
||||
expect(paths).toHaveLength(18);
|
||||
for (const from of Object.keys(LEGACY_SERVICE_REDIRECTS)) {
|
||||
expect(paths, from).not.toContain(from);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user