Skip to content

Commit 7274a58

Browse files
Add mobile inline advertising to the Puzzles hub (#16718)
1 parent 6509a9f commit 7274a58

5 files changed

Lines changed: 111 additions & 12 deletions

File tree

dotcom-rendering/src/components/PuzzlesDirectory.test.tsx

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ import type {
88
} from '../types/puzzlesPage';
99
import { getPuzzleUrl, PuzzlesDirectory } from './PuzzlesDirectory';
1010

11-
jest.mock('./AdSlot.web', () => ({
12-
AdSlot: ({ index }: { index: number }) => (
13-
<div data-testid={`ad-${index}`} />
14-
),
15-
}));
1611
jest.mock('./Island', () => ({
1712
Island: ({ children }: { children: ReactNode }) => children,
1813
}));
@@ -38,6 +33,36 @@ const section = (
3833
});
3934

4035
describe('PuzzlesDirectory', () => {
36+
it('renders unique desktop and mobile IDs for multiple blueprint slots', () => {
37+
const layout: PuzzlesLayoutType = {
38+
containers: ['inline1', 'inline2'].map((adSlot) =>
39+
section({
40+
id: adSlot,
41+
title: '',
42+
variant: 'ad',
43+
adSlot,
44+
content: { items: [], nestedContainers: [] },
45+
}),
46+
),
47+
};
48+
const { rerender } = render(
49+
<PuzzlesDirectory layout={layout} renderAds={true} />,
50+
);
51+
const ids = Array.from(
52+
document.querySelectorAll('.js-ad-slot'),
53+
({ id }) => id,
54+
);
55+
expect(ids).toEqual([
56+
'dfp-ad--fronts-banner-1',
57+
'dfp-ad--inline1--mobile',
58+
'dfp-ad--fronts-banner-2',
59+
'dfp-ad--inline2--mobile',
60+
]);
61+
expect(new Set(ids).size).toBe(ids.length);
62+
rerender(<PuzzlesDirectory layout={layout} renderAds={false} />);
63+
expect(document.querySelector('.js-ad-slot')).not.toBeInTheDocument();
64+
});
65+
4166
it('does not render a disabled featured container', () => {
4267
render(
4368
<PuzzlesDirectory
@@ -176,9 +201,14 @@ describe('PuzzlesDirectory', () => {
176201
screen.queryByRole('heading', { name: 'Empty' }),
177202
).not.toBeInTheDocument();
178203
expect(document.querySelector('img')).not.toBeInTheDocument();
179-
expect(screen.queryByTestId('ad-2')).not.toBeInTheDocument();
204+
expect(document.querySelector('.js-ad-slot')).not.toBeInTheDocument();
180205
rerender(<PuzzlesDirectory layout={layout} renderAds={true} />);
181-
expect(screen.getByTestId('ad-2')).toBeInTheDocument();
206+
expect(
207+
document.getElementById('dfp-ad--fronts-banner-2'),
208+
).toHaveAttribute('data-name', 'fronts-banner-2');
209+
expect(
210+
document.getElementById('dfp-ad--inline2--mobile'),
211+
).toHaveAttribute('data-name', 'inline2');
182212
});
183213

184214
it('renders the archive dropdown and closes it with Escape or an outside click', async () => {

dotcom-rendering/src/components/PuzzlesDirectory.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ export const PuzzlesDirectory = ({ layout, renderAds }: Props) => (
416416
index={index}
417417
position="fronts-banner"
418418
/>
419+
<AdSlot position="mobile-front" index={index} />
419420
</div>
420421
);
421422
}

dotcom-rendering/src/layouts/PuzzlesLayout.test.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ import { PuzzlesLayout } from './PuzzlesLayout';
66
jest.mock('../components/Masthead/Masthead', () => ({
77
Masthead: () => <header data-testid="masthead" />,
88
}));
9-
jest.mock('../components/HeaderAdSlot', () => ({
10-
HeaderAdSlot: () => <div data-testid="header-ad" />,
11-
}));
129
jest.mock('../components/Footer', () => ({
1310
Footer: () => <div data-testid="footer" />,
1411
}));
@@ -51,12 +48,17 @@ describe('PuzzlesLayout', () => {
5148
const { rerender } = render(
5249
<PuzzlesLayout NAV={nav} puzzlesPage={page(true) as never} />,
5350
);
54-
expect(screen.queryByTestId('header-ad')).not.toBeInTheDocument();
51+
expect(document.querySelector('.js-ad-slot')).not.toBeInTheDocument();
5552
expect(screen.getByTestId('masthead')).toBeInTheDocument();
5653
expect(screen.getByTestId('footer')).toBeInTheDocument();
5754
rerender(
5855
<PuzzlesLayout NAV={nav} puzzlesPage={page(false) as never} />,
5956
);
60-
expect(screen.getByTestId('header-ad')).toBeInTheDocument();
57+
expect(
58+
document.getElementById('dfp-ad--top-above-nav'),
59+
).toBeInTheDocument();
60+
expect(
61+
document.getElementById('dfp-ad--mobile-above-nav'),
62+
).not.toBeInTheDocument();
6163
});
6264
});

dotcom-rendering/src/model/validate.puzzlesPage.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,68 @@ const validPage = () => ({
3838
});
3939

4040
describe('validateAsPuzzlesPageType', () => {
41+
it.each(['inline1', 'mostpop'])(
42+
'rejects repeated %s slot names',
43+
(adSlot) => {
44+
const page = validPage();
45+
const container = {
46+
id: 'first-ad',
47+
title: '',
48+
variant: adSlot === 'mostpop' ? 'supporting' : 'ad',
49+
adSlot,
50+
content: { items: [], nestedContainers: [] },
51+
...(adSlot === 'mostpop'
52+
? {
53+
supporting: {
54+
usefulLinksTitle: 'Useful links',
55+
usefulLinks: [],
56+
popularTitle: 'Most popular puzzles',
57+
popularGroups: [],
58+
},
59+
}
60+
: {}),
61+
};
62+
page.layout.containers.push(container as never);
63+
expect(validateAsPuzzlesPageType(page)).toBeDefined();
64+
page.layout.containers.push({
65+
...container,
66+
id: 'second-ad',
67+
} as never);
68+
expect(() => validateAsPuzzlesPageType(page)).toThrow(
69+
'Unable to validate request body for puzzles page',
70+
);
71+
},
72+
);
73+
74+
it.each(['inline0', 'inline-1', 'hub-inline', 'inline1junk', undefined])(
75+
'rejects an unsupported ad slot: %s',
76+
(adSlot) => {
77+
const page = validPage();
78+
page.layout.containers.push({
79+
id: 'invalid-ad',
80+
title: '',
81+
variant: 'ad',
82+
adSlot,
83+
content: { items: [], nestedContainers: [] },
84+
} as never);
85+
expect(() => validateAsPuzzlesPageType(page)).toThrow();
86+
},
87+
);
88+
89+
it('accepts distinct inline slot names', () => {
90+
const page = validPage();
91+
for (const adSlot of ['inline1', 'inline2']) {
92+
page.layout.containers.push({
93+
id: adSlot,
94+
title: '',
95+
variant: 'ad',
96+
adSlot,
97+
content: { items: [], nestedContainers: [] },
98+
} as never);
99+
}
100+
expect(validateAsPuzzlesPageType(page)).toBeDefined();
101+
});
102+
41103
it('accepts a valid recursive blueprint contract', () => {
42104
expect(
43105
validateAsPuzzlesPageType(validPage()).layout.containers[0]?.id,

dotcom-rendering/src/model/validate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,9 @@ export const validateAsPuzzlesPageType = (data: unknown): FEPuzzlesPageType => {
354354
const unique = (values: string[]) => new Set(values).size === values.length;
355355
const containerIds = containers.map(({ id }) => id);
356356
const itemIds = items.map(({ id }) => id);
357+
const adSlots = containers.flatMap(({ adSlot }) =>
358+
adSlot === undefined ? [] : [adSlot],
359+
);
357360
const popularReferencesValid = containers.every((container) =>
358361
(container.supporting?.popularGroups ?? []).every((group) =>
359362
group.itemIds.every((id) => itemIds.includes(id)),
@@ -366,6 +369,7 @@ export const validateAsPuzzlesPageType = (data: unknown): FEPuzzlesPageType => {
366369
if (
367370
!unique(containerIds) ||
368371
!unique(itemIds) ||
372+
!unique(adSlots) ||
369373
!popularReferencesValid ||
370374
!topLevelOnlyContainersValid
371375
) {

0 commit comments

Comments
 (0)