From b0aec7ba940784390208c811e76a65ca9062a752 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Mon, 13 Jul 2026 12:19:02 +0200 Subject: [PATCH] fix: resolve selected winners from populated CMS data --- src/spa/cmsRoute.tsx | 8 +++++++ src/spa/pages/About.tsx | 15 ++---------- src/spa/pages/Home.tsx | 11 +++------ tests/int/cms-route-relationship.int.spec.ts | 25 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 tests/int/cms-route-relationship.int.spec.ts diff --git a/src/spa/cmsRoute.tsx b/src/spa/cmsRoute.tsx index ebb8a2c..91765a4 100644 --- a/src/spa/cmsRoute.tsx +++ b/src/spa/cmsRoute.tsx @@ -42,6 +42,14 @@ export function useCmsCollection(collection: CmsRouteCollection) { return useCmsRoute()?.lists?.[collection] || [] } +export function resolveCmsRelationship(entry: unknown, docs: CmsRouteDoc[]) { + const entryID = entry && typeof entry === 'object' ? (entry as { id?: unknown }).id : entry + const populatedDoc = docs.find((doc) => String(doc.id) === String(entryID)) + + if (populatedDoc) return populatedDoc + return entry && typeof entry === 'object' ? (entry as CmsRouteDoc) : undefined +} + export function useApplicationPhase() { return useCmsRoute()?.applicationPhase } diff --git a/src/spa/pages/About.tsx b/src/spa/pages/About.tsx index 4f45ea3..7ef2a2e 100644 --- a/src/spa/pages/About.tsx +++ b/src/spa/pages/About.tsx @@ -6,7 +6,7 @@ import TestimonialsSection from '@/spa/components/TestimonialsSection' import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg' import Image from '@/spa/components/ui/UnoptimizedImage' import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField' -import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute' +import { resolveCmsRelationship, useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute' import { WINNERS, type Winner } from '@/spa/data/winners' import { useIsMobile } from '@/spa/hooks/useIsMobile' import { Link } from '@/spa/router' @@ -108,17 +108,6 @@ const normalizeWinner = ( } } -const winnerFromRelationship = ( - entry: unknown, - allDocs: CmsRouteDoc[], - placeholderImage: string, - fallbackWinner: typeof aboutContent.highlights.fallbackWinner, -) => { - if (entry && typeof entry === 'object') return normalizeWinner(entry as CmsRouteDoc, placeholderImage, fallbackWinner) - const doc = allDocs.find((item) => String(item.id) === String(entry)) - return normalizeWinner(doc, placeholderImage, fallbackWinner) -} - const About: React.FC = () => { const isMobile = useIsMobile() const placeholderImage = usePreistraegerPlaceholderImage() @@ -144,7 +133,7 @@ const About: React.FC = () => { const highlightWinners = useMemo(() => { const selected = fallbackArray(highlights.selectedWinners, []) - .map((entry) => winnerFromRelationship(entry, cmsWinners, placeholderImage, highlightFallbackWinner)) + .map((entry) => normalizeWinner(resolveCmsRelationship(entry, cmsWinners), placeholderImage, highlightFallbackWinner)) .filter((winner): winner is Winner => Boolean(winner)) if (selected.length) return selected.slice(0, 8) diff --git a/src/spa/pages/Home.tsx b/src/spa/pages/Home.tsx index 3386b6f..23e569f 100644 --- a/src/spa/pages/Home.tsx +++ b/src/spa/pages/Home.tsx @@ -11,7 +11,7 @@ import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; import Image from '@/spa/components/ui/UnoptimizedImage' import type { SpaApplicationPhaseData, SpaApplicationPhaseItem } from '@/spa/applicationPhase' -import { useApplicationPhase, useCmsCollection, useCmsRoute } from '@/spa/cmsRoute' +import { resolveCmsRelationship, useApplicationPhase, useCmsCollection, useCmsRoute } from '@/spa/cmsRoute' import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField' import { homeContent } from '@/spa/homeContent' import { netzwerkContent } from '@/spa/netzwerkContent' @@ -276,13 +276,8 @@ const Home: React.FC = () => { }; const showHeroSecondaryCta = !HIDE_MEMBERSHIP_HOME_HERO_CTA || !isMembershipCta(heroSecondaryCta); const selectedWinnerDocs = selectedWinners - .map((selected) => { - const selectedRecord = selected as { id?: string | number } - const selectedID = typeof selected === 'object' && selected !== null ? selectedRecord.id : selected - const listDoc = cmsPreistraeger.find((doc) => String((doc as { id?: string | number }).id) === String(selectedID)) - return listDoc || (typeof selected === 'object' ? selected : undefined) - }) - .filter(Boolean) + .map((selected) => resolveCmsRelationship(selected, cmsPreistraeger)) + .filter(Boolean) const homeWinners = selectedWinnerDocs.length ? selectedWinnerDocs : (cmsPreistraeger.length ? cmsPreistraeger : WINNERS).filter((w) => Number((w as { year?: string | number }).year) === 2025).slice(0, 8); diff --git a/tests/int/cms-route-relationship.int.spec.ts b/tests/int/cms-route-relationship.int.spec.ts new file mode 100644 index 0000000..cf117c9 --- /dev/null +++ b/tests/int/cms-route-relationship.int.spec.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest' + +import { resolveCmsRelationship, type CmsRouteDoc } from '@/spa/cmsRoute' + +const fullWinner: CmsRouteDoc = { + id: 20, + title: 'Adelholzener Alpenquellen GmbH', + image: { id: 55, url: '/api/media/file/image%20(1).png' }, +} + +describe('CMS relationship resolution', () => { + it('prefers the fully populated collection document over a shallow relationship object', () => { + expect(resolveCmsRelationship({ id: 20, image: 55 }, [fullWinner])).toBe(fullWinner) + }) + + it('resolves an ID-only relationship', () => { + expect(resolveCmsRelationship(20, [fullWinner])).toBe(fullWinner) + }) + + it('preserves a populated relationship when no list document is available', () => { + const relationship = { id: 20, image: { id: 55, url: '/api/media/file/image%20(1).png' } } + + expect(resolveCmsRelationship(relationship, [])).toBe(relationship) + }) +})