Skip to content

Commit b27bedc

Browse files
authored
Merge pull request #14232 from guardian/doml/loop-video-interaction-stories
Loop Video: interaction stories
2 parents 1adbeb5 + cad9915 commit b27bedc

File tree

2 files changed

+87
-7
lines changed

2 files changed

+87
-7
lines changed

dotcom-rendering/src/components/LoopVideo.stories.tsx

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { breakpoints } from '@guardian/source/foundations';
22
import type { Meta, StoryObj } from '@storybook/react';
3+
import { expect, userEvent, within } from '@storybook/test';
34
import { centreColumnDecorator } from '../../.storybook/decorators/gridDecorators';
45
import { LoopVideo } from './LoopVideo.importable';
56

6-
export default {
7+
const meta = {
78
component: LoopVideo,
89
title: 'Components/LoopVideo',
910
decorators: [centreColumnDecorator],
@@ -15,7 +16,10 @@ export default {
1516
},
1617
} satisfies Meta<typeof LoopVideo>;
1718

18-
export const Default = {
19+
export default meta;
20+
type Story = StoryObj<typeof LoopVideo>;
21+
22+
export const Default: Story = {
1923
name: 'Default',
2024
args: {
2125
src: 'https://uploads.guim.co.uk/2025%2F06%2F20%2Ftesting+only%2C+please+ignore--3cb22b60-2c3f-48d6-8bce-38c956907cce-3.mp4',
@@ -26,14 +30,84 @@ export const Default = {
2630
image: 'https://media.guim.co.uk/9bdb802e6da5d3fd249b5060f367b3a817965f0c/0_0_1800_1080/master/1800.jpg',
2731
fallbackImage: '',
2832
},
29-
} satisfies StoryObj<typeof LoopVideo>;
33+
};
3034

31-
export const Without5to4Ratio = {
35+
export const Without5to4Ratio: Story = {
3236
name: 'Without 5:4 aspect ratio',
3337
args: {
3438
...Default.args,
3539
src: 'https://uploads.guim.co.uk/2024/10/01/241001HeleneLoop_2.mp4',
3640
height: 1080,
3741
width: 1920,
3842
},
39-
} satisfies StoryObj<typeof LoopVideo>;
43+
};
44+
45+
export const PausePlay: Story = {
46+
...Default,
47+
play: async ({ canvasElement }) => {
48+
const canvas = within(canvasElement);
49+
const videoEl = canvas.getByTestId('loop-video');
50+
51+
await userEvent.click(videoEl, {
52+
delay: 300, // Allow video to start playing.
53+
});
54+
await canvas.findByTestId('play-icon');
55+
56+
const progressBar = await canvas.findByRole('progressbar');
57+
await expect(Number(progressBar.ariaValueNow)).toBeGreaterThan(0);
58+
59+
// Play Video
60+
await userEvent.click(videoEl);
61+
await expect(canvas.queryByTestId('play-icon')).not.toBeInTheDocument();
62+
},
63+
};
64+
65+
export const UnmuteMute: Story = {
66+
...Default,
67+
parameters: {
68+
test: {
69+
// The following error is received without this flag: "TypeError: ophan.trackClickComponentEvent is not a function"
70+
dangerouslyIgnoreUnhandledErrors: true,
71+
},
72+
},
73+
play: async ({ canvasElement }) => {
74+
const canvas = within(canvasElement);
75+
76+
await canvas.findByTestId('unmute-icon');
77+
78+
await userEvent.click(canvas.getByTestId('unmute-icon'));
79+
await canvas.findByTestId('mute-icon');
80+
81+
await userEvent.click(canvas.getByTestId('mute-icon'));
82+
await canvas.findByTestId('unmute-icon');
83+
},
84+
};
85+
86+
// Function to emulate pausing between interactions
87+
function sleep(ms: number) {
88+
return new Promise((resolve) => setTimeout(resolve, ms));
89+
}
90+
91+
export const InteractionObserver: Story = {
92+
...Default,
93+
render: (args) => (
94+
<div data-testid="test-container">
95+
<LoopVideo {...args} />
96+
<div style={{ height: '100vh' }}></div>
97+
<p data-testid="page-end">End of page</p>
98+
</div>
99+
),
100+
play: async ({ canvasElement }) => {
101+
const canvas = within(canvasElement);
102+
103+
await sleep(500); // Allow enough time for the autoplay video to start.
104+
canvas.getByTestId('page-end').scrollIntoView();
105+
106+
const progressBar = await canvas.findByRole('progressbar');
107+
const progress = Number(progressBar.ariaValueNow);
108+
109+
await sleep(500); // Allow enough time to be confident that the video is paused.
110+
await expect(Number(progressBar.ariaValueNow)).toEqual(progress);
111+
await expect(canvas.queryByTestId('play-icon')).not.toBeInTheDocument();
112+
},
113+
};

dotcom-rendering/src/components/LoopVideoPlayer.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ export const LoopVideoPlayer = forwardRef(
137137
id={loopVideoId}
138138
css={videoStyles(width, height)}
139139
ref={ref}
140-
role="button"
141140
tabIndex={0}
141+
data-testid="loop-video"
142142
height={height}
143143
width={width}
144144
data-link-name={`gu-video-loop-${
@@ -180,6 +180,7 @@ export const LoopVideoPlayer = forwardRef(
180180
onClick={handlePlayPauseClick}
181181
css={playIconStyles}
182182
data-link-name={`gu-video-loop-play-${atomId}`}
183+
data-testid="play-icon"
183184
>
184185
<PlayIcon iconWidth="narrow" />
185186
</button>
@@ -199,7 +200,12 @@ export const LoopVideoPlayer = forwardRef(
199200
isMuted ? 'unmute' : 'mute'
200201
}-${atomId}`}
201202
>
202-
<div css={audioIconContainerStyles}>
203+
<div
204+
css={audioIconContainerStyles}
205+
data-testId={`${
206+
isMuted ? 'unmute' : 'mute'
207+
}-icon`}
208+
>
203209
<AudioIcon
204210
size="xsmall"
205211
theme={{

0 commit comments

Comments
 (0)