Skip to content

Commit d744cea

Browse files
Update PuzzleIframe.island.test.tsx for the refactored responsibility split
Renders now pass a real PuzzleConfig (puzzleConfig prop) instead of a pre-resolved src string. buildPuzzleIframeSrc's tests now assert the provider URL is resolved via resolvePuzzleIframeUrl before guardian-puzzle-context is layered on top, and add coverage for a non-AmuseLabs provider (wordiply) confirming uid/darkMode are not added for it while guardian-puzzle-context still is, i.e. the two mechanisms coexist correctly and neither breaks the other. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent c3a4c99 commit d744cea

1 file changed

Lines changed: 78 additions & 61 deletions

File tree

dotcom-rendering/src/components/PuzzleIframe.island.test.tsx

Lines changed: 78 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { act, render, screen, waitFor } from '@testing-library/react';
22
import { getAuthStatus, subscribeToAuthStateChange } from '../lib/identity';
33
import { useMatchMedia } from '../lib/useMatchMedia';
4+
import { puzzleConfigs } from '../model/puzzles/puzzleConfigs';
45
import {
56
buildPuzzleIframeSrc,
67
type PuzzleContext,
@@ -35,79 +36,65 @@ const signedOut = () => ({ kind: 'SignedOut' as const });
3536
const contextParam = (context: PuzzleContext) =>
3637
`guardian-puzzle-context=${encodeURIComponent(JSON.stringify(context))}`;
3738

39+
const sudokuEasyConfig = puzzleConfigs['sudoku-easy']!;
40+
const wordiplyConfig = puzzleConfigs.wordiply!;
41+
3842
describe('buildPuzzleIframeSrc', () => {
39-
it('appends the context as a JSON query param when the src has none', () => {
43+
it('resolves the provider URL then appends guardian-puzzle-context on top', () => {
4044
const context: PuzzleContext = {
4145
userId: null,
4246
darkMode: false,
4347
puzzleDate: null,
4448
};
45-
expect(
46-
buildPuzzleIframeSrc('https://example.com/puzzle', context),
47-
).toBe(`https://example.com/puzzle?${contextParam(context)}`);
49+
expect(buildPuzzleIframeSrc(sudokuEasyConfig, context)).toBe(
50+
`https://tg.amuselabs.com/guardian/date-picker?set=guardian-sudoku-easy&embed=1&idx=1&darkMode=0&${contextParam(context)}`,
51+
);
4852
});
4953

50-
it('preserves existing query params when appending the context', () => {
54+
it('preserves the provider-resolved uid/darkMode params when appending guardian-puzzle-context', () => {
5155
const context: PuzzleContext = {
5256
userId: 'abc123',
5357
darkMode: true,
5458
puzzleDate: '2026-09-15',
5559
};
56-
expect(
57-
buildPuzzleIframeSrc(
58-
'https://example.com/puzzle?set=guardian-sudoku-easy&embed=1',
59-
context,
60-
),
61-
).toBe(
62-
`https://example.com/puzzle?set=guardian-sudoku-easy&embed=1&${contextParam(context)}&uid=abc123`,
60+
const src = buildPuzzleIframeSrc(sudokuEasyConfig, context);
61+
const url = new URL(src);
62+
63+
expect(url.searchParams.get('uid')).toBe('abc123');
64+
expect(url.searchParams.get('darkMode')).toBe('1');
65+
expect(url.searchParams.get('guardian-puzzle-context')).toBe(
66+
JSON.stringify(context),
6367
);
6468
});
6569

66-
it('always includes the context, even when signed out and dark mode is off', () => {
70+
it('always includes guardian-puzzle-context, even when signed out and dark mode is off', () => {
6771
const context: PuzzleContext = {
6872
userId: null,
6973
darkMode: false,
7074
puzzleDate: null,
7175
};
72-
expect(
73-
buildPuzzleIframeSrc('https://example.com/puzzle', context),
74-
).toContain('guardian-puzzle-context=');
75-
});
76-
77-
it('returns the src unchanged if it cannot be parsed as an absolute URL', () => {
78-
expect(
79-
buildPuzzleIframeSrc('not-a-url', {
80-
userId: null,
81-
darkMode: false,
82-
puzzleDate: null,
83-
}),
84-
).toBe('not-a-url');
85-
});
86-
87-
it('appends uid alongside guardian-puzzle-context when the reader is signed in', () => {
88-
const context: PuzzleContext = {
89-
userId: 'user-123',
90-
darkMode: false,
91-
puzzleDate: null,
92-
};
93-
const src = buildPuzzleIframeSrc('https://example.com/puzzle', context);
94-
const url = new URL(src);
95-
96-
expect(url.searchParams.get('uid')).toBe('user-123');
97-
expect(url.searchParams.has('guardian-puzzle-context')).toBe(true);
76+
expect(buildPuzzleIframeSrc(sudokuEasyConfig, context)).toContain(
77+
'guardian-puzzle-context=',
78+
);
9879
});
9980

100-
it('omits uid entirely when the reader is signed out (not uid=null or empty)', () => {
81+
it('applies guardian-puzzle-context uniformly to a non-AmuseLabs provider too (wordiply)', () => {
10182
const context: PuzzleContext = {
102-
userId: null,
103-
darkMode: false,
83+
userId: 'abc123',
84+
darkMode: true,
10485
puzzleDate: null,
10586
};
106-
const src = buildPuzzleIframeSrc('https://example.com/puzzle', context);
87+
const src = buildPuzzleIframeSrc(wordiplyConfig, context);
10788
const url = new URL(src);
10889

90+
expect(url.origin + url.pathname).toBe('https://www.wordiply.com/');
91+
expect(url.searchParams.get('guardian-puzzle-context')).toBe(
92+
JSON.stringify(context),
93+
);
94+
// Wordiply has no confirmed uid/darkMode query param support, so
95+
// neither is provider-added, only DCR's own generic context blob is.
10996
expect(url.searchParams.has('uid')).toBe(false);
110-
expect(url.searchParams.has('guardian-puzzle-context')).toBe(true);
97+
expect(url.searchParams.has('darkMode')).toBe(false);
11198
});
11299
});
113100

@@ -131,12 +118,15 @@ describe('PuzzleIframe', () => {
131118
const getUidFromSrc = (src: string): string | null =>
132119
new URL(src).searchParams.get('uid');
133120

121+
const getDarkModeParamFromSrc = (src: string): string | null =>
122+
new URL(src).searchParams.get('darkMode');
123+
134124
it('renders userId: null and darkMode: false while signed out with dark mode unavailable', async () => {
135125
mockedGetAuthStatus.mockResolvedValue(signedOut());
136126

137127
render(
138128
<PuzzleIframe
139-
src="https://example.com/puzzle"
129+
puzzleConfig={sudokuEasyConfig}
140130
title="Puzzle"
141131
darkModeAvailable={false}
142132
puzzleDate={null}
@@ -158,7 +148,7 @@ describe('PuzzleIframe', () => {
158148

159149
render(
160150
<PuzzleIframe
161-
src="https://example.com/puzzle"
151+
puzzleConfig={sudokuEasyConfig}
162152
title="Puzzle"
163153
darkModeAvailable={false}
164154
puzzleDate={null}
@@ -181,7 +171,7 @@ describe('PuzzleIframe', () => {
181171

182172
render(
183173
<PuzzleIframe
184-
src="https://example.com/puzzle"
174+
puzzleConfig={sudokuEasyConfig}
185175
title="Puzzle"
186176
darkModeAvailable={false}
187177
puzzleDate={null}
@@ -200,7 +190,7 @@ describe('PuzzleIframe', () => {
200190

201191
render(
202192
<PuzzleIframe
203-
src="https://example.com/puzzle"
193+
puzzleConfig={sudokuEasyConfig}
204194
title="Puzzle"
205195
darkModeAvailable={true}
206196
puzzleDate={null}
@@ -219,7 +209,7 @@ describe('PuzzleIframe', () => {
219209

220210
const { rerender } = render(
221211
<PuzzleIframe
222-
src="https://example.com/puzzle"
212+
puzzleConfig={sudokuEasyConfig}
223213
title="Puzzle"
224214
darkModeAvailable={true}
225215
puzzleDate={null}
@@ -239,7 +229,7 @@ describe('PuzzleIframe', () => {
239229
mockedUseMatchMedia.mockReturnValue(true);
240230
rerender(
241231
<PuzzleIframe
242-
src="https://example.com/puzzle"
232+
puzzleConfig={sudokuEasyConfig}
243233
title="Puzzle"
244234
darkModeAvailable={true}
245235
puzzleDate={null}
@@ -257,7 +247,7 @@ describe('PuzzleIframe', () => {
257247

258248
render(
259249
<PuzzleIframe
260-
src="https://example.com/puzzle"
250+
puzzleConfig={sudokuEasyConfig}
261251
title="Puzzle"
262252
darkModeAvailable={true}
263253
puzzleDate={null}
@@ -293,7 +283,7 @@ describe('PuzzleIframe', () => {
293283

294284
render(
295285
<PuzzleIframe
296-
src="https://example.com/puzzle"
286+
puzzleConfig={sudokuEasyConfig}
297287
title="Puzzle"
298288
darkModeAvailable={false}
299289
puzzleDate={null}
@@ -324,7 +314,7 @@ describe('PuzzleIframe', () => {
324314

325315
const { unmount } = render(
326316
<PuzzleIframe
327-
src="https://example.com/puzzle"
317+
puzzleConfig={sudokuEasyConfig}
328318
title="Puzzle"
329319
darkModeAvailable={false}
330320
puzzleDate={null}
@@ -342,7 +332,7 @@ describe('PuzzleIframe', () => {
342332

343333
render(
344334
<PuzzleIframe
345-
src="https://example.com/puzzle"
335+
puzzleConfig={sudokuEasyConfig}
346336
title="Puzzle"
347337
darkModeAvailable={false}
348338
puzzleDate="2026-09-15"
@@ -360,7 +350,7 @@ describe('PuzzleIframe', () => {
360350

361351
render(
362352
<PuzzleIframe
363-
src="https://example.com/puzzle"
353+
puzzleConfig={sudokuEasyConfig}
364354
title="Puzzle"
365355
darkModeAvailable={false}
366356
puzzleDate={null}
@@ -373,31 +363,33 @@ describe('PuzzleIframe', () => {
373363
);
374364
});
375365

376-
it('includes uid alongside guardian-puzzle-context once signed in', async () => {
366+
it('includes uid and darkMode=1 (AmuseLabs-specific) alongside guardian-puzzle-context once signed in with dark mode on', async () => {
377367
mockedGetAuthStatus.mockResolvedValue(signedIn('user-123'));
368+
mockedUseMatchMedia.mockReturnValue(true);
378369

379370
render(
380371
<PuzzleIframe
381-
src="https://example.com/puzzle"
372+
puzzleConfig={sudokuEasyConfig}
382373
title="Puzzle"
383-
darkModeAvailable={false}
374+
darkModeAvailable={true}
384375
puzzleDate={null}
385376
/>,
386377
);
387378

388379
const iframe = await screen.findByTitle<HTMLIFrameElement>('Puzzle');
389380
await waitFor(() => expect(getUidFromSrc(iframe.src)).toBe('user-123'));
381+
expect(getDarkModeParamFromSrc(iframe.src)).toBe('1');
390382
expect(
391383
new URL(iframe.src).searchParams.has('guardian-puzzle-context'),
392384
).toBe(true);
393385
});
394386

395-
it('omits uid entirely while signed out (not uid=null or empty)', async () => {
387+
it('omits uid entirely while signed out, but still sends darkMode=0 (AmuseLabs-specific)', async () => {
396388
mockedGetAuthStatus.mockResolvedValue(signedOut());
397389

398390
render(
399391
<PuzzleIframe
400-
src="https://example.com/puzzle"
392+
puzzleConfig={sudokuEasyConfig}
401393
title="Puzzle"
402394
darkModeAvailable={false}
403395
puzzleDate={null}
@@ -409,6 +401,7 @@ describe('PuzzleIframe', () => {
409401
expect(getContextFromSrc(iframe.src).userId).toBeNull(),
410402
);
411403
expect(new URL(iframe.src).searchParams.has('uid')).toBe(false);
404+
expect(getDarkModeParamFromSrc(iframe.src)).toBe('0');
412405
expect(
413406
new URL(iframe.src).searchParams.has('guardian-puzzle-context'),
414407
).toBe(true);
@@ -419,7 +412,7 @@ describe('PuzzleIframe', () => {
419412

420413
render(
421414
<PuzzleIframe
422-
src="https://example.com/puzzle"
415+
puzzleConfig={sudokuEasyConfig}
423416
title="Puzzle"
424417
darkModeAvailable={false}
425418
puzzleDate={null}
@@ -438,4 +431,28 @@ describe('PuzzleIframe', () => {
438431

439432
await waitFor(() => expect(getUidFromSrc(iframe.src)).toBeNull());
440433
});
434+
435+
it('does not add uid/darkMode query params for a non-AmuseLabs provider (wordiply)', async () => {
436+
mockedGetAuthStatus.mockResolvedValue(signedIn('user-123'));
437+
mockedUseMatchMedia.mockReturnValue(true);
438+
439+
render(
440+
<PuzzleIframe
441+
puzzleConfig={wordiplyConfig}
442+
title="Puzzle"
443+
darkModeAvailable={true}
444+
puzzleDate={null}
445+
/>,
446+
);
447+
448+
const iframe = await screen.findByTitle<HTMLIFrameElement>('Puzzle');
449+
await waitFor(() =>
450+
expect(
451+
new URL(iframe.src).searchParams.has('guardian-puzzle-context'),
452+
).toBe(true),
453+
);
454+
expect(new URL(iframe.src).searchParams.has('uid')).toBe(false);
455+
expect(new URL(iframe.src).searchParams.has('darkMode')).toBe(false);
456+
expect(iframe.src.startsWith('https://www.wordiply.com/')).toBe(true);
457+
});
441458
});

0 commit comments

Comments
 (0)