Skip to content
Draft
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
15 changes: 1 addition & 14 deletions src/app/components/FrostedGlassPromo/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { PropsWithChildren } from 'react';

import { ToggleContextProvider } from '../../contexts/ToggleContext';
import { RequestContextProvider } from '../../contexts/RequestContext';
import { ServiceContextProvider } from '../../contexts/ServiceContext';

import { STORY_PAGE } from '../../routes/utils/pageTypes';
import makeRelativeUrlPath from '../../lib/utilities/makeRelativeUrlPath';
import * as clickTracking from '../../hooks/useClickTrackerHandler';
import { render } from '../react-testing-library-with-providers';
import { Services, Variants } from '../../models/types/global';

Expand Down Expand Up @@ -185,18 +182,8 @@ describe('Frosted Glass Promo', () => {
expect(container).toBeEmptyDOMElement();
});

// Only expecting clicks to be emitted from here - view tracking is handled at
// Expects view tracking to be handled at
// the list level - eg containers/CpsFeatureAnalysis
it('should track clicks', () => {
const clickTrackerSpy = jest.spyOn(clickTracking, 'default');
render(<Component {...cpsPromoFixture} />);

expect(clickTrackerSpy).toHaveBeenCalledWith({
componentName: 'features',
url: cpsPromoFixture.item.locators.assetUri,
});
});

it('should render lazyload component for frosted glass section', () => {
const { getByTestId } = render(
<Component {...linkPromoFixture} service="pidgin" />,
Expand Down
19 changes: 8 additions & 11 deletions src/app/components/FrostedGlassPromo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ import type { ReactNode } from 'react';
import { PropsWithChildren, use } from 'react';
import pick from 'ramda/src/pick';
import Lazyload from 'react-lazyload';

import IMAGE from '../Image';
import makeRelativeUrlPath from '../../lib/utilities/makeRelativeUrlPath';
import useClickTrackerHandler from '../../hooks/useClickTrackerHandler';
import { RequestContext } from '../../contexts/RequestContext';

import FrostedGlassPanel from './FrostedGlassPanel';
import withData from './withData';

import styles from './styles';
import { EventTrackingBlock } from '../../models/types/eventTracking';
import { PromoProps } from './types';
import Link from '../Link';

const PANEL_OFFSET = 250;

Expand Down Expand Up @@ -54,15 +51,15 @@ const FrostedGlassPromo = ({
const isCanonical = !isAmp;
const relativeUrl = makeRelativeUrlPath(url);

const clickTracker = useClickTrackerHandler({
const eventTrackingInfo = {
...(eventTrackingData || {}),
url: relativeUrl,
});
};

const promoText = (
<>
<h3 css={styles.header}>
<a
<Link
css={theme => [
styles.anchor,
{
Expand All @@ -74,10 +71,10 @@ const FrostedGlassPromo = ({
},
]}
href={relativeUrl}
{...(eventTrackingData && clickTracker)}
eventTrackingData={eventTrackingInfo}
>
{children}
</a>
</Link>
</h3>
{footer}
</>
Expand All @@ -88,10 +85,10 @@ const FrostedGlassPromo = ({
/* eslint-disable react/self-closing-comp */
return (
<div css={styles.componentWrapper} data-testid={`frosted-promo-${index}`}>
<a
<Link
css={styles.clickableArea}
href={relativeUrl}
{...(eventTrackingData && clickTracker)}
eventTrackingData={eventTrackingInfo}
aria-hidden="true"
tabIndex={-1}
/>
Expand Down
15 changes: 15 additions & 0 deletions src/app/components/InPictureVideo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Description

**Next.js only component** - In picture video maintains video playback as the user browses through our Next.js single page applications.

## Props

| Name | type | Description |
| -------- | -------- | ---------------------------------------------------------------------------------------- |
| | |

### Example

```javascript
<InPictureVideo />
```
22 changes: 22 additions & 0 deletions src/app/components/InPictureVideo/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import InPictureVideo from '.'
import readme from './README.md'
import metadata from './metadata.json'

const Component = () => (
<InPictureVideo />
);

export const Example = () => (
<Component />
);

export default {
title: 'Components/InPictureVideo',
Component,
parameters: {
docs: {
readme,
metadata,
},
},
};
18 changes: 18 additions & 0 deletions src/app/components/InPictureVideo/index.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Theme, css } from '@emotion/react';

export default {
container: ({ spacings }: Theme) =>
css({
position: 'fixed',
display: 'block',
top: `${spacings.FULL}rem`,
insetInlineEnd: `${spacings.FULL}rem`,
zIndex: 10,
}),
video: () =>
css({
width: '240px',
height: '240px',
borderRadius: '10%',
}),
};
9 changes: 9 additions & 0 deletions src/app/components/InPictureVideo/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { render } from '../react-testing-library-with-providers';
import InPicture from '.';

describe('InPictureVideo', () => {
it('should track clicks', () => {
render(<InPicture />);
expect(true).toBe(true);
});
});
15 changes: 15 additions & 0 deletions src/app/components/InPictureVideo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import style from './index.style';

export default () => {
return (
<section css={style.container}>
<iframe
css={style.video}
src="https://www.youtube.com/embed/EColTNIbOko?si=DmXVv_JFXkxx8YFa"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
/>
</section>
);
};
29 changes: 29 additions & 0 deletions src/app/components/InPictureVideo/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"alpha": false,
"lastUpdated": {
"day": 26,
"month": "December",
"year": 2025
},
"uxAccessibilityDoc": {
"done": false,
"reference": {
"url": "",
"label": "Screen Reader UX"
}
},
"acceptanceCriteria": {
"done": false,
"reference": {
"url": "",
"label": "Accessibility Acceptance Criteria"
}
},
"swarm": {
"done": false,
"reference": {
"url": "",
"label": "Accessibility Swarm Notes"
}
}
}
52 changes: 52 additions & 0 deletions src/app/components/Link/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as clickTracking from '#app/hooks/useClickTrackerHandler';
import { render } from '../react-testing-library-with-providers';
import Link from '.';

describe('Link', () => {
it.each([
{
type: 'html',
spaLink: false,
},
{
type: 'Next',
spaLink: true,
},
])(
'should render a valid anchor element for a $type type link',
({ spaLink }) => {
const href = '/forwarding_link';
const title = 'Some Title';

const { container } = render(
<Link href={href} spaLink={spaLink}>
{title}
</Link>,
);
const anchor = container.querySelector('a');

expect(anchor?.href).toBe(`http://localhost${href}`);
expect(anchor?.innerHTML).toBe(title);
},
);

it('should track clicks', () => {
const href = '/forwarding_link';
const title = 'Some Title';
const isSpaLink = true;
const sampleEventData = {
url: href,
block: {
componentName: title,
},
};
const clickTrackerSpy = jest.spyOn(clickTracking, 'default');

render(
<Link href={href} eventTrackingData={sampleEventData} spaLink={isSpaLink}>
{title}
</Link>,
);
expect(clickTrackerSpy).toHaveBeenCalledWith(sampleEventData, isSpaLink);
});
});
46 changes: 46 additions & 0 deletions src/app/components/Link/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { PropsWithChildren, use } from 'react';
import Link from 'next/link';
import useClickTrackerHandler from '#app/hooks/useClickTrackerHandler';
import { RequestContext } from '#app/contexts/RequestContext';

type Props = {
href: string;
className?: string;
spaLink?: boolean;
tabIndex?: number;
eventTrackingData?: {
url: string;
block?: { componentName: string } | undefined;
};
};

export default ({
spaLink = true,
children,
href,
className,
tabIndex,
eventTrackingData,
...props
}: PropsWithChildren<Props>) => {
const { isLite, isAmp } = use(RequestContext);
const NextLink = Link;
const Anchor = 'a' as React.ElementType;
const isSpaLink = spaLink && !isLite && !isAmp;
const Component = isSpaLink ? NextLink : Anchor;

const clickTracker = useClickTrackerHandler(eventTrackingData, isSpaLink);

return (
<Component
{...(className && { className })}
{...(tabIndex && { tabIndex })}
// TODO: Remove this bit ?renderer_env=test, I only put it there for dev purposes
href={`${href}?renderer_env=test`}
{...(eventTrackingData && clickTracker)}
{...props}
>
{children}
</Component>
);
};
17 changes: 11 additions & 6 deletions src/app/hooks/useClickTrackerHandler/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { sendEventBeacon } from '../../components/ATIAnalytics/beacon/index';
import { ServiceContext } from '../../contexts/ServiceContext';
import { isValidClick } from './clickTypes';

const useClickTrackerHandler = (eventTrackingData = {}) => {
const useClickTrackerHandler = (eventTrackingData = {}, spaLink = false) => {
const {
pageIdentifier,
producerId,
Expand Down Expand Up @@ -67,8 +67,10 @@ const useClickTrackerHandler = (eventTrackingData = {}) => {
statsDestination,
].every(Boolean);
if (shouldSendEvent) {
event.stopPropagation();
event.preventDefault();
if (!spaLink) {
event.stopPropagation();
event.preventDefault();
}

if (
optimizely &&
Expand Down Expand Up @@ -114,7 +116,9 @@ const useClickTrackerHandler = (eventTrackingData = {}) => {
if (optimizely) {
optimizely.close();
}
window.location.assign(nextPageUrl);
if (!spaLink) {
window.location.assign(nextPageUrl);
}
}
}
}
Expand All @@ -131,6 +135,7 @@ const useClickTrackerHandler = (eventTrackingData = {}) => {
producerName,
service,
statsDestination,
spaLink,
optimizely,
experimentVariant,
sendOptimizelyEvents,
Expand All @@ -147,11 +152,11 @@ const useClickTrackerHandler = (eventTrackingData = {}) => {
);
};

export default (eventTrackingData = {}) => {
export default (eventTrackingData = {}, spaLink = false) => {
const { isAmp } = use(RequestContext);
const isHydrated = useHydrationDetection();

const clickTracker = useClickTrackerHandler(eventTrackingData);
const clickTracker = useClickTrackerHandler(eventTrackingData, spaLink);

const enableStaticTracking = !isHydrated && !isAmp;
const reverbStaticUrl = constructReverbUrl({
Expand Down
9 changes: 6 additions & 3 deletions webpack.config.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,14 +200,17 @@ module.exports = ({
// Display full duplicates information? (Default: `false`)
verbose: true,
exclude({ name, path }) {
return (
const babelCriteria =
name === '@babel/runtime' &&
[
'./~/@emotion/react/~/@babel/runtime',
'./~/@loadable/component/~/@babel/runtime',
'./~/react-router-dom/~/@babel/runtime',
].includes(path)
);
].includes(path);
// Adding this one as next-js and react-router uses their own isolated version of this package.
// Since we're moving to next-js anyhow, it wouldn't be worth the dev effort of trying to resolve and maintain this duplicate dependency.
const pathToRegexCriteria = name === 'path-to-regexp';
return pathToRegexCriteria || babelCriteria;
},
}),
/*
Expand Down
Loading