Skip to content

Commit 677c04d

Browse files
gustavo-olmedodependabot[bot]Jakeii
authored
Puzzles/add dcr base page pipeline (#16656)
* Add puzzles page to define payload contract * Add an experiment helper to centralize name and enabled group * Update types remove useless id * Add recursive validation for frontend payload * Add base layout for puzzles * Add initial puzzle page * Add the html renderer, this converts a validated puzzles page into a complete html doc * Add guarded request handler * Update to register routes, these expose dev GET /PuzzlesPage/*url nad POST /PuzzlesPage for prod & dev * Add shared test fixtures * Add tests for puzzles hub experiment lib * Add tests for the validation puzzles part * Add tests for 200, 400, invalid payloads, and using the experiment * chore(deps): bump the storybook group across 1 directory with 4 updates (#16600) Bumps the storybook group with 4 updates in the / directory: [@storybook/addon-a11y](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/a11y), [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs), [@storybook/react-webpack5](https://github.com/storybookjs/storybook/tree/HEAD/code/frameworks/react-webpack5) and [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/core). Updates `@storybook/addon-a11y` from 10.5.7 to 10.5.10 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.10/code/addons/a11y) Updates `@storybook/addon-docs` from 10.5.7 to 10.5.10 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.10/code/addons/docs) Updates `@storybook/react-webpack5` from 10.5.7 to 10.5.10 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.10/code/frameworks/react-webpack5) Updates `storybook` from 10.5.7 to 10.5.10 - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v10.5.10/code/core) --- updated-dependencies: - dependency-name: "@storybook/addon-a11y" dependency-version: 10.5.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/addon-docs" dependency-version: 10.5.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: storybook - dependency-name: "@storybook/react-webpack5" dependency-version: 10.5.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: storybook - dependency-name: storybook dependency-version: 10.5.10 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: storybook ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * prevent adding 100% tests without intermediate smaller test (#16623) * Remove superfluous custom resource logging (#16665) * Fix build * Fix run chromatic --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jake <1731150+Jakeii@users.noreply.github.com>
1 parent 78c6b29 commit 677c04d

13 files changed

Lines changed: 957 additions & 0 deletions
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import type {
2+
FEPuzzlesPageType,
3+
PuzzlesLayoutType,
4+
} from '../../src/types/puzzlesPage';
5+
import { Standard } from '../generated/fe-articles/Standard';
6+
7+
export const minimalPuzzlesLayout: PuzzlesLayoutType = {
8+
filters: [],
9+
containers: [],
10+
};
11+
12+
export const fullPuzzlesLayout: PuzzlesLayoutType = {
13+
filters: [
14+
{
15+
id: 'word-games',
16+
title: 'Word games',
17+
target: '#word-games',
18+
backgroundColour: '#f9d4e8',
19+
},
20+
],
21+
containers: [
22+
{
23+
title: 'Word games',
24+
variant: 'standard',
25+
filterId: 'word-games',
26+
content: {
27+
items: [
28+
[
29+
{
30+
id: 'wordiply-daily',
31+
title: 'Wordiply',
32+
type: 'wordiply',
33+
set: 'all',
34+
cardVariant: 'primary',
35+
cadence: 'Daily',
36+
slug: 'wordiply',
37+
url: 'https://www.wordiply.com/',
38+
variant: 'iframe-page',
39+
backgroundColour: '#f9d4e8',
40+
filterId: 'word-games',
41+
},
42+
],
43+
],
44+
nestedContainers: [
45+
{
46+
title: 'Word wheel',
47+
desktopSpan: 6,
48+
content: {
49+
items: [
50+
[
51+
{
52+
id: 'word-wheel-daily',
53+
title: 'Word wheel',
54+
type: 'word-wheel',
55+
set: 'all',
56+
cardVariant: 'compact',
57+
cadence: 'Daily',
58+
index: 1,
59+
},
60+
],
61+
],
62+
nestedContainers: [],
63+
archive: {
64+
id: 'word-wheel-archive',
65+
title: 'Word wheel archive',
66+
type: 'word-wheel',
67+
set: 'all',
68+
cardVariant: 'archive',
69+
slug: 'word-wheel',
70+
url: '/puzzles/word-wheel/archive',
71+
variant: 'archive-page',
72+
},
73+
},
74+
},
75+
],
76+
},
77+
},
78+
],
79+
};
80+
81+
export const createPuzzlesPage = (
82+
overrides: Partial<FEPuzzlesPageType> = {},
83+
): FEPuzzlesPageType => ({
84+
id: 'puzzles',
85+
editionId: Standard.editionId,
86+
editionLongForm: Standard.editionLongForm,
87+
contributionsServiceUrl: Standard.contributionsServiceUrl,
88+
webTitle: 'Puzzles & Games',
89+
description: 'Play the Guardian’s daily puzzles and games.',
90+
config: {
91+
...Standard.config,
92+
contentType: 'Puzzles',
93+
serverSideABTests: {},
94+
},
95+
nav: Standard.nav,
96+
pageFooter: Standard.pageFooter,
97+
commercialProperties: Standard.commercialProperties,
98+
isAdFreeUser: false,
99+
canonicalUrl: 'https://www.theguardian.com/puzzles',
100+
layout: minimalPuzzlesLayout,
101+
...overrides,
102+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Global } from '@emotion/react';
2+
import { StrictMode } from 'react';
3+
import { PuzzlesLayout } from '../layouts/PuzzlesLayout';
4+
import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat';
5+
import { rootStyles } from '../lib/rootStyles';
6+
import type { NavType } from '../model/extract-nav';
7+
import type { FEPuzzlesPageType } from '../types/puzzlesPage';
8+
import { AdmiralScript } from './AdmiralScript.island';
9+
import { AlreadyVisited } from './AlreadyVisited.island';
10+
import { useConfig } from './ConfigContext';
11+
import { FocusStyles } from './FocusStyles.island';
12+
import { Island } from './Island';
13+
import { Metrics } from './Metrics.island';
14+
import { SetABTests } from './SetABTests.island';
15+
import { SkipTo } from './SkipTo';
16+
17+
type Props = {
18+
puzzlesPage: FEPuzzlesPageType;
19+
NAV: NavType;
20+
};
21+
22+
export const PuzzlesPage = ({ puzzlesPage, NAV }: Props) => {
23+
const format = {
24+
display: ArticleDisplay.Standard,
25+
design: ArticleDesign.Standard,
26+
theme: Pillar.Lifestyle,
27+
};
28+
const { darkModeAvailable } = useConfig();
29+
30+
return (
31+
<StrictMode>
32+
<Global styles={rootStyles(format, darkModeAvailable)} />
33+
<SkipTo id="maincontent" label="Skip to main content" />
34+
<SkipTo id="navigation" label="Skip to navigation" />
35+
<Island priority="feature" defer={{ until: 'idle' }}>
36+
<AlreadyVisited />
37+
</Island>
38+
<Island priority="feature" defer={{ until: 'idle' }}>
39+
<AdmiralScript />
40+
</Island>
41+
<Island priority="feature" defer={{ until: 'idle' }}>
42+
<FocusStyles />
43+
</Island>
44+
<Island priority="critical">
45+
<Metrics
46+
commercialMetricsEnabled={
47+
!!puzzlesPage.config.switches.commercialMetrics
48+
}
49+
/>
50+
</Island>
51+
<Island priority="critical">
52+
<SetABTests
53+
serverSideABTests={puzzlesPage.config.serverSideABTests}
54+
/>
55+
</Island>
56+
<PuzzlesLayout puzzlesPage={puzzlesPage} NAV={NAV} />
57+
</StrictMode>
58+
);
59+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { css } from '@emotion/react';
2+
import { headlineBold34, palette, space } from '@guardian/source/foundations';
3+
import { Footer } from '../components/Footer';
4+
import { HeaderAdSlot } from '../components/HeaderAdSlot';
5+
import { Masthead } from '../components/Masthead/Masthead';
6+
import { Section } from '../components/Section';
7+
import type { NavType } from '../model/extract-nav';
8+
import type { FEPuzzlesPageType } from '../types/puzzlesPage';
9+
import { Stuck } from './lib/stickiness';
10+
11+
type Props = {
12+
puzzlesPage: FEPuzzlesPageType;
13+
NAV: NavType;
14+
};
15+
16+
const heading = css`
17+
margin: 0;
18+
padding: ${space[6]}px 0 ${space[12]}px;
19+
${headlineBold34};
20+
`;
21+
22+
/**
23+
* The base puzzles page shell. Blueprint-driven composition is intentionally
24+
* deferred to the next task.
25+
*/
26+
export const PuzzlesLayout = ({ puzzlesPage, NAV }: Props) => (
27+
<>
28+
<div data-print-layout="hide" id="bannerandheader">
29+
{!puzzlesPage.isAdFreeUser && (
30+
<Stuck>
31+
<Section
32+
fullWidth={true}
33+
showTopBorder={false}
34+
showSideBorders={false}
35+
padSides={false}
36+
shouldCenter={false}
37+
>
38+
<HeaderAdSlot />
39+
</Section>
40+
</Stuck>
41+
)}
42+
<Masthead
43+
nav={NAV}
44+
editionId={puzzlesPage.editionId}
45+
idUrl={puzzlesPage.config.idUrl}
46+
mmaUrl={puzzlesPage.config.mmaUrl}
47+
discussionApiUrl={puzzlesPage.config.discussionApiUrl}
48+
idApiUrl={puzzlesPage.config.idApiUrl}
49+
contributionsServiceUrl={puzzlesPage.contributionsServiceUrl}
50+
showSubNav={true}
51+
showSlimNav={false}
52+
hasPageSkin={false}
53+
hasPageSkinContentSelfConstrain={false}
54+
/>
55+
</div>
56+
57+
<main data-layout="PuzzlesPageLayout" id="maincontent">
58+
<Section fullWidth={true} showTopBorder={false}>
59+
<h1 css={heading}>{puzzlesPage.webTitle}</h1>
60+
</Section>
61+
</main>
62+
63+
<Section
64+
fullWidth={true}
65+
padSides={false}
66+
backgroundColour={palette.brand[400]}
67+
borderColour={palette.brand[600]}
68+
showSideBorders={false}
69+
element="footer"
70+
>
71+
<Footer
72+
pageFooter={puzzlesPage.pageFooter}
73+
pillars={NAV.pillars}
74+
urls={NAV.readerRevenueLinks.footer}
75+
editionId={puzzlesPage.editionId}
76+
/>
77+
</Section>
78+
</>
79+
);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {
2+
isPuzzlesHubEnabled,
3+
puzzlesHubExperiment,
4+
puzzlesHubParticipation,
5+
} from './puzzlesHubExperiment';
6+
7+
describe('isPuzzlesHubEnabled', () => {
8+
it('enables only the configured variant', () => {
9+
expect(
10+
isPuzzlesHubEnabled({
11+
serverSideABTests: puzzlesHubParticipation(
12+
puzzlesHubExperiment.variant,
13+
),
14+
}),
15+
).toBe(true);
16+
});
17+
18+
it.each([
19+
puzzlesHubParticipation(puzzlesHubExperiment.control),
20+
puzzlesHubParticipation('unknown'),
21+
puzzlesHubParticipation('variant:extra'),
22+
{},
23+
{ 'another-test': 'variant' },
24+
])('returns false for non-variant participation %#', (participations) => {
25+
expect(isPuzzlesHubEnabled({ serverSideABTests: participations })).toBe(
26+
false,
27+
);
28+
});
29+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { ConfigType } from '../types/config';
2+
3+
export const puzzlesHubExperiment = {
4+
name: 'puzzles-new-hub',
5+
variant: 'variant',
6+
control: 'control',
7+
} as const;
8+
9+
type PuzzlesExperimentConfig = Pick<ConfigType, 'serverSideABTests'>;
10+
11+
export const isPuzzlesHubEnabled = ({
12+
serverSideABTests,
13+
}: PuzzlesExperimentConfig): boolean =>
14+
serverSideABTests[puzzlesHubExperiment.name] ===
15+
puzzlesHubExperiment.variant;
16+
17+
export const puzzlesHubParticipation = (
18+
group: string,
19+
): Record<string, string> => ({
20+
[puzzlesHubExperiment.name]: group,
21+
});

0 commit comments

Comments
 (0)