Files
bmp-website-2026/tests/int/netzwerk-sponsor-hierarchy.int.spec.tsx
2026-07-31 16:52:49 +02:00

204 lines
6.8 KiB
TypeScript

import { cleanup, render, screen, within } from '@testing-library/react'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import { CmsRouteProvider, type CmsRouteData } from '@/spa/cmsRoute'
import { netzwerkContent } from '@/spa/netzwerkContent'
import Netzwerk from '@/spa/pages/Netzwerk'
import {
groupSponsorsByTier,
sponsorCardBasis,
sponsorColumns,
sponsorViewport,
} from '@/spa/sponsorHierarchy'
vi.mock('next/navigation', () => ({
usePathname: () => '/netzwerk',
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
}))
let viewportWidth = 1440
const partner = (name: string, tier: 1 | 2 | 3, sortOrder: number) => ({
id: name.toLowerCase().replace(/\s/g, '-'),
stableId: name.toLowerCase().replace(/\s/g, '-'),
name,
tier,
sortOrder,
active: true,
desc: `${name} description`,
fullDesc: `${name} details`,
industry: `${name} industry`,
location: `${name} location`,
website: `${name.toLowerCase()}.example`,
})
const partners = [
partner('Tier One B', 1, 200),
partner('Tier Three H', 3, 8),
partner('Tier Two B', 2, 20),
partner('Tier Three A', 3, 1),
partner('Tier One A', 1, 100),
partner('Tier Three C', 3, 3),
partner('Tier Two A', 2, 10),
partner('Tier Three B', 3, 2),
partner('Tier Three D', 3, 4),
partner('Tier Three E', 3, 5),
partner('Tier Three F', 3, 6),
partner('Tier Three G', 3, 7),
]
const sponsorCopy = {
eyebrow: 'CMS sponsor eyebrow',
heading: 'CMS sponsor heading',
description: 'CMS sponsor supporting copy',
detailLabel: 'CMS sponsor action',
tiers: [
{ tier: 1, label: 'CMS feature tier', note: 'CMS feature tier note' },
{ tier: 2, label: 'CMS standard tier', note: 'CMS standard tier note' },
{ tier: 3, label: 'CMS compact tier', note: 'CMS compact tier note' },
],
}
const routeData = (): CmsRouteData => ({
collection: 'pages',
doc: {
id: 'network-page',
slug: 'netzwerk',
spaPath: '/netzwerk',
netzwerk: { partners: sponsorCopy },
},
lists: {
partners,
'jury-mitglieder': [],
},
})
function renderNetwork(width: number) {
viewportWidth = width
return render(
<CmsRouteProvider value={routeData()}>
<Netzwerk />
</CmsRouteProvider>,
)
}
describe('responsive sponsor hierarchy', () => {
beforeAll(() => {
Object.defineProperty(window, 'matchMedia', {
configurable: true,
value: vi.fn().mockImplementation((query: string) => {
const maximum = Number(query.match(/max-width:\s*(\d+)px/)?.[1] || Number.POSITIVE_INFINITY)
return {
addEventListener: vi.fn(),
matches: viewportWidth <= maximum,
media: query,
removeEventListener: vi.fn(),
}
}),
})
})
afterEach(() => cleanup())
it('keeps tiers separate and applies Partner sortOrder only within each tier', () => {
const grouped = groupSponsorsByTier(partners)
expect(grouped[1].map(({ name }) => name)).toEqual(['Tier One A', 'Tier One B'])
expect(grouped[2].map(({ name }) => name)).toEqual(['Tier Two A', 'Tier Two B'])
expect(grouped[3].map(({ name }) => name)).toEqual([
'Tier Three A',
'Tier Three B',
'Tier Three C',
'Tier Three D',
'Tier Three E',
'Tier Three F',
'Tier Three G',
'Tier Three H',
])
expect([...grouped[1], ...grouped[2], ...grouped[3]].map(({ name }) => name)).toEqual([
'Tier One A',
'Tier One B',
'Tier Two A',
'Tier Two B',
'Tier Three A',
'Tier Three B',
'Tier Three C',
'Tier Three D',
'Tier Three E',
'Tier Three F',
'Tier Three G',
'Tier Three H',
])
})
it('defines the approved columns and conservative narrow-mobile fallback', () => {
expect([1, 2, 3].map((tier) => sponsorColumns(tier as 1 | 2 | 3, 'desktop'))).toEqual([2, 3, 4])
expect([1, 2, 3].map((tier) => sponsorColumns(tier as 1 | 2 | 3, 'tablet'))).toEqual([2, 2, 3])
expect([1, 2, 3].map((tier) => sponsorColumns(tier as 1 | 2 | 3, 'mobile'))).toEqual([1, 1, 2])
expect([1, 2, 3].map((tier) => sponsorColumns(tier as 1 | 2 | 3, 'narrow-mobile'))).toEqual([
1, 1, 1,
])
expect(
sponsorViewport({ isMobile: false, isNarrowMobile: false, isTabletOrMobile: true }),
).toBe('tablet')
expect(sponsorCardBasis(3, 16)).toBe('calc((100% - 32px) / 3)')
})
it('renders every visible sponsor-section label from Netzwerk CMS fields', () => {
renderNetwork(1440)
expect(screen.getByText(sponsorCopy.eyebrow)).toBeTruthy()
expect(screen.getByText(sponsorCopy.heading)).toBeTruthy()
expect(screen.getByText(sponsorCopy.description)).toBeTruthy()
sponsorCopy.tiers.forEach(({ label, note }) => {
expect(screen.getByRole('heading', { name: label })).toBeTruthy()
expect(screen.getByText(note)).toBeTruthy()
})
expect(screen.getAllByText(sponsorCopy.detailLabel)).toHaveLength(partners.length)
expect(screen.queryByText(netzwerkContent.partners.description)).toBeNull()
netzwerkContent.partners.tiers.forEach(({ label }) =>
expect(screen.queryByText(label)).toBeNull(),
)
})
it.each([
{ width: 1440, viewport: 'desktop', columns: [2, 3, 4] },
{ width: 1024, viewport: 'tablet', columns: [2, 2, 3] },
{ width: 390, viewport: 'mobile', columns: [1, 1, 2] },
{ width: 340, viewport: 'narrow-mobile', columns: [1, 1, 1] },
])('centers independent bands at $width px', ({ width, viewport, columns }) => {
renderNetwork(width)
columns.forEach((columnCount, index) => {
const tier = index + 1
const band = screen.getByTestId(`sponsor-tier-${tier}`)
const cards = screen.getByTestId(`sponsor-tier-${tier}-cards`)
expect(band.getAttribute('data-columns')).toBe(String(columnCount))
expect(band.getAttribute('data-viewport')).toBe(viewport)
expect(cards.style.display).toBe('flex')
expect(cards.style.flexWrap).toBe('wrap')
expect(cards.style.justifyContent).toBe('center')
expect(within(band).getAllByRole('button')).toHaveLength(tier === 3 ? 8 : 2)
})
})
it('keeps sponsor defaults and legacy labels out of the rendered component source', () => {
const source = readFileSync(join(process.cwd(), 'src/spa/pages/Netzwerk.tsx'), 'utf8')
const visibleDefaults = [
netzwerkContent.partners.eyebrow,
netzwerkContent.partners.heading,
netzwerkContent.partners.description,
netzwerkContent.partners.detailLabel,
...netzwerkContent.partners.tiers.map(({ label }) => label),
'Partner in drei Kategorien: Hauptsponsoren, Medienpartner und weitere Sponsoren. Klicken für Details.',
]
visibleDefaults.forEach((copy) => {
expect(source).not.toContain(`'${copy}'`)
expect(source).not.toContain(`"${copy}"`)
})
})
})