Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions dotcom-rendering/src/components/PuzzlesDirectory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ import type {
} from '../types/puzzlesPage';
import { getPuzzleUrl, PuzzlesDirectory } from './PuzzlesDirectory';

jest.mock('./AdSlot.web', () => ({
AdSlot: ({ index }: { index: number }) => (
<div data-testid={`ad-${index}`} />
),
}));
jest.mock('./Island', () => ({
Island: ({ children }: { children: ReactNode }) => children,
}));
Expand All @@ -38,6 +33,36 @@ const section = (
});

describe('PuzzlesDirectory', () => {
it('renders unique desktop and mobile IDs for multiple blueprint slots', () => {
const layout: PuzzlesLayoutType = {
containers: ['inline1', 'inline2'].map((adSlot) =>
section({
id: adSlot,
title: '',
variant: 'ad',
adSlot,
content: { items: [], nestedContainers: [] },
}),
),
};
const { rerender } = render(
<PuzzlesDirectory layout={layout} renderAds={true} />,
);
const ids = Array.from(
document.querySelectorAll('.js-ad-slot'),
({ id }) => id,
);
expect(ids).toEqual([
'dfp-ad--fronts-banner-1',
'dfp-ad--inline1--mobile',
'dfp-ad--fronts-banner-2',
'dfp-ad--inline2--mobile',
]);
expect(new Set(ids).size).toBe(ids.length);
rerender(<PuzzlesDirectory layout={layout} renderAds={false} />);
expect(document.querySelector('.js-ad-slot')).not.toBeInTheDocument();
});

it('does not render a disabled featured container', () => {
render(
<PuzzlesDirectory
Expand Down Expand Up @@ -176,9 +201,14 @@ describe('PuzzlesDirectory', () => {
screen.queryByRole('heading', { name: 'Empty' }),
).not.toBeInTheDocument();
expect(document.querySelector('img')).not.toBeInTheDocument();
expect(screen.queryByTestId('ad-2')).not.toBeInTheDocument();
expect(document.querySelector('.js-ad-slot')).not.toBeInTheDocument();
rerender(<PuzzlesDirectory layout={layout} renderAds={true} />);
expect(screen.getByTestId('ad-2')).toBeInTheDocument();
expect(
document.getElementById('dfp-ad--fronts-banner-2'),
).toHaveAttribute('data-name', 'fronts-banner-2');
expect(
document.getElementById('dfp-ad--inline2--mobile'),
).toHaveAttribute('data-name', 'inline2');
});

it('renders the archive dropdown and closes it with Escape or an outside click', async () => {
Expand Down
1 change: 1 addition & 0 deletions dotcom-rendering/src/components/PuzzlesDirectory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ export const PuzzlesDirectory = ({ layout, renderAds }: Props) => (
index={index}
position="fronts-banner"
/>
<AdSlot position="mobile-front" index={index} />
</div>
);
}
Expand Down
12 changes: 7 additions & 5 deletions dotcom-rendering/src/layouts/PuzzlesLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import { PuzzlesLayout } from './PuzzlesLayout';
jest.mock('../components/Masthead/Masthead', () => ({
Masthead: () => <header data-testid="masthead" />,
}));
jest.mock('../components/HeaderAdSlot', () => ({
HeaderAdSlot: () => <div data-testid="header-ad" />,
}));
jest.mock('../components/Footer', () => ({
Footer: () => <div data-testid="footer" />,
}));
Expand Down Expand Up @@ -51,12 +48,17 @@ describe('PuzzlesLayout', () => {
const { rerender } = render(
<PuzzlesLayout NAV={nav} puzzlesPage={page(true) as never} />,
);
expect(screen.queryByTestId('header-ad')).not.toBeInTheDocument();
expect(document.querySelector('.js-ad-slot')).not.toBeInTheDocument();
expect(screen.getByTestId('masthead')).toBeInTheDocument();
expect(screen.getByTestId('footer')).toBeInTheDocument();
rerender(
<PuzzlesLayout NAV={nav} puzzlesPage={page(false) as never} />,
);
expect(screen.getByTestId('header-ad')).toBeInTheDocument();
expect(
document.getElementById('dfp-ad--top-above-nav'),
).toBeInTheDocument();
expect(
document.getElementById('dfp-ad--mobile-above-nav'),
).not.toBeInTheDocument();
});
});
62 changes: 62 additions & 0 deletions dotcom-rendering/src/model/validate.puzzlesPage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,68 @@ const validPage = () => ({
});

describe('validateAsPuzzlesPageType', () => {
it.each(['inline1', 'mostpop'])(
'rejects repeated %s slot names',
(adSlot) => {
const page = validPage();
const container = {
id: 'first-ad',
title: '',
variant: adSlot === 'mostpop' ? 'supporting' : 'ad',
adSlot,
content: { items: [], nestedContainers: [] },
...(adSlot === 'mostpop'
? {
supporting: {
usefulLinksTitle: 'Useful links',
usefulLinks: [],
popularTitle: 'Most popular puzzles',
popularGroups: [],
},
}
: {}),
};
page.layout.containers.push(container as never);
expect(validateAsPuzzlesPageType(page)).toBeDefined();
page.layout.containers.push({
...container,
id: 'second-ad',
} as never);
expect(() => validateAsPuzzlesPageType(page)).toThrow(
'Unable to validate request body for puzzles page',
);
},
);

it.each(['inline0', 'inline-1', 'hub-inline', 'inline1junk', undefined])(
'rejects an unsupported ad slot: %s',
(adSlot) => {
const page = validPage();
page.layout.containers.push({
id: 'invalid-ad',
title: '',
variant: 'ad',
adSlot,
content: { items: [], nestedContainers: [] },
} as never);
expect(() => validateAsPuzzlesPageType(page)).toThrow();
},
);

it('accepts distinct inline slot names', () => {
const page = validPage();
for (const adSlot of ['inline1', 'inline2']) {
page.layout.containers.push({
id: adSlot,
title: '',
variant: 'ad',
adSlot,
content: { items: [], nestedContainers: [] },
} as never);
}
expect(validateAsPuzzlesPageType(page)).toBeDefined();
});

it('accepts a valid recursive blueprint contract', () => {
expect(
validateAsPuzzlesPageType(validPage()).layout.containers[0]?.id,
Expand Down
4 changes: 4 additions & 0 deletions dotcom-rendering/src/model/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ export const validateAsPuzzlesPageType = (data: unknown): FEPuzzlesPageType => {
const unique = (values: string[]) => new Set(values).size === values.length;
const containerIds = containers.map(({ id }) => id);
const itemIds = items.map(({ id }) => id);
const adSlots = containers.flatMap(({ adSlot }) =>
adSlot === undefined ? [] : [adSlot],
);
const popularReferencesValid = containers.every((container) =>
(container.supporting?.popularGroups ?? []).every((group) =>
group.itemIds.every((id) => itemIds.includes(id)),
Expand All @@ -366,6 +369,7 @@ export const validateAsPuzzlesPageType = (data: unknown): FEPuzzlesPageType => {
if (
!unique(containerIds) ||
!unique(itemIds) ||
!unique(adSlots) ||
!popularReferencesValid ||
!topLevelOnlyContainersValid
) {
Expand Down
Loading