diff --git a/.eslintrc.js b/.eslintrc.js index 90352719ca8..e82086ff96f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,6 +42,7 @@ module.exports = { ], 'import/extensions': [1, { json: 'ignorePackages' }], 'jsx-a11y/no-redundant-roles': 'off', + 'default-param-last': 'off', }, settings: { 'import/resolver': { diff --git a/.yarn/cache/eslint-config-airbnb-base-npm-14.2.1-50131c00fb-858bea748a.zip b/.yarn/cache/eslint-config-airbnb-base-npm-14.2.1-50131c00fb-858bea748a.zip deleted file mode 100644 index 5f84b75fab2..00000000000 Binary files a/.yarn/cache/eslint-config-airbnb-base-npm-14.2.1-50131c00fb-858bea748a.zip and /dev/null differ diff --git a/.yarn/cache/eslint-config-airbnb-base-npm-15.0.0-802837dd26-38626bad2c.zip b/.yarn/cache/eslint-config-airbnb-base-npm-15.0.0-802837dd26-38626bad2c.zip new file mode 100644 index 00000000000..d8e7f9ea15f Binary files /dev/null and b/.yarn/cache/eslint-config-airbnb-base-npm-15.0.0-802837dd26-38626bad2c.zip differ diff --git a/.yarn/cache/eslint-config-airbnb-npm-18.2.1-19125926b3-ea11cd0006.zip b/.yarn/cache/eslint-config-airbnb-npm-18.2.1-19125926b3-ea11cd0006.zip deleted file mode 100644 index b570b9e811c..00000000000 Binary files a/.yarn/cache/eslint-config-airbnb-npm-18.2.1-19125926b3-ea11cd0006.zip and /dev/null differ diff --git a/.yarn/cache/eslint-config-airbnb-npm-19.0.4-a73150c84a-253178689c.zip b/.yarn/cache/eslint-config-airbnb-npm-19.0.4-a73150c84a-253178689c.zip new file mode 100644 index 00000000000..19b19d063e9 Binary files /dev/null and b/.yarn/cache/eslint-config-airbnb-npm-19.0.4-a73150c84a-253178689c.zip differ diff --git a/README.md b/README.md index a5b1574852d..d7de8d91d0d 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Install Node. [https://nodejs.org/en/](https://nodejs.org/en/). We use the versi nvm use ``` -#### Install Yarn +### Install Yarn The Simorgh project uses Yarn for package management. It is recommended to install Yarn through the npm package manager, which comes bundled with Node.js when you install it on your system. To install Yarn, run this command: diff --git a/package.json b/package.json index fefec031bdb..274291a4c64 100644 --- a/package.json +++ b/package.json @@ -186,7 +186,7 @@ "cypress-terminal-report": "3.5.2", "depcheck": "1.4.3", "eslint": "7.32.0", - "eslint-config-airbnb": "18.2.1", + "eslint-config-airbnb": "19.0.4", "eslint-config-prettier": "8.5.0", "eslint-import-resolver-alias": "1.1.2", "eslint-plugin-cypress": "2.12.1", diff --git a/src/app/components/EmbedConsentBanner/ConsentBanner.tsx b/src/app/components/EmbedConsentBanner/ConsentBanner.tsx index 183fdb01817..427004a94ca 100644 --- a/src/app/components/EmbedConsentBanner/ConsentBanner.tsx +++ b/src/app/components/EmbedConsentBanner/ConsentBanner.tsx @@ -157,11 +157,11 @@ type ConsentBannerContentProps = { id?: string; }; -const ConsentBanner = ({ +function ConsentBanner({ provider, clickHandler, id, -}: ConsentBannerContentProps) => { +}: ConsentBannerContentProps) { const { externalLinkText, translations } = useContext(ServiceContext); const consentTranslations = getTranslations( @@ -202,6 +202,6 @@ const ConsentBanner = ({ ); -}; +} export default ConsentBanner; diff --git a/src/app/components/EmbedConsentBanner/index.stories.tsx b/src/app/components/EmbedConsentBanner/index.stories.tsx index 5f1336e38d8..50c1c200e56 100644 --- a/src/app/components/EmbedConsentBanner/index.stories.tsx +++ b/src/app/components/EmbedConsentBanner/index.stories.tsx @@ -12,9 +12,11 @@ import { } from '.'; import ThemeProvider from '../ThemeProvider'; -const BackgroundColorWrapper = ({ children }: PropsWithChildren) => ( -
{children}
-); +function BackgroundColorWrapper({ children }: PropsWithChildren) { + return ( +
{children}
+ ); +} interface Props { service: Services; @@ -23,12 +25,12 @@ interface Props { provider: ConsentBannerProviders; } -const Component = ({ +function Component({ service = 'news', variant, isAmp, provider = 'youtube', -}: Props) => { +}: Props) { const EmbedBanner = !isAmp ? EmbedConsentBannerCanonical : EmbedConsentBannerAmp; @@ -44,7 +46,7 @@ const Component = ({ ); -}; +} export default { title: 'Containers/Social Embed/Consent Banner', @@ -52,21 +54,21 @@ export default { parameters: { chromatic: { disable: true } }, }; -export const CanonicalYoutube = (props: Props) => ( - -); -export const CanonicalYoutubeMundo = (props: Props) => ( - -); -export const AmpYoutube = (props: Props) => ( - -); -export const CanonicalTikTok = (props: Props) => ( - -); -export const CanonicalTikTokMundo = (props: Props) => ( - -); -export const AmpTikTok = (props: Props) => ( - -); +export function CanonicalYoutube(props: Props) { + return ; +} +export function CanonicalYoutubeMundo(props: Props) { + return ; +} +export function AmpYoutube(props: Props) { + return ; +} +export function CanonicalTikTok(props: Props) { + return ; +} +export function CanonicalTikTokMundo(props: Props) { + return ; +} +export function AmpTikTok(props: Props) { + return ; +} diff --git a/src/app/components/EmbedConsentBanner/index.tsx b/src/app/components/EmbedConsentBanner/index.tsx index 636d82a093b..e38ebfaee0b 100644 --- a/src/app/components/EmbedConsentBanner/index.tsx +++ b/src/app/components/EmbedConsentBanner/index.tsx @@ -23,11 +23,11 @@ type ConsentBannerProps = { id?: string; }; -const EmbedConsentBannerAmp = ({ +function EmbedConsentBannerAmp({ provider, id, children, -}: PropsWithChildren) => { +}: PropsWithChildren) { if (!CONSENT_BANNER_PROVIDERS.includes(provider)) return children as JSX.Element; @@ -47,12 +47,12 @@ const EmbedConsentBannerAmp = ({ ); -}; +} -const EmbedConsentBannerCanonical = ({ +function EmbedConsentBannerCanonical({ provider, children, -}: PropsWithChildren>) => { +}: PropsWithChildren>) { const [consented, setConsented] = useState(false); const handleClickTracking = useClickTrackerHandler( @@ -75,6 +75,6 @@ const EmbedConsentBannerCanonical = ({ }} /> ); -}; +} export { EmbedConsentBannerCanonical, EmbedConsentBannerAmp }; diff --git a/src/app/components/Heading/index.stories.tsx b/src/app/components/Heading/index.stories.tsx index df1c37ebda3..a8059a01e92 100644 --- a/src/app/components/Heading/index.stories.tsx +++ b/src/app/components/Heading/index.stories.tsx @@ -15,7 +15,7 @@ interface Props { const EMPTY_OPTION = '--'; -const HeadingStory = ({ service, variant, text }: Props) => { +function HeadingStory({ service, variant, text }: Props) { const selectedLevel = select( 'level', { @@ -82,7 +82,7 @@ const HeadingStory = ({ service, variant, text }: Props) => { ); -}; +} export default { title: 'New Components/Heading', diff --git a/src/app/components/Heading/index.tsx b/src/app/components/Heading/index.tsx index 1d3c9a950e0..f2b00ab682a 100644 --- a/src/app/components/Heading/index.tsx +++ b/src/app/components/Heading/index.tsx @@ -1,6 +1,6 @@ /** @jsx jsx */ -import React, { FC, HTMLAttributes } from 'react'; +import React, { HTMLAttributes } from 'react'; import { jsx } from '@emotion/react'; import { GelFontSize, FontVariant } from '../../models/types/theming'; @@ -30,14 +30,14 @@ const sizes: Sizes = { h4: 'greatPrimer', }; -const Heading: FC = ({ +function Heading({ children, className, fontVariant = 'sansBold', level, size, ...htmlAttributes -}: Props) => { +}: Props) { const element: Element = `h${level}`; return ( @@ -62,6 +62,6 @@ const Heading: FC = ({ {children} ); -}; +} export default Heading; diff --git a/src/app/components/Image/index.stories.tsx b/src/app/components/Image/index.stories.tsx index 5765ec25c22..8f92e58bb94 100644 --- a/src/app/components/Image/index.stories.tsx +++ b/src/app/components/Image/index.stories.tsx @@ -4,72 +4,84 @@ import AmpDecorator from '../../../../.storybook/helpers/ampDecorator'; import ThemeProvider from '../ThemeProvider'; import Image from '.'; -export const BasicImage = () => ( - - A penguin stands on an ice floe - -); +export function BasicImage() { + return ( + + A penguin stands on an ice floe + + ); +} -export const ResponsiveImage = () => ( - - A penguin stands on an ice floe - -); +export function ResponsiveImage() { + return ( + + A penguin stands on an ice floe + + ); +} -export const ResponsiveWebPWithJpegFallback = () => ( - - A penguin stands on an ice floe - -); +export function ResponsiveWebPWithJpegFallback() { + return ( + + A penguin stands on an ice floe + + ); +} -export const BasicAMPImage = () => ( - - A penguin stands on an ice floe - -); +export function BasicAMPImage() { + return ( + + A penguin stands on an ice floe + + ); +} -export const ResponsiveAMPImage = () => ( - - A penguin stands on an ice floe - -); +export function ResponsiveAMPImage() { + return ( + + A penguin stands on an ice floe + + ); +} -export const AMPWebPWithJpegFallback = () => ( - - A penguin stands on an ice floe - -); +export function AMPWebPWithJpegFallback() { + return ( + + A penguin stands on an ice floe + + ); +} BasicAMPImage.decorators = [AmpDecorator]; ResponsiveAMPImage.decorators = [AmpDecorator]; diff --git a/src/app/components/Image/index.test.tsx b/src/app/components/Image/index.test.tsx index 63391b9f4b1..1ce9f2ae0d0 100644 --- a/src/app/components/Image/index.test.tsx +++ b/src/app/components/Image/index.test.tsx @@ -15,20 +15,22 @@ const removeStyles = (el: HTMLElement) => { return dom.window.document.body.firstChild; }; -const Fixture = ({ ...props }) => ( - Test image alt text -); +function Fixture({ ...props }) { + return ( + Test image alt text + ); +} describe('Image - Canonical', () => { it('should preload when preload is true', async () => { diff --git a/src/app/components/Image/index.tsx b/src/app/components/Image/index.tsx index 2f456a96bb7..41c4304f734 100644 --- a/src/app/components/Image/index.tsx +++ b/src/app/components/Image/index.tsx @@ -31,7 +31,7 @@ const getLegacyBrowserAspectRatio = (x: number, y: number) => .toString() .concat('%'); -const Image = ({ +function Image({ alt, aspectRatio, attribution, @@ -48,7 +48,7 @@ const Image = ({ sizes, src, width, -}: PropsWithChildren) => { +}: PropsWithChildren) { const [isLoaded, setIsLoaded] = useState(false); const showPlaceholder = placeholder && !isLoaded; const hasDimensions = width && height; @@ -153,6 +153,6 @@ const Image = ({ ); -}; +} export default Image; diff --git a/src/app/components/InlineLink/index.stories.tsx b/src/app/components/InlineLink/index.stories.tsx index 08f972f65a2..f695e57de12 100644 --- a/src/app/components/InlineLink/index.stories.tsx +++ b/src/app/components/InlineLink/index.stories.tsx @@ -15,49 +15,57 @@ interface Props { text: string; } -const Providers = ({ children, service, variant }: Omit) => ( - - - {children} - - -); +function Providers({ children, service, variant }: Omit) { + return ( + + + {children} + + + ); +} -export const InternalInlineLink = ({ +export function InternalInlineLink({ service, variant, text, -}: Omit) => ( - - - -); +}: Omit) { + return ( + + + + ); +} -export const ExternalInlineLink = ({ +export function ExternalInlineLink({ service, variant, text, -}: Omit) => ( - - - -); +}: Omit) { + return ( + + + + ); +} -export const InlineLinkWithTypographyStyles = ({ +export function InlineLinkWithTypographyStyles({ service, variant, text, -}: Omit) => ( - - - -); +}: Omit) { + return ( + + + + ); +} -export const InlineLinkInsideText = ({ +export function InlineLinkInsideText({ service, variant, text, -}: Omit) => { +}: Omit) { const words = text.split(' '); const middleIndex = Math.ceil(words.length / 2) - 1; const middleWord = words[middleIndex]; @@ -73,7 +81,7 @@ export const InlineLinkInsideText = ({ ); -}; +} export default { title: 'New Components/InlineLink', diff --git a/src/app/components/InlineLink/index.tsx b/src/app/components/InlineLink/index.tsx index 783b5eaa4ab..cce319780ea 100644 --- a/src/app/components/InlineLink/index.tsx +++ b/src/app/components/InlineLink/index.tsx @@ -1,6 +1,6 @@ /** @jsx jsx */ -import { useContext, FC, HTMLAttributes } from 'react'; +import { useContext, HTMLAttributes } from 'react'; import { jsx, Theme } from '@emotion/react'; import Url from 'url-parse'; import { Link as ClientSideLink } from 'react-router-dom'; @@ -38,7 +38,7 @@ const parseLocation = (location: string) => end: false, }).exec(location); -const InlineLink: FC = ({ +function InlineLink({ allowCSR = false, className, fontVariant, @@ -46,7 +46,7 @@ const InlineLink: FC = ({ text, to, ...htmlAttributes -}: Props) => { +}: Props) { const { externalLinkText } = useContext(ServiceContext); const { hostname } = new Url(to); const isExternalLink = !bbcDomains.some(bbcDomain => hostname === bbcDomain); @@ -92,6 +92,6 @@ const InlineLink: FC = ({ {text} ); -}; +} export default InlineLink; diff --git a/src/app/components/Paragraph/index.stories.tsx b/src/app/components/Paragraph/index.stories.tsx index a4e906c119e..7a067e8cd66 100644 --- a/src/app/components/Paragraph/index.stories.tsx +++ b/src/app/components/Paragraph/index.stories.tsx @@ -15,7 +15,7 @@ interface Props { const EMPTY_OPTION = '--'; -const ParagraphStory = ({ service, variant, text }: Props) => { +function ParagraphStory({ service, variant, text }: Props) { const selectedFontVariant = select( 'fontVariant', { @@ -71,7 +71,7 @@ const ParagraphStory = ({ service, variant, text }: Props) => { ); -}; +} export default { title: 'New Components/Paragraph', diff --git a/src/app/components/Paragraph/index.tsx b/src/app/components/Paragraph/index.tsx index 879bbaa6958..420e58da5bb 100644 --- a/src/app/components/Paragraph/index.tsx +++ b/src/app/components/Paragraph/index.tsx @@ -1,6 +1,6 @@ /** @jsx jsx */ -import React, { FC, HTMLAttributes } from 'react'; +import React, { HTMLAttributes } from 'react'; import { jsx } from '@emotion/react'; import { FontVariant, GelFontSize } from '../../models/types/theming'; @@ -13,33 +13,35 @@ interface Props extends HTMLAttributes { size?: GelFontSize; } -const Paragraph: FC = ({ +function Paragraph({ children, className, fontVariant, size, ...htmlAttributes -}: Props) => ( - - {children} - -); +}: Props) { + return ( + + {children} + + ); +} export default Paragraph; diff --git a/src/app/components/Text/index.stories.tsx b/src/app/components/Text/index.stories.tsx index 9dbbfd834d7..51bc55670c4 100644 --- a/src/app/components/Text/index.stories.tsx +++ b/src/app/components/Text/index.stories.tsx @@ -15,7 +15,7 @@ interface Props { const EMPTY_OPTION = '--'; -const TextStory = ({ service, variant, text }: Props) => { +function TextStory({ service, variant, text }: Props) { const selectedFontVariant = select( 'fontVariant', { @@ -72,7 +72,7 @@ const TextStory = ({ service, variant, text }: Props) => { ); -}; +} export default { title: 'New Components/Text', diff --git a/src/app/components/ThemeProvider/__mocks__/index.tsx b/src/app/components/ThemeProvider/__mocks__/index.tsx index e51131713a5..c5a4736d2f4 100644 --- a/src/app/components/ThemeProvider/__mocks__/index.tsx +++ b/src/app/components/ThemeProvider/__mocks__/index.tsx @@ -120,7 +120,7 @@ interface Props { variant: Variants; } -const ThemeProvider = ({ children, service, ...rest }: Props) => { +function ThemeProvider({ children, service, ...rest }: Props) { const variant = rest.variant || defaultServiceVariants[service]; const ThemeProviderSynchronous = variant === 'default' || !variant @@ -128,6 +128,6 @@ const ThemeProvider = ({ children, service, ...rest }: Props) => { : themeProviders[service][variant]; return {children}; -}; +} export default ThemeProvider; diff --git a/src/app/components/ThemeProvider/withThemeProvider.tsx b/src/app/components/ThemeProvider/withThemeProvider.tsx index 072a4f173b3..88d2dd66b9a 100644 --- a/src/app/components/ThemeProvider/withThemeProvider.tsx +++ b/src/app/components/ThemeProvider/withThemeProvider.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { Global, ThemeProvider as EmotionThemeProvider, @@ -119,10 +119,6 @@ import { import { BrandPalette, Typography } from '../../models/types/theming'; -type Props = { - children: React.ReactNode; -}; - const withThemeProvider = ({ typography, palette: brandPalette, @@ -257,12 +253,14 @@ const withThemeProvider = ({ }, }; - const ThemeProvider: React.FC = ({ children }) => ( - - - {children} - - ); + function ThemeProvider({ children }: PropsWithChildren) { + return ( + + + {children} + + ); + } return ThemeProvider; }; diff --git a/src/app/components/icons/index.tsx b/src/app/components/icons/index.tsx index 5d3cb2840d3..e89f733b29c 100644 --- a/src/app/components/icons/index.tsx +++ b/src/app/components/icons/index.tsx @@ -1,38 +1,44 @@ import React from 'react'; -export const Ellipsis = () => ( - -); -export const LeftChevron = ({ className }: { className?: string }) => ( - -); +export function Ellipsis() { + return ( + + ); +} +export function LeftChevron({ className }: { className?: string }) { + return ( + + ); +} -export const RightChevron = ({ className }: { className?: string }) => ( - -); +export function RightChevron({ className }: { className?: string }) { + return ( + + ); +} diff --git a/src/app/components/react-testing-library-with-providers.tsx b/src/app/components/react-testing-library-with-providers.tsx index 17cff90b26c..ef76ac6c747 100644 --- a/src/app/components/react-testing-library-with-providers.tsx +++ b/src/app/components/react-testing-library-with-providers.tsx @@ -1,4 +1,4 @@ -import React, { FC, ReactElement } from 'react'; +import React, { ReactElement } from 'react'; import { render, RenderOptions } from '@testing-library/react'; import { ServiceContextProvider } from '../contexts/ServiceContext'; @@ -26,7 +26,7 @@ interface Props { // Uses a custom render so consumers don't need to wrap test fixtures in context and theme providers in every test suite // https://testing-library.com/docs/react-testing-library/setup/#custom-render -const AllTheProviders: FC = ({ +function AllTheProviders({ children, isAmp = false, pageData = pageDataFixture, @@ -36,7 +36,7 @@ const AllTheProviders: FC = ({ toggles = {}, variant = 'default', showAdsBasedOnLocation = false, -}: Props) => { +}: Props) { return ( @@ -57,7 +57,7 @@ const AllTheProviders: FC = ({ ); -}; +} const customRender = ( ui: ReactElement, diff --git a/src/app/contexts/ServiceContext/__mocks__/index.tsx b/src/app/contexts/ServiceContext/__mocks__/index.tsx index 53198540ae8..e55e16efa8d 100644 --- a/src/app/contexts/ServiceContext/__mocks__/index.tsx +++ b/src/app/contexts/ServiceContext/__mocks__/index.tsx @@ -17,12 +17,12 @@ interface Props { * using service contexts. */ export const ServiceContext = React.createContext({}); -export const ServiceContextProvider = ({ +export function ServiceContextProvider({ children, pageLang, service, variant, -}: PropsWithChildren) => { +}: PropsWithChildren) { const dataKey: Variants = getLangOverride({ service, pageLang }) || getVariant({ service, variant }); return ( @@ -30,7 +30,7 @@ export const ServiceContextProvider = ({ {children} ); -}; +} ServiceContextProvider.propTypes = { children: node.isRequired, service: string.isRequired, diff --git a/src/app/contexts/ServiceContext/index.test.tsx b/src/app/contexts/ServiceContext/index.test.tsx index c833c416489..99bf8706639 100644 --- a/src/app/contexts/ServiceContext/index.test.tsx +++ b/src/app/contexts/ServiceContext/index.test.tsx @@ -19,11 +19,11 @@ describe('ServiceContextProvider', () => { describe('should load hydrated service context', () => { const testForServiceAndVariant = (service: Services, variant: Variants) => { it(`should have a brand name for ${service} and variant ${variant}`, async () => { - const Component = () => { + function Component() { const { brandName } = useContext(ServiceContext); return {brandName}; - }; + } const serviceContextProps = { service, @@ -56,11 +56,11 @@ describe('ServiceContextProvider', () => { }); it(`should return null for foobar service`, async () => { - const Component = () => { + function Component() { const { brandName } = useContext(ServiceContext); return {brandName}; - }; + } const { container } = render( // @ts-expect-error test passing invalid service @@ -119,7 +119,7 @@ describe('ServiceContextProvider', () => { assertionValue, }) => { it(description, async () => { - const Component = () => { + function Component() { const { translations } = useContext(ServiceContext); return ( @@ -127,7 +127,7 @@ describe('ServiceContextProvider', () => { {translations[assertionValue as keyof Translations]?.toString()} ); - }; + } let container!: HTMLElement; diff --git a/src/app/contexts/ServiceContext/index.tsx b/src/app/contexts/ServiceContext/index.tsx index 8b69b155734..39427c4d9eb 100644 --- a/src/app/contexts/ServiceContext/index.tsx +++ b/src/app/contexts/ServiceContext/index.tsx @@ -13,12 +13,12 @@ interface Props { pageLang?: string; } -export const ServiceContextProvider = ({ +export function ServiceContextProvider({ children, service, variant, pageLang, -}: PropsWithChildren) => { +}: PropsWithChildren) { const LoadableContextProvider = services[service]; if (!LoadableContextProvider) { @@ -36,4 +36,4 @@ export const ServiceContextProvider = ({ {children} ); -}; +} diff --git a/src/app/legacy/containers/ATIAnalytics/beacon/index.test.js b/src/app/legacy/containers/ATIAnalytics/beacon/index.test.js index 5506ee953bd..c85c4362a62 100644 --- a/src/app/legacy/containers/ATIAnalytics/beacon/index.test.js +++ b/src/app/legacy/containers/ATIAnalytics/beacon/index.test.js @@ -3,8 +3,15 @@ import * as analyticsUtils from '#lib/analyticsUtils'; import { sendEventBeacon } from '.'; const sendBeaconSpy = jest.spyOn(sendBeacon, 'default'); -analyticsUtils.getAtUserId = jest.fn().mockReturnValue('123-456-789'); -analyticsUtils.getCurrentTime = jest.fn().mockReturnValue('00-00-00'); + +const analyticsUtilsUserIdSpy = jest.spyOn(analyticsUtils, 'getAtUserId'); +analyticsUtilsUserIdSpy.mockReturnValue('123-456-789'); + +const analyticsUtilsCurrentTimeSpy = jest.spyOn( + analyticsUtils, + 'getCurrentTime', +); +analyticsUtilsCurrentTimeSpy.mockReturnValue('00-00-00'); describe('beacon', () => { const atiBaseUrl = 'https://foobar.com?'; diff --git a/src/app/legacy/containers/ATIAnalytics/canonical/index.test.jsx b/src/app/legacy/containers/ATIAnalytics/canonical/index.test.jsx index 8fd2feea88a..c795fca6894 100644 --- a/src/app/legacy/containers/ATIAnalytics/canonical/index.test.jsx +++ b/src/app/legacy/containers/ATIAnalytics/canonical/index.test.jsx @@ -3,6 +3,8 @@ import { render, act } from '@testing-library/react'; import * as beacon from '#lib/analyticsUtils/sendBeacon'; import CanonicalATIAnalytics from '.'; +const beaconSpy = jest.spyOn(beacon, 'default'); + describe('Canonical ATI Analytics', () => { afterEach(() => { jest.clearAllMocks(); @@ -10,13 +12,12 @@ describe('Canonical ATI Analytics', () => { const atiBaseUrl = 'https://foobar.com?'; const mockPageviewParams = 'key=value&key2=value2&x8=[simorgh]'; - const mockSendBeacon = jest.fn().mockReturnValue('beacon-return-value'); + const mockSendBeacon = beaconSpy.mockReturnValue('beacon-return-value'); it('calls atiBaseURL and sendBeacon with required params', () => { const expectedUrl = `${atiBaseUrl}${mockPageviewParams}`; process.env.SIMORGH_ATI_BASE_URL = atiBaseUrl; - beacon.default = mockSendBeacon; act(() => { render(); diff --git a/src/app/legacy/containers/ATIAnalytics/index.test.jsx b/src/app/legacy/containers/ATIAnalytics/index.test.jsx index 1f126c95ba0..e6e77364812 100644 --- a/src/app/legacy/containers/ATIAnalytics/index.test.jsx +++ b/src/app/legacy/containers/ATIAnalytics/index.test.jsx @@ -30,27 +30,44 @@ import ATIAnalytics from '.'; import * as amp from './amp'; import * as canonical from './canonical'; -analyticsUtils.getAtUserId = jest.fn(); -analyticsUtils.getCurrentTime = jest.fn().mockReturnValue('00-00-00'); -analyticsUtils.getPublishedDatetime = jest - .fn() - .mockReturnValue('1970-01-01T00:00:00.000Z'); - -const ContextWrap = ({ pageType, platform, children, service }) => ( - - - {children} - - +const ampSpy = jest.spyOn(amp, 'default'); +const mockAmp = ampSpy.mockReturnValue('amp-return-value'); + +const canonicalSpy = jest.spyOn(canonical, 'default'); +const mockCanonical = canonicalSpy.mockReturnValue('canonical-return-value'); + +const analyticsUtilsUserIdSpy = jest.spyOn(analyticsUtils, 'getAtUserId'); +analyticsUtilsUserIdSpy.mockReturnValue(); + +const analyticsUtilsCurrentTimeSpy = jest.spyOn( + analyticsUtils, + 'getCurrentTime', +); +analyticsUtilsCurrentTimeSpy.mockReturnValue('00-00-00'); + +const analyticsUtilsPublishDateSpy = jest.spyOn( + analyticsUtils, + 'getPublishedDatetime', ); +analyticsUtilsPublishDateSpy.mockReturnValue('1970-01-01T00:00:00.000Z'); + +function ContextWrap({ pageType, platform, children, service }) { + return ( + + + {children} + + + ); +} ContextWrap.propTypes = { children: node.isRequired, @@ -66,9 +83,6 @@ describe('ATI Analytics Container', () => { describe('pageType article', () => { it('should call CanonicalATIAnalytics when platform is canonical', () => { - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; - render( { }); it('should call AmpATIAnalytics when platform is Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( @@ -109,8 +120,6 @@ describe('ATI Analytics Container', () => { setWindowValue('location', { href: `https://localhost`, }); - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; render( @@ -126,9 +135,6 @@ describe('ATI Analytics Container', () => { }); it('should call AmpATIAnalytics when platform is Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( @@ -145,9 +151,6 @@ describe('ATI Analytics Container', () => { describe('pageType=MAP', () => { it('should call CanonicalATIAnalytics when platform is canonical', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( { describe('pageType=PGL', () => { it('should call CanonicalATIAnalytics when platform is canonical', () => { - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; - render( { }); it('should call AmpATIAnalytics when platform is Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( { describe('pageType=STY', () => { it('should call CanonicalATIAnalytics when platform is canonical', () => { - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; - render( @@ -229,9 +223,6 @@ describe('ATI Analytics Container', () => { }); it('should call AmpATIAnalytics when platform is Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( @@ -246,9 +237,6 @@ describe('ATI Analytics Container', () => { }); it('should call AmpATIAnalytics when platform is Amp and pageType is CSP', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( { }); it('should return the correct language param when service is Ukrainian and pageData language is Ukrainian on canonical', () => { - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; - render( { }); it('should return the correct language param when service is Ukrainian and pageData language is Ukrainian on Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( @@ -305,9 +287,6 @@ describe('ATI Analytics Container', () => { }); it('should return the correct language param when service is Ukrainian and pageData language is Russian on canonical', () => { - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; - render( { }); it('should return the correct language param when service is Ukrainian and pageData language is Russian on Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( @@ -363,8 +339,6 @@ describe('ATI Analytics Container', () => { setWindowValue('location', { href: 'https://localhost?at_medium=email&at_emailtype=acquisition&at_creation=my_creation', }); - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; render( @@ -382,8 +356,6 @@ describe('ATI Analytics Container', () => { setWindowValue('location', { href: 'http://localhost?foo=bar', }); - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; render( @@ -400,9 +372,6 @@ describe('ATI Analytics Container', () => { }); describe('pageType=FIX', () => { it('should call CanonicalATIAnalytics when platform is canonical', () => { - const mockCanonical = jest.fn().mockReturnValue('canonical-return-value'); - canonical.default = mockCanonical; - render( { }); it('should call AmpATIAnalytics when platform is Amp', () => { - const mockAmp = jest.fn().mockReturnValue('amp-return-value'); - amp.default = mockAmp; - render( jest.fn()); onClient.mockImplementation(() => isOnClient); @@ -304,9 +308,9 @@ describe('Chartbeat utilities', () => { const mockTitle = jest.fn().mockImplementation(() => pageTitle); if (pageType === ARTICLE_PAGE) { - articleUtils.getPromoHeadline = mockTitle; + articleUtilsSpy.mockImplementation(mockTitle); } else { - frontPageUtils.getPageTitle = mockTitle; + frontPageUtilsSpy.mockImplementation(mockTitle); } expect(getTitle({ pageType, pageData, brandName })).toBe(pageTitle); @@ -426,7 +430,7 @@ describe('Chartbeat utilities', () => { .fn() .mockImplementation(() => 'This is an index page title'); - frontPageUtils.getPageTitle = mockTitle; + frontPageUtilsSpy.mockImplementation(mockTitle); const expectedCookieValue = 'foobar'; jest.spyOn(Cookie, 'get').mockImplementation(() => expectedCookieValue); @@ -919,7 +923,7 @@ describe('Chartbeat utilities', () => { .fn() .mockImplementation(() => 'This is an index page title'); - frontPageUtils.getPageTitle = mockTitle; + frontPageUtilsSpy.mockImplementation(mockTitle); const expectedCookieValue = 'foobar'; jest.spyOn(Cookie, 'get').mockImplementation(() => expectedCookieValue); @@ -959,7 +963,7 @@ describe('Chartbeat utilities', () => { .fn() .mockImplementation(() => 'This is a Feature Index page title'); - frontPageUtils.getPageTitle = mockTitle; + frontPageUtilsSpy.mockImplementation(mockTitle); const expectedCookieValue = 'foobar'; jest.spyOn(Cookie, 'get').mockImplementation(() => expectedCookieValue); @@ -999,7 +1003,7 @@ describe('Chartbeat utilities', () => { .fn() .mockImplementation(() => 'This is an index page title'); - frontPageUtils.getPageTitle = mockTitle; + frontPageUtilsSpy.mockImplementation(mockTitle); const expectedCookieValue = 'foobar'; jest.spyOn(Cookie, 'get').mockImplementation(() => expectedCookieValue); diff --git a/src/app/legacy/containers/ConsentBanner/Banner/getDataAttribute.js b/src/app/legacy/containers/ConsentBanner/Banner/getDataAttribute.js index 2d93a766890..1c96651ebb6 100644 --- a/src/app/legacy/containers/ConsentBanner/Banner/getDataAttribute.js +++ b/src/app/legacy/containers/ConsentBanner/Banner/getDataAttribute.js @@ -1,4 +1,6 @@ -const getDataAttribute = type => value => - type === 'cookie' ? { 'data-cookie-banner': value } : null; +const getDataAttribute = type => + function (value) { + return type === 'cookie' ? { 'data-cookie-banner': value } : null; + }; export default getDataAttribute; diff --git a/src/app/legacy/containers/Text/index.jsx b/src/app/legacy/containers/Text/index.jsx index b42eb1e6afa..8f9fbe8f30b 100644 --- a/src/app/legacy/containers/Text/index.jsx +++ b/src/app/legacy/containers/Text/index.jsx @@ -4,11 +4,11 @@ import paragraph from '../Paragraph'; import unorderedList from '../BulletedList'; import Blocks from '../Blocks'; -const TextContainer = ({ blocks, componentsToRender }) => { +function TextContainer({ blocks, componentsToRender }) { if (!blocks) return null; return ; -}; +} export const TextPropTypes = { blocks: arrayOf( diff --git a/src/app/legacy/psammead/psammead-storybook-helpers/src/buildRTLSubstories.test.js b/src/app/legacy/psammead/psammead-storybook-helpers/src/buildRTLSubstories.test.js index b8ccbf8c6b0..04538f08f63 100644 --- a/src/app/legacy/psammead/psammead-storybook-helpers/src/buildRTLSubstories.test.js +++ b/src/app/legacy/psammead/psammead-storybook-helpers/src/buildRTLSubstories.test.js @@ -4,7 +4,8 @@ import * as withServicesKnob from './withServicesKnob'; const mockAddStory = jest.fn(); -withServicesKnob.default = jest.fn(() => 'withServicesKnob'); +const withServicesSpy = jest.spyOn(withServicesKnob, 'default'); +withServicesSpy.mockImplementation = jest.fn(() => 'withServicesKnob'); jest.mock('@storybook/react', () => ({ storiesOf: jest.fn(() => ({ diff --git a/src/app/models/propTypes/general/index.js b/src/app/models/propTypes/general/index.js index 658aa605d3c..83a5b4d12ab 100644 --- a/src/app/models/propTypes/general/index.js +++ b/src/app/models/propTypes/general/index.js @@ -13,8 +13,8 @@ export const blocksWithTypes = blockTypes => ({ blocks: arrayOf(oneOfType(blockTypes).isRequired).isRequired, }); -export const arrayOfSpecificBlocks = - propTypeData => (props, key, componentName) => { +export const arrayOfSpecificBlocks = propTypeData => + function (props, key, componentName) { const { [key]: propData } = props; if (!Array.isArray(propData)) { diff --git a/src/app/models/propTypes/general/index.test.js b/src/app/models/propTypes/general/index.test.js index e3cd8428175..049adabadbf 100644 --- a/src/app/models/propTypes/general/index.test.js +++ b/src/app/models/propTypes/general/index.test.js @@ -3,6 +3,8 @@ import { arrayOfSpecificBlocks } from './index'; import * as getMissingRequiredProps from './getMissingRequiredProps'; +const missingPropsSpy = jest.spyOn(getMissingRequiredProps, 'default'); + const propData = { testProp: [ { type: 'propOne', value: 'Some data' }, @@ -28,8 +30,7 @@ describe('arrayOfSpecificBlocks', () => { }); it('should return an error if missing required props', () => { - getMissingRequiredProps.default = jest - .fn() + missingPropsSpy .mockReturnValue([]) .mockReturnValueOnce(['missingProp', 'missingPropTwo']); diff --git a/src/app/pages/ArticlePage/Byline/index.stories.tsx b/src/app/pages/ArticlePage/Byline/index.stories.tsx index e14f90823ca..371f6fd638f 100644 --- a/src/app/pages/ArticlePage/Byline/index.stories.tsx +++ b/src/app/pages/ArticlePage/Byline/index.stories.tsx @@ -22,18 +22,20 @@ interface ComponentProps extends Props { fixture: any; } -const Component = ({ +function Component({ service, variant, fixture, children, -}: PropsWithChildren) => ( - - - {children} - - -); +}: PropsWithChildren) { + return ( + + + {children} + + + ); +} export default { title: 'Components/Byline', @@ -41,57 +43,69 @@ export default { decorators: [withKnobs, withServicesKnob()], }; -export const AuthorRoleByline = ({ service, variant }: Props) => ( - -); -export const LinkByline = ({ service, variant }: Props) => ( - -); -export const AuthorRoleTimestampByline = ({ service, variant }: Props) => ( - - - -); -export const LinkAndLocationByline = ({ service, variant }: Props) => ( - - - -); -export const LinkLocationNoPhotoByline = ({ service, variant }: Props) => ( - - - -); -export const LinkLocationPhotoByline = ({ service, variant }: Props) => ( - - - -); + ); +} +export function LinkByline({ service, variant }: Props) { + return ( + + ); +} +export function AuthorRoleTimestampByline({ service, variant }: Props) { + return ( + + + + ); +} +export function LinkAndLocationByline({ service, variant }: Props) { + return ( + + + + ); +} +export function LinkLocationNoPhotoByline({ service, variant }: Props) { + return ( + + + + ); +} +export function LinkLocationPhotoByline({ service, variant }: Props) { + return ( + + + + ); +} diff --git a/src/app/pages/ArticlePage/Byline/index.tsx b/src/app/pages/ArticlePage/Byline/index.tsx index ea08011d9cb..05589e7b096 100644 --- a/src/app/pages/ArticlePage/Byline/index.tsx +++ b/src/app/pages/ArticlePage/Byline/index.tsx @@ -15,7 +15,7 @@ type Props = { blocks: any; }; -const Byline = ({ blocks, children }: PropsWithChildren) => { +function Byline({ blocks, children }: PropsWithChildren) { const { translations, dir } = useContext(ServiceContext); const isRtl = dir === 'rtl'; const bylineBlocks = pathOr([], [0, 'model', 'blocks'], blocks); @@ -227,7 +227,7 @@ const Byline = ({ blocks, children }: PropsWithChildren) => { ); -}; +} Byline.defaultProps = { children: null, diff --git a/src/app/pages/TopicPage/Curation/Subhead/index.stories.tsx b/src/app/pages/TopicPage/Curation/Subhead/index.stories.tsx index 06353856f78..68282a3266a 100644 --- a/src/app/pages/TopicPage/Curation/Subhead/index.stories.tsx +++ b/src/app/pages/TopicPage/Curation/Subhead/index.stories.tsx @@ -13,7 +13,7 @@ interface Props { variant: Variants; } -const Component = ({ service, variant }: Props) => { +function Component({ service, variant }: Props) { return ( @@ -21,9 +21,9 @@ const Component = ({ service, variant }: Props) => { ); -}; +} -const WithLink = ({ service, variant }: Props) => { +function WithLink({ service, variant }: Props) { return ( @@ -31,7 +31,7 @@ const WithLink = ({ service, variant }: Props) => { ); -}; +} export default { title: 'Topic/Curations/Subheading', diff --git a/src/app/pages/TopicPage/Curation/Subhead/index.test.tsx b/src/app/pages/TopicPage/Curation/Subhead/index.test.tsx index a92f26892c8..5ff740eaa8f 100644 --- a/src/app/pages/TopicPage/Curation/Subhead/index.test.tsx +++ b/src/app/pages/TopicPage/Curation/Subhead/index.test.tsx @@ -11,15 +11,17 @@ interface Props { } /* eslint-disable react/prop-types */ -const SubheadWithContext = ({ +function SubheadWithContext({ children = '', link = '', service = 'mundo', -}: PropsWithChildren) => ( - - {children} - -); +}: PropsWithChildren) { + return ( + + {children} + + ); +} describe('Curation Subhead Component', () => { it('should render a link correctly with the url contained in the href', () => { diff --git a/src/app/pages/TopicPage/Curation/Subhead/index.tsx b/src/app/pages/TopicPage/Curation/Subhead/index.tsx index fdabe30f54e..8d8198646c1 100644 --- a/src/app/pages/TopicPage/Curation/Subhead/index.tsx +++ b/src/app/pages/TopicPage/Curation/Subhead/index.tsx @@ -11,7 +11,7 @@ interface Props { a11yID?: string; } -const Subhead = ({ children, link, a11yID }: PropsWithChildren) => { +function Subhead({ children, link, a11yID }: PropsWithChildren) { const { service, script, dir } = useContext(ServiceContext); const Wrapper = link @@ -27,7 +27,7 @@ const Subhead = ({ children, link, a11yID }: PropsWithChildren) => { {children} ); -}; +} Subhead.defaultProps = { link: '', a11yID: '' }; diff --git a/src/app/pages/TopicPage/HierarchicalGrid/index.stories.tsx b/src/app/pages/TopicPage/HierarchicalGrid/index.stories.tsx index 8303a1f5c87..1f1eb7cbdc0 100644 --- a/src/app/pages/TopicPage/HierarchicalGrid/index.stories.tsx +++ b/src/app/pages/TopicPage/HierarchicalGrid/index.stories.tsx @@ -13,7 +13,7 @@ interface Props { variant: Variants; } -const Component = ({ service, variant }: Props) => { +function Component({ service, variant }: Props) { return ( @@ -27,7 +27,7 @@ const Component = ({ service, variant }: Props) => { ); -}; +} export default { title: 'Topic/HierarchicalGrid', diff --git a/src/app/pages/TopicPage/HierarchicalGrid/index.tsx b/src/app/pages/TopicPage/HierarchicalGrid/index.tsx index b62826dc54e..1636c52ca7f 100644 --- a/src/app/pages/TopicPage/HierarchicalGrid/index.tsx +++ b/src/app/pages/TopicPage/HierarchicalGrid/index.tsx @@ -40,7 +40,7 @@ const getStyles = (promoCount: number, i: number, mq: any) => { }); }; -const HiearchicalGrid = ({ promos, headingLevel }: Promos) => { +function HiearchicalGrid({ promos, headingLevel }: Promos) { if (!promos || promos.length < 3) return null; const promoItems = promos.slice(0, 12); return ( @@ -85,6 +85,6 @@ const HiearchicalGrid = ({ promos, headingLevel }: Promos) => { })} ); -}; +} export default HiearchicalGrid; diff --git a/src/app/routes/cpsAsset/getInitialData/convertToOptimoBlocks/blocks/include/index.test.js b/src/app/routes/cpsAsset/getInitialData/convertToOptimoBlocks/blocks/include/index.test.js index 8934c5c58ac..1ec524a60ae 100644 --- a/src/app/routes/cpsAsset/getInitialData/convertToOptimoBlocks/blocks/include/index.test.js +++ b/src/app/routes/cpsAsset/getInitialData/convertToOptimoBlocks/blocks/include/index.test.js @@ -7,6 +7,9 @@ import pageData from './fixtures'; import * as fetchMarkup from './fetchMarkup'; import * as getImageBlock from './getImageBlock'; +const fetchMarkUpSpy = jest.spyOn(fetchMarkup, 'default'); +const getImageBlockSpy = jest.spyOn(getImageBlock, 'default'); + const includeMarkup = `
INCLUDE Markup
`; const canonicalPathname = 'https://www.bbc.com/service/foo'; @@ -64,7 +67,7 @@ describe('Convert Include block', () => { describe('Data dependent conditions', () => { beforeEach(() => { - fetchMarkup.default = jest.fn().mockReturnValue(includeMarkup); + fetchMarkUpSpy.mockReturnValue(includeMarkup); }); it.each` @@ -93,15 +96,15 @@ describe('Convert Include block', () => { jest.clearAllMocks(); }); it(`when fetchMarkup returns null`, async () => { - fetchMarkup.default = jest.fn().mockReturnValue(null); + fetchMarkUpSpy.mockReturnValue(null); expect( await convertInclude(idt2Block, pageData, null, canonicalPathname), ).toMatchSnapshot(); }); it(`when getImageBlock returns null`, async () => { - getImageBlock.default = jest.fn().mockReturnValue(null); - fetchMarkup.default = jest.fn().mockReturnValue(includeMarkup); + getImageBlockSpy.mockReturnValue(null); + fetchMarkUpSpy.mockReturnValue(includeMarkup); expect( await convertInclude(idt2Block, pageData, null, canonicalPathname), ).toMatchSnapshot(); diff --git a/src/server/Document/component.test.jsx b/src/server/Document/component.test.jsx index 4586950157c..10415461f19 100644 --- a/src/server/Document/component.test.jsx +++ b/src/server/Document/component.test.jsx @@ -26,46 +26,48 @@ describe('Document Component', () => { ); const links = ( <> - - - + + + ); // eslint-disable-next-line react/prop-types - const TestDocumentComponent = ({ service, isAmp }) => ( - - - Test title - - -