204 lines
6.8 KiB
TypeScript
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}"`)
|
|
})
|
|
})
|
|
})
|