src/app/components/Header/ScriptLink/index.test.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
import React from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { RequestContext, RequestContextProps } from '#contexts/RequestContext';
import { ToggleContext } from '#contexts/ToggleContext';
import {
  articlePath,
  cpsAssetPagePath,
  errorPagePath,
  frontPagePath,
  legacyAssetPagePath,
  topicPath,
} from '#app/routes/utils/regex';
import { render } from '../../react-testing-library-with-providers';
import { service as ukChinaServiceConfig } from '../../../lib/config/services/ukchina';
import { service as serbianServiceConfig } from '../../../lib/config/services/serbian';
import { ServiceContext } from '../../../contexts/ServiceContext';
import ScriptLinkContainer, { getVariantHref } from '.';
import ThemeProvider from '../../ThemeProvider';

const requestContextMock = {
  variant: 'lat',
  env: 'test',
  isNextJs: false,
};

const withRouter = (
  component: React.ReactElement,
  matchPath: string,
  path: string,
) => {
  const Wrapper = (
    <MemoryRouter initialEntries={[path]}>
      <Route path={matchPath}>{component}</Route>
    </MemoryRouter>
  );

  return {
    ...render(Wrapper),
  };
};

const defaultToggleState = {
  scriptLink: {
    enabled: true,
  },
  variantCookie: {
    enabled: true,
  },
};

const mockToggleDispatch = jest.fn();

const toggleContextMock = {
  toggleState: defaultToggleState,
  toggleDispatch: mockToggleDispatch,
};

const ScriptLinkContainerWithContext = ({
  serviceContext = serbianServiceConfig.lat,
  requestContext = requestContextMock,
  toggleContext = toggleContextMock,
  ...props
}) => (
  <ThemeProvider service="serbian" variant="lat">
    <ToggleContext.Provider value={toggleContext}>
      <ServiceContext.Provider value={serviceContext}>
        <RequestContext.Provider value={requestContext as RequestContextProps}>
          <ScriptLinkContainer {...props} />
        </RequestContext.Provider>
      </ServiceContext.Provider>
    </ToggleContext.Provider>
  </ThemeProvider>
);

describe(`Script Link`, () => {
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should render correctly', () => {
    const { container } = render(
      <MemoryRouter initialEntries={['/serbian/lat']}>
        <Route path={frontPagePath}>
          <ScriptLinkContainerWithContext />
        </Route>
      </MemoryRouter>,
    );
    expect(container).toMatchSnapshot();
  });

  describe('assertions', () => {
    const testCases = {
      article: {
        matchPath: articlePath,
        path: '/serbian/articles/c805k05kr73o/lat',
        variantPath: '/serbian/articles/c805k05kr73o/cyr',
      },
      cpsAssetPage: {
        matchPath: cpsAssetPagePath,
        path: '/serbian/lat/srbija-46748932',
        variantPath: '/serbian/cyr/srbija-46748932',
      },
      errorPage: {
        matchPath: errorPagePath,
        path: '/serbian/404/lat',
        variantPath: '/serbian/404/cyr',
      },
      frontPage: {
        matchPath: frontPagePath,
        path: '/serbian/lat',
        variantPath: '/serbian/cyr',
      },
      legacyAssetPage: {
        matchPath: legacyAssetPagePath,
        path: '/ukchina/trad/multimedia/2015/11/151120_video_100w_london_chinese_entrepreneurs',
        variantPath:
          '/ukchina/simp/multimedia/2015/11/151120_video_100w_london_chinese_entrepreneurs',
        serviceContext: ukChinaServiceConfig.trad,
        requestContext: { variant: 'trad', env: 'test' },
        otherVariant: 'simp',
      },
    };

    describe('canonical', () => {
      Object.keys(testCases).forEach(testCase => {
        it(`Script Link should contain link to other variant when on ${testCase}`, () => {
          const {
            matchPath,
            path,
            variantPath,
            // @ts-expect-error type inference issue
            serviceContext = serbianServiceConfig.lat,
            // @ts-expect-error type inference issue
            requestContext = requestContextMock,
            // @ts-expect-error type inference issue
            toggleContext = toggleContextMock,
            // @ts-expect-error type inference issue
            otherVariant = 'cyr',
          } = testCases[testCase as keyof typeof testCases];

          const { container } = withRouter(
            <ScriptLinkContainerWithContext
              serviceContext={serviceContext}
              requestContext={requestContext}
              toggleContext={toggleContext}
            />,
            matchPath,
            path,
          );

          const scriptLink = container.querySelector(
            `a[data-variant="${otherVariant}"]`,
          ) as Element;

          expect(scriptLink.getAttribute('href')).toBe(variantPath);
        });
      });
      it(`Script Link should contain link to other variant when on topic page`, () => {
        const matchPath = topicPath;
        const path = '/serbian/lat/topics/c06g871g3knt';
        const variantPath = '/serbian/cyr/topics/c7zp707dy8yt';
        const otherVariant = 'cyr';

        const { container } = withRouter(
          <ScriptLinkContainerWithContext scriptSwitchId="c7zp707dy8yt" />,
          matchPath,
          path,
        );

        const scriptLink = container.querySelector(
          `a[data-variant="${otherVariant}"]`,
        ) as Element;

        expect(scriptLink.getAttribute('href')).toBe(variantPath);
      });
    });

    describe('amp', () => {
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      const { errorPage, ...ampTestCases } = testCases;
      Object.keys(ampTestCases).forEach(testCase => {
        it(`Script Link should contain link to other variant when on ${testCase}`, () => {
          const {
            matchPath,
            path,
            variantPath,
            // @ts-expect-error type inference issue
            serviceContext = serbianServiceConfig.lat,
            // @ts-expect-error type inference issue
            requestContext = requestContextMock,
            // @ts-expect-error type inference issue
            toggleContext = toggleContextMock,
            // @ts-expect-error type inference issue
            otherVariant = 'cyr',
          } = testCases[testCase as keyof typeof testCases];

          const { container } = withRouter(
            <ScriptLinkContainerWithContext
              serviceContext={serviceContext}
              requestContext={requestContext}
              toggleContext={toggleContext}
            />,
            matchPath,
            `${path}.amp`,
          );

          const scriptLink = container.querySelector(
            `a[data-variant="${otherVariant}"]`,
          ) as Element;

          expect(scriptLink.getAttribute('href')).toBe(`${variantPath}`);
        });
      });
    });
  });

  describe('getVariantHref', () => {
    it('should generate correct variant href on a canonical page', () => {
      const path = '/:foo(foo)/:bar(bar):variant(/simp|/trad|/cyr|/lat)';

      expect(
        getVariantHref({
          path,
          params: { foo: 'foo', bar: 'bar', variant: '/lat' } as Record<
            string,
            string
          >,
          service: 'serbian',
          variant: 'cyr',
        }),
      ).toEqual('/foo/bar/cyr');
    });

    it('should generate correct variant href on an amp page', () => {
      const path =
        '/:foo(foo)/:bar(bar):variant(/simp|/trad|/cyr|/lat):amp(.amp)?';

      expect(
        getVariantHref({
          path,
          params: { foo: 'foo', bar: 'bar', variant: '/lat', amp: '.amp' },
          service: 'serbian',
          variant: 'cyr',
        }),
      ).toEqual('/foo/bar/cyr');
    });

    it('should generate fallback if no path defined', () => {
      expect(
        getVariantHref({
          params: {},
          service: 'serbian',
          variant: 'cyr',
        }),
      ).toEqual('/serbian/cyr');
    });

    it('should generate fallback if path does not match defined route from config', () => {
      expect(
        getVariantHref({
          path: '/',
          params: {},
          service: 'serbian',
          variant: 'cyr',
        }),
      ).toEqual('/serbian/cyr');
    });

    it('should generate fallback if a parameter specified in the path is not provided', () => {
      expect(
        getVariantHref({
          path: '/:foo',
          params: {},
          service: 'serbian',
          variant: 'cyr',
        }),
      ).toEqual('/serbian/cyr');

      expect(
        getVariantHref({
          path: '/:foo:bar',
          params: { foo: 'foo' },
          service: 'serbian',
          variant: 'cyr',
        }),
      ).toEqual('/serbian/cyr');
    });
  });

  it('should not render when scriptLink toggle is off', () => {
    const testToggles = {
      scriptLink: {
        enabled: false,
      },
      variantCookie: {
        enabled: false,
      },
    };
    const { container } = withRouter(
      <ScriptLinkContainerWithContext
        toggleContext={{
          toggleState: testToggles,
          toggleDispatch: mockToggleDispatch,
        }}
      />,
      frontPagePath,
      '/serbian/lat',
    );
    expect(container).toBeEmptyDOMElement();
  });

  it('should not render when app is Next.JS', () => {
    const { container } = withRouter(
      <ScriptLinkContainerWithContext
        requestContext={{
          variant: 'lat',
          env: 'test',
          isNextJs: true,
        }}
      />,
      frontPagePath,
      '/serbian/lat',
    );
    expect(container).toBeEmptyDOMElement();
  });
});