From f465839af0e49fa5021272c0a85786a90a763589 Mon Sep 17 00:00:00 2001 From: syntaxbullet Date: Fri, 31 Jul 2026 17:24:36 +0200 Subject: [PATCH] fix(home): honor phase headline line breaks --- src/ApplicationPhase/config.ts | 2 +- ...1_200000_home_phase_headline_line_break.ts | 23 ++++++ src/migrations/index.ts | 6 ++ src/spa/applicationPhase.ts | 2 +- src/spa/pages/Home.tsx | 15 ++-- tests/int/home-phase-headline.int.spec.tsx | 73 +++++++++++++++++++ 6 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 src/migrations/20260731_200000_home_phase_headline_line_break.ts create mode 100644 tests/int/home-phase-headline.int.spec.tsx diff --git a/src/ApplicationPhase/config.ts b/src/ApplicationPhase/config.ts index 50f4760..dc355dd 100644 --- a/src/ApplicationPhase/config.ts +++ b/src/ApplicationPhase/config.ts @@ -26,7 +26,7 @@ const phaseFields: Field[] = [ text('tag', 'Badge'), { name: 'pulse', label: 'Show pulse dot', type: 'checkbox', defaultValue: true }, text('phase', 'Phase label'), - text('headline', 'Headline'), + textarea('headline', 'Headline'), textarea('body', 'Body copy'), { name: 'cta', label: 'Primary CTA', type: 'group', fields: linkGroup() }, text('accent', 'Accent color'), diff --git a/src/migrations/20260731_200000_home_phase_headline_line_break.ts b/src/migrations/20260731_200000_home_phase_headline_line_break.ts new file mode 100644 index 0000000..dbf167a --- /dev/null +++ b/src/migrations/20260731_200000_home_phase_headline_line_break.ts @@ -0,0 +1,23 @@ +import { type MigrateDownArgs, type MigrateUpArgs, sql } from '@payloadcms/db-sqlite' + +const literal = (value: string) => `'${value.replace(/'/g, "''")}'` + +const previousHeadline = 'DER BMP 2026 WAR EIN ERFOLG.' +const unbrokenHeadline = 'Ein weiteres erfolgreiches Jahr' +const splitHeadline = 'Ein weiteres\nerfolgreiches Jahr' + +export async function up({ db, payload: _payload, req: _req }: MigrateUpArgs): Promise { + await db.run( + sql.raw( + `UPDATE application_phase_phases SET headline = ${literal(splitHeadline)} WHERE num = '03' AND headline IN (${literal(previousHeadline)}, ${literal(unbrokenHeadline)});`, + ), + ) +} + +export async function down({ db, payload: _payload, req: _req }: MigrateDownArgs): Promise { + await db.run( + sql.raw( + `UPDATE application_phase_phases SET headline = ${literal(previousHeadline)} WHERE num = '03' AND headline = ${literal(splitHeadline)};`, + ), + ) +} diff --git a/src/migrations/index.ts b/src/migrations/index.ts index ed972bc..4fa94d8 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -20,6 +20,7 @@ import * as migration_20260731_160000_focused_payload_cleanup from './20260731_1 import * as migration_20260731_170000_netzwerk_greeting_video from './20260731_170000_netzwerk_greeting_video'; import * as migration_20260731_180000_press_contact_image from './20260731_180000_press_contact_image'; import * as migration_20260731_190000_responsive_sponsor_hierarchy from './20260731_190000_responsive_sponsor_hierarchy'; +import * as migration_20260731_200000_home_phase_headline_line_break from './20260731_200000_home_phase_headline_line_break'; export const migrations = [ { @@ -132,4 +133,9 @@ export const migrations = [ down: migration_20260731_190000_responsive_sponsor_hierarchy.down, name: '20260731_190000_responsive_sponsor_hierarchy', }, + { + up: migration_20260731_200000_home_phase_headline_line_break.up, + down: migration_20260731_200000_home_phase_headline_line_break.down, + name: '20260731_200000_home_phase_headline_line_break', + }, ]; diff --git a/src/spa/applicationPhase.ts b/src/spa/applicationPhase.ts index e26b723..ac6a594 100644 --- a/src/spa/applicationPhase.ts +++ b/src/spa/applicationPhase.ts @@ -70,7 +70,7 @@ export const defaultApplicationPhaseData: SpaApplicationPhaseData = { tag: 'ABGESCHLOSSEN', pulse: false, phase: 'Preisverleihung · Oktober 2026', - headline: 'DER BMP 2026 WAR EIN ERFOLG.', + headline: 'Ein weiteres\nerfolgreiches Jahr', body: 'Wir danken allen 247 Bewerberinnen und Bewerbern für Mut und Exzellenz. Drei außergewöhnliche Unternehmen wurden in München ausgezeichnet – ein unvergesslicher Abend.', cta: { label: 'Alle Preisträger ansehen', url: '/preistraeger' }, diff --git a/src/spa/pages/Home.tsx b/src/spa/pages/Home.tsx index 64bb261..3ecc84b 100644 --- a/src/spa/pages/Home.tsx +++ b/src/spa/pages/Home.tsx @@ -116,7 +116,7 @@ const isMembershipCta = (cta?: { label?: string; url?: string; to?: string } | n cta?.label?.trim().toLowerCase() === 'mitglied werden'; function Lines({ text }: { text: string }) { - return <>{text.split('\n').map((line, i) => {i > 0 &&
}{line}
)}; + return <>{text.split(/\r?\n/).map((line, i) => {i > 0 &&
}{line}
)}; } function partnerLogos(partners: PartnerLogoData[]) { @@ -542,7 +542,7 @@ const Home: React.FC = () => { // ─── StatusSlider ───────────────────────────────────────────────────────────── -function StatusSlider({ data }: { data?: SpaApplicationPhaseData }) { +export function StatusSlider({ data }: { data?: SpaApplicationPhaseData }) { const isMobile = useIsMobile(); if (!data?.phases?.length) return null; @@ -568,11 +568,12 @@ function StatusSlider({ data }: { data?: SpaApplicationPhaseData }) { return (
-

- {phase.headline} +

+

{phase.body} diff --git a/tests/int/home-phase-headline.int.spec.tsx b/tests/int/home-phase-headline.int.spec.tsx new file mode 100644 index 0000000..34bb28b --- /dev/null +++ b/tests/int/home-phase-headline.int.spec.tsx @@ -0,0 +1,73 @@ +import { readFileSync } from 'node:fs' +import { join } from 'node:path' + +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' + +import type { SpaApplicationPhaseData } from '@/spa/applicationPhase' +import { StatusSlider } from '@/spa/pages/Home' + +vi.mock('@/spa/hooks/useIsMobile', () => ({ + useIsMobile: () => false, +})) + +vi.mock('next/navigation', () => ({ + usePathname: () => '/', + useRouter: () => ({ push: vi.fn(), replace: vi.fn() }), +})) + +const applicationPhase = (headline: string): SpaApplicationPhaseData => ({ + activePhase: '0', + noActionLabel: 'No action', + successApplicationsLabel: 'Applications', + successWinnersLabel: 'Winners', + phases: [ + { + num: '03', + tag: 'Complete', + phase: 'Award ceremony', + headline, + body: 'CMS body copy', + accent: '#EFBF04', + bg: '#111D55', + meta: 'CMS meta copy', + successApplications: '247', + successWinners: '3', + }, + ], +}) + +describe('homepage application-phase headline', () => { + afterEach(() => cleanup()) + + it('renders each explicit CMS newline as an intentional line break', () => { + render() + + const headline = screen.getByTestId('phase-headline') + expect(headline.textContent).toBe('Ein weitereserfolgreiches Jahr') + expect(headline.querySelectorAll('br')).toHaveLength(1) + expect(headline.childNodes[0]?.textContent).toBe('Ein weiteres') + expect(headline.childNodes[2]?.textContent).toBe('erfolgreiches Jahr') + }) + + it('keeps arbitrary CMS-edited copy authoritative and naturally wrappable', () => { + const editedHeadline = + 'Eine redaktionell geänderte Überschrift mit zusätzlichem Inhalt für schmale Ansichten' + + render() + + const headline = screen.getByTestId('phase-headline') + expect(headline.textContent).toBe(editedHeadline) + expect(headline.querySelector('br')).toBeNull() + expect(headline.classList.contains('type-page-title')).toBe(true) + expect(screen.getByTestId('phase-section').style.height).toBe('auto') + expect(screen.getByTestId('phase-section').style.minHeight).toBe('480px') + }) + + it('keeps the requested copy out of the homepage component', () => { + const source = readFileSync(join(process.cwd(), 'src/spa/pages/Home.tsx'), 'utf8') + + expect(source).not.toContain('Ein weiteres') + expect(source).not.toContain('erfolgreiches Jahr') + }) +})