fix(participation): clarify eligibility hierarchy
This commit is contained in:
@@ -49,6 +49,7 @@ function Lines({ text }: { text: string }) {
|
|||||||
|
|
||||||
const Participation: React.FC = () => {
|
const Participation: React.FC = () => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const isEligibilityCompact = useIsMobile(900);
|
||||||
const qualSectionRef = useRef<HTMLElement>(null);
|
const qualSectionRef = useRef<HTMLElement>(null);
|
||||||
const cms = (useCmsRoute()?.doc?.participation || {}) as ParticipationCms;
|
const cms = (useCmsRoute()?.doc?.participation || {}) as ParticipationCms;
|
||||||
const hero = { ...participationContent.hero, ...(cms.hero || {}) };
|
const hero = { ...participationContent.hero, ...(cms.hero || {}) };
|
||||||
@@ -94,10 +95,10 @@ const Participation: React.FC = () => {
|
|||||||
<WegZurAuszeichnungSection content={process} />
|
<WegZurAuszeichnungSection content={process} />
|
||||||
|
|
||||||
{/* ── QUALIFICATIONS ───────────────────────────────────────────────────── */}
|
{/* ── QUALIFICATIONS ───────────────────────────────────────────────────── */}
|
||||||
<section id="voraussetzungen" ref={qualSectionRef} style={{ background: '#fff', overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
<section id="voraussetzungen" ref={qualSectionRef} data-testid="eligibility-section" style={{ background: '#fff', overflow: 'hidden', position: 'relative', isolation: 'isolate', scrollMarginTop: 60 }}>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '48px 24px 32px' : '80px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 24 : 48, alignItems: 'flex-end', borderBottom: `1px solid #D0D5DD` }}>
|
<div data-testid="eligibility-header" style={{ position: 'relative', zIndex: 1, padding: isMobile ? '48px 24px 36px' : '80px 80px 56px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 24 : 48, alignItems: 'flex-end', background: '#fff', borderBottom: '1px solid rgba(17,29,85,0.16)' }}>
|
||||||
<div>
|
<div>
|
||||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'var(--text-eyebrow-light)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>{fallbackText(eligibility.eyebrow, participationContent.eligibility.eyebrow)}</span>
|
<span style={{ fontFamily: FF, fontSize: 10, color: 'var(--text-eyebrow-light)', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 16 }}>{fallbackText(eligibility.eyebrow, participationContent.eligibility.eyebrow)}</span>
|
||||||
<h2 className="type-section-title text-role-heading" style={{ textTransform: 'uppercase', margin: 0 }}>
|
<h2 className="type-section-title text-role-heading" style={{ textTransform: 'uppercase', margin: 0 }}>
|
||||||
@@ -122,56 +123,60 @@ const Participation: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 4 criteria as editorial rows */}
|
{/* 4 criteria as editorial rows */}
|
||||||
<div style={{ position: 'relative', zIndex: 1, background: '#fff', borderBottom: `1px solid #D0D5DD` }}>
|
<div data-testid="eligibility-criteria-group" style={{ position: 'relative', zIndex: 1, background: '#F4F6F8', borderBottom: '1px solid rgba(17,29,85,0.16)', boxShadow: 'inset 0 1px 0 rgba(17,29,85,0.04)' }}>
|
||||||
{eligibilityCriteria.map((item, i, arr) => {
|
<ol data-testid="eligibility-criteria" style={{ listStyle: 'none', maxWidth: 1280, margin: '0 auto', padding: isEligibilityCompact ? '8px 0' : '16px 80px', boxSizing: 'border-box' }}>
|
||||||
const Icon = ICONS[item.icon as keyof typeof ICONS] || MapPin;
|
{eligibilityCriteria.map((item, i, arr) => {
|
||||||
return (
|
const Icon = ICONS[item.icon as keyof typeof ICONS] || MapPin;
|
||||||
<QualRow key={i} item={item} Icon={Icon} last={i === arr.length - 1} />
|
return (
|
||||||
);
|
<QualRow key={i} item={item} Icon={Icon} last={i === arr.length - 1} index={i} compact={isEligibilityCompact} />
|
||||||
})}
|
);
|
||||||
|
})}
|
||||||
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Zusätzliche Hinweise: Verbund + Wiederbewerber */}
|
{/* Zusätzliche Hinweise: Verbund + Wiederbewerber */}
|
||||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '28px 24px' : '40px 80px', display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: isMobile ? 24 : 48, borderBottom: '1px solid #D0D5DD' }}>
|
<div data-testid="eligibility-notes-group" style={{ position: 'relative', zIndex: 1, background: '#fff', borderBottom: '1px solid rgba(17,29,85,0.16)' }}>
|
||||||
{eligibilityNotes.map((note, i) => {
|
<div style={{ maxWidth: 1280, margin: '0 auto', padding: isEligibilityCompact ? '12px 24px' : '40px 80px 44px', display: 'grid', gridTemplateColumns: isEligibilityCompact ? '1fr' : 'repeat(2, minmax(0, 1fr))', gap: isEligibilityCompact ? 0 : 48, boxSizing: 'border-box' }}>
|
||||||
const Icon = ICONS[note.icon as keyof typeof ICONS] || Building2;
|
{eligibilityNotes.map((note, i) => {
|
||||||
return (
|
const Icon = ICONS[note.icon as keyof typeof ICONS] || Building2;
|
||||||
<div key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 14 }}>
|
return (
|
||||||
<Icon size={18} style={{ color: GOLD, flexShrink: 0, marginTop: 3 }} strokeWidth={1.7} />
|
<div key={i} data-testid="eligibility-note" data-order={i + 1} style={{ display: 'flex', alignItems: 'flex-start', gap: 14, padding: isEligibilityCompact ? '20px 0' : '18px 0 0', borderTop: '2px solid rgba(239,191,4,0.72)', borderBottom: isEligibilityCompact && i < eligibilityNotes.length - 1 ? '1px solid rgba(17,29,85,0.12)' : 'none' }}>
|
||||||
<div>
|
<Icon aria-hidden="true" size={18} style={{ color: GOLD, flexShrink: 0, marginTop: 3 }} strokeWidth={1.7} />
|
||||||
<div style={{ fontFamily: FF, fontSize: 13, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>{fallbackText(note.title, '')}</div>
|
<div>
|
||||||
<p className="type-body text-role-secondary" style={{ margin: 0 }}>
|
<div style={{ fontFamily: FF, fontSize: 13, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 8 }}>{fallbackText(note.title, '')}</div>
|
||||||
{fallbackText(note.body, '')}
|
<p className="type-body text-role-secondary" style={{ margin: 0 }}>
|
||||||
</p>
|
{fallbackText(note.body, '')}
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA nudge */}
|
{/* CTA nudge */}
|
||||||
<div style={{ position: 'relative', zIndex: 1, padding: isMobile ? '24px 24px' : '32px 80px', display: 'flex', flexDirection: isMobile ? 'column' : 'row', alignItems: isMobile ? 'flex-start' : 'center', justifyContent: 'space-between', gap: 24 }}>
|
<aside data-testid="eligibility-summary" style={{ position: 'relative', zIndex: 1, padding: isEligibilityCompact ? '24px' : '28px 80px', background: NAVY, boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.08)' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ maxWidth: 1120, margin: '0 auto', display: 'flex', alignItems: 'flex-start', gap: 12 }}>
|
||||||
<CheckCircle2 size={18} style={{ color: GOLD, flexShrink: 0 }} />
|
<CheckCircle2 aria-hidden="true" size={19} style={{ color: GOLD, flexShrink: 0, marginTop: 3 }} />
|
||||||
<span style={{ fontFamily: FF, fontSize: 18, fontWeight: 600, color: '#101828' }}>{fallbackText(eligibility.nudgeText, participationContent.eligibility.nudgeText)}</span>
|
<span style={{ fontFamily: FF, fontSize: isMobile ? 17 : 18, fontWeight: 600, color: '#fff', lineHeight: 1.55 }}>{fallbackText(eligibility.nudgeText, participationContent.eligibility.nudgeText)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* ── BEWERBUNGSWEGE ───────────────────────────────────────────────────── */}
|
{/* ── BEWERBUNGSWEGE ───────────────────────────────────────────────────── */}
|
||||||
<BewerbungswegeSection content={applicationWays} />
|
<BewerbungswegeSection content={applicationWays} />
|
||||||
|
|
||||||
{/* ── DATES BANNER ─────────────────────────────────────────────────────── */}
|
{/* ── DATES BANNER ─────────────────────────────────────────────────────── */}
|
||||||
<section id="fristen" style={{ background: GOLD }}>
|
<section id="fristen" data-testid="important-dates-transition" style={{ background: GOLD, borderTop: `${isMobile ? 5 : 8}px solid ${NAVY}`, scrollMarginTop: 80, boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.24)' }}>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
/* Mobile: slim horizontal bar */
|
/* Mobile: slim horizontal bar */
|
||||||
<div style={{ padding: '24px 22px 26px' }}>
|
<div style={{ padding: '24px 22px 26px' }}>
|
||||||
<div style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.2em', marginBottom: 18 }}>{fallbackText(datesBanner.heading, participationContent.datesBanner.heading)}</div>
|
<div style={{ fontFamily: FF, fontSize: 12, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.2em', marginBottom: 18 }}>{fallbackText(datesBanner.heading, participationContent.datesBanner.heading)}</div>
|
||||||
<div style={{ display: 'flex' }}>
|
<div data-testid="important-dates-list" style={{ display: 'grid' }}>
|
||||||
{mobileDates.map((d, i) => (
|
{mobileDates.map((d, i) => (
|
||||||
<div key={i} style={{ flex: 1, minWidth: 0, paddingLeft: i > 0 ? 14 : 0, borderLeft: i > 0 ? '1px solid rgba(3,9,58,0.2)' : 'none' }}>
|
<div key={i} data-testid="important-date-item" data-order={i + 1} style={{ display: 'grid', gridTemplateColumns: 'minmax(112px, 0.9fr) minmax(0, 1.1fr)', gap: 16, minWidth: 0, padding: '14px 0', borderTop: '1px solid rgba(3,9,58,0.2)', alignItems: 'center' }}>
|
||||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 900, color: '#101828', letterSpacing: '-0.02em', lineHeight: 1.12, marginBottom: 6 }}>{fallbackText(d.date, '')}</div>
|
<div style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: '#101828', letterSpacing: '-0.02em', lineHeight: 1.12 }}>{fallbackText(d.date, '')}</div>
|
||||||
<div style={{ fontFamily: FF, fontSize: 9.5, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.55)', lineHeight: 1.25 }}>{fallbackText(d.label, '')}</div>
|
<div style={{ fontFamily: FF, fontSize: 10.5, fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.72)', lineHeight: 1.35, overflowWrap: 'anywhere' }}>{fallbackText(d.label, '')}</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +193,7 @@ const Participation: React.FC = () => {
|
|||||||
<React.Fragment key={i}>
|
<React.Fragment key={i}>
|
||||||
<div style={{ padding: '48px 40px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
<div style={{ padding: '48px 40px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||||
<div style={{ fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.4rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.02em', marginBottom: 6 }}>{fallbackText(d.date, '')}</div>
|
<div style={{ fontFamily: FF, fontSize: 'clamp(1.1rem, 1.8vw, 1.4rem)', fontWeight: 900, color: '#101828', letterSpacing: '-0.02em', marginBottom: 6 }}>{fallbackText(d.date, '')}</div>
|
||||||
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.55)' }}>{fallbackText(d.label, '')}</div>
|
<div style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, letterSpacing: '0.18em', textTransform: 'uppercase', color: 'rgba(16,24,40,0.72)' }}>{fallbackText(d.label, '')}</div>
|
||||||
</div>
|
</div>
|
||||||
{i < desktopDates.length - 1 && <div style={{ background: 'rgba(3,9,58,0.12)' }} />}
|
{i < desktopDates.length - 1 && <div style={{ background: 'rgba(3,9,58,0.12)' }} />}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
@@ -207,31 +212,32 @@ const Participation: React.FC = () => {
|
|||||||
|
|
||||||
// ── QualRow ───────────────────────────────────────────────────────────────────
|
// ── QualRow ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
function QualRow({ item, Icon, last }: { item: { num: string; title: string; body: string }; Icon: React.ElementType; last: boolean }) {
|
function QualRow({ item, Icon, last, index, compact }: { item: { num: string; title: string; body: string }; Icon: React.ElementType; last: boolean; index: number; compact: boolean }) {
|
||||||
const [hovered, setHovered] = React.useState(false);
|
const [hovered, setHovered] = React.useState(false);
|
||||||
const isMobileRow = useIsMobile();
|
|
||||||
return (
|
return (
|
||||||
<div
|
<li
|
||||||
style={{ display: 'grid', gridTemplateColumns: isMobileRow ? '40px 1fr' : '80px 200px 1fr 48px', gap: isMobileRow ? '0 16px' : '0 32px', padding: isMobileRow ? '24px 24px' : '28px 80px', borderBottom: last ? 'none' : '1px solid #D0D5DD', alignItems: isMobileRow ? 'flex-start' : 'center', background: hovered ? '#EAE7E6' : 'transparent', transition: 'background 0.15s', cursor: 'default' }}
|
data-testid="eligibility-criterion"
|
||||||
|
data-order={index + 1}
|
||||||
|
style={{ display: 'grid', gridTemplateColumns: compact ? '40px 1fr' : '64px minmax(180px, 220px) minmax(0, 1fr) 44px', gap: compact ? '0 16px' : '0 28px', padding: compact ? '22px 24px' : '26px 0', borderBottom: last ? 'none' : '1px solid rgba(17,29,85,0.14)', alignItems: compact ? 'flex-start' : 'center', background: hovered ? '#fff' : 'transparent', transition: 'background 0.15s', cursor: 'default' }}
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
onMouseLeave={() => setHovered(false)}
|
onMouseLeave={() => setHovered(false)}
|
||||||
>
|
>
|
||||||
<span style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: hovered ? GOLD : 'rgba(239,191,4,0.5)', letterSpacing: '0.1em', transition: 'color 0.2s', paddingTop: 2 }}>{item.num}</span>
|
<span style={{ fontFamily: FF, fontSize: 11, fontWeight: 700, color: hovered ? '#715600' : '#5B6475', letterSpacing: '0.1em', transition: 'color 0.2s', paddingTop: 2 }}>{item.num}</span>
|
||||||
{isMobileRow ? (
|
{compact ? (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 4 }}>{item.title}</div>
|
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: 4 }}>{item.title}</div>
|
||||||
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.5)', lineHeight: 1.6 }}>{item.body}</div>
|
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.72)', lineHeight: 1.6 }}>{item.body}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em' }}>{item.title}</div>
|
<div style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', textTransform: 'uppercase', letterSpacing: '0.07em' }}>{item.title}</div>
|
||||||
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.5)', lineHeight: 1.6 }}>{item.body}</div>
|
<div style={{ fontFamily: FB, fontSize: 17, color: 'rgba(16,24,40,0.72)', lineHeight: 1.6 }}>{item.body}</div>
|
||||||
<div style={{ width: 36, height: 36, background: hovered ? GOLD : 'rgba(3,9,58,0.06)', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 0.2s' }}>
|
<div style={{ width: 36, height: 36, background: hovered ? GOLD : 'rgba(3,9,58,0.06)', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'background 0.2s' }}>
|
||||||
<Icon size={16} style={{ color: hovered ? '#101828' : 'rgba(16,24,40,0.3)', transition: 'color 0.2s' }} strokeWidth={1.5} />
|
<Icon size={16} style={{ color: hovered ? '#101828' : 'rgba(16,24,40,0.3)', transition: 'color 0.2s' }} strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
157
tests/int/participation-eligibility.int.spec.tsx
Normal file
157
tests/int/participation-eligibility.int.spec.tsx
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import { cleanup, render, screen, within } from '@testing-library/react'
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { CmsRouteProvider, type CmsRouteData } from '@/spa/cmsRoute'
|
||||||
|
import Participation from '@/spa/pages/Participation'
|
||||||
|
|
||||||
|
const viewport = vi.hoisted(() => ({ mobile: false }))
|
||||||
|
|
||||||
|
vi.mock('@/spa/hooks/useIsMobile', () => ({
|
||||||
|
useIsMobile: () => viewport.mobile,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('next/navigation', () => ({
|
||||||
|
usePathname: () => '/teilnahme',
|
||||||
|
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/spa/components/WegZurAuszeichnungSection', () => ({
|
||||||
|
default: () => <section data-testid="timeline-stub" />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/spa/components/AwardsGridSection', () => ({
|
||||||
|
default: () => <section data-testid="awards-stub" />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/spa/components/forms/ApplicationFormSection', () => ({
|
||||||
|
ApplicationFormSection: () => <section data-testid="application-form-stub" />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/spa/components/ui/UnoptimizedImage', () => ({
|
||||||
|
default: ({ alt }: { alt: string }) => <div role="img" aria-label={alt} />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const participationRoute: CmsRouteData = {
|
||||||
|
collection: 'pages',
|
||||||
|
doc: {
|
||||||
|
id: 'participation-page',
|
||||||
|
slug: 'teilnahme',
|
||||||
|
spaPath: '/teilnahme',
|
||||||
|
participation: {
|
||||||
|
eligibility: {
|
||||||
|
eyebrow: 'CMS eligibility eyebrow',
|
||||||
|
heading: 'CMS ELIGIBILITY\nHEADING',
|
||||||
|
description: 'CMS eligibility introduction.',
|
||||||
|
primaryCta: { label: 'CMS eligibility CTA', url: '/kontakt' },
|
||||||
|
criteria: [
|
||||||
|
{
|
||||||
|
num: 'A',
|
||||||
|
title: 'First CMS criterion',
|
||||||
|
body: 'First CMS criterion body.',
|
||||||
|
icon: 'mapPin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
num: 'B',
|
||||||
|
title: 'Second CMS criterion',
|
||||||
|
body: 'Second CMS criterion body.',
|
||||||
|
icon: 'users',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
notes: [
|
||||||
|
{ title: 'First CMS note', body: 'First CMS note body.', icon: 'building2' },
|
||||||
|
{ title: 'Second CMS note', body: 'Second CMS note body.', icon: 'userCheck' },
|
||||||
|
],
|
||||||
|
nudgeText: 'CMS eligibility conclusion.',
|
||||||
|
},
|
||||||
|
datesBanner: {
|
||||||
|
heading: 'CMS important dates',
|
||||||
|
description: 'CMS dates description.',
|
||||||
|
mobileItems: [{ date: 'Mobile date', label: 'Mobile label' }],
|
||||||
|
desktopItems: [{ date: 'Desktop date', label: 'Desktop label' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderParticipation = () =>
|
||||||
|
render(
|
||||||
|
<CmsRouteProvider value={participationRoute}>
|
||||||
|
<Participation />
|
||||||
|
</CmsRouteProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('participation eligibility hierarchy', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
viewport.mobile = false
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => cleanup())
|
||||||
|
|
||||||
|
it('keeps CMS eligibility content and its CTA authoritative inside semantic groups', () => {
|
||||||
|
renderParticipation()
|
||||||
|
|
||||||
|
const section = screen.getByTestId('eligibility-section')
|
||||||
|
const criteria = within(section).getAllByTestId('eligibility-criterion')
|
||||||
|
const notes = within(section).getAllByTestId('eligibility-note')
|
||||||
|
const cta = within(section).getByRole('link', { name: 'CMS eligibility CTA' })
|
||||||
|
|
||||||
|
expect(within(section).getByRole('heading', { level: 2 }).textContent).toBe(
|
||||||
|
'CMS ELIGIBILITYHEADING',
|
||||||
|
)
|
||||||
|
expect(within(section).getByText('CMS eligibility introduction.')).toBeTruthy()
|
||||||
|
expect(criteria.map((row) => row.textContent)).toEqual([
|
||||||
|
'AFirst CMS criterionFirst CMS criterion body.',
|
||||||
|
'BSecond CMS criterionSecond CMS criterion body.',
|
||||||
|
])
|
||||||
|
expect(criteria.every((row) => row.tagName === 'LI')).toBe(true)
|
||||||
|
expect(criteria[0]?.parentElement?.tagName).toBe('OL')
|
||||||
|
expect(notes.map((note) => note.textContent)).toEqual([
|
||||||
|
'First CMS noteFirst CMS note body.',
|
||||||
|
'Second CMS noteSecond CMS note body.',
|
||||||
|
])
|
||||||
|
expect(within(section).getByText('CMS eligibility conclusion.')).toBeTruthy()
|
||||||
|
expect(cta.getAttribute('href')).toBe('/kontakt')
|
||||||
|
expect(within(section).getAllByRole('link')).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('provides restrained group and dates-transition hooks on desktop', () => {
|
||||||
|
renderParticipation()
|
||||||
|
|
||||||
|
expect(screen.getByTestId('eligibility-header').style.background).toBe('rgb(255, 255, 255)')
|
||||||
|
expect(screen.getByTestId('eligibility-criteria-group').style.background).toBe(
|
||||||
|
'rgb(244, 246, 248)',
|
||||||
|
)
|
||||||
|
expect(screen.getByTestId('eligibility-summary').style.background).toBe('rgb(17, 29, 85)')
|
||||||
|
expect(screen.getByTestId('important-dates-transition').style.borderTopWidth).toBe('8px')
|
||||||
|
expect(screen.getByRole('heading', { name: 'CMS important dates', level: 3 })).toBeTruthy()
|
||||||
|
expect(screen.getByText('Desktop date')).toBeTruthy()
|
||||||
|
expect(screen.queryByText('Mobile date')).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps criteria, notes, and conclusion in a single readable mobile order', () => {
|
||||||
|
viewport.mobile = true
|
||||||
|
renderParticipation()
|
||||||
|
|
||||||
|
const section = screen.getByTestId('eligibility-section')
|
||||||
|
const criteriaGroup = within(section).getByTestId('eligibility-criteria-group')
|
||||||
|
const notesGroup = within(section).getByTestId('eligibility-notes-group')
|
||||||
|
const summary = within(section).getByTestId('eligibility-summary')
|
||||||
|
const rows = within(criteriaGroup).getAllByTestId('eligibility-criterion')
|
||||||
|
|
||||||
|
expect(rows.map((row) => row.getAttribute('data-order'))).toEqual(['1', '2'])
|
||||||
|
expect(rows.every((row) => row.style.gridTemplateColumns === '40px 1fr')).toBe(true)
|
||||||
|
expect(
|
||||||
|
criteriaGroup.compareDocumentPosition(notesGroup) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
notesGroup.compareDocumentPosition(summary) & Node.DOCUMENT_POSITION_FOLLOWING,
|
||||||
|
).toBeTruthy()
|
||||||
|
expect(screen.getByTestId('important-dates-transition').style.borderTopWidth).toBe('5px')
|
||||||
|
expect(screen.getByTestId('important-date-item').getAttribute('data-order')).toBe('1')
|
||||||
|
expect(screen.getByTestId('important-date-item').style.gridTemplateColumns).toBe(
|
||||||
|
'minmax(112px, 0.9fr) minmax(0, 1.1fr)',
|
||||||
|
)
|
||||||
|
expect(screen.getByText('Mobile date')).toBeTruthy()
|
||||||
|
expect(screen.queryByText('Desktop date')).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user