From 85c4c42bd601270d7be0f34a0767a34bb85e29bb Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
Date: Tue, 12 Dec 2023 18:50:03 +0100
Subject: refactor(hooks): rewrite useBreadcrumbs hook

* use next/router to get the slug instead of using props
* handle cases where the current page title is not provided
* update JSON-LD schema to match the example in documentation
* add tests
---
 .../hooks/use-breadcrumbs/use-breadcrumbs.test.tsx | 232 +++++++++++++++++++++
 1 file changed, 232 insertions(+)
 create mode 100644 src/utils/hooks/use-breadcrumbs/use-breadcrumbs.test.tsx

(limited to 'src/utils/hooks/use-breadcrumbs/use-breadcrumbs.test.tsx')

diff --git a/src/utils/hooks/use-breadcrumbs/use-breadcrumbs.test.tsx b/src/utils/hooks/use-breadcrumbs/use-breadcrumbs.test.tsx
new file mode 100644
index 0000000..9778aed
--- /dev/null
+++ b/src/utils/hooks/use-breadcrumbs/use-breadcrumbs.test.tsx
@@ -0,0 +1,232 @@
+import { describe, expect, it } from '@jest/globals';
+import { act, renderHook } from '@testing-library/react';
+import nextRouterMock from 'next-router-mock';
+import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider';
+import type { ReactNode } from 'react';
+import { IntlProvider } from 'react-intl';
+import { PAGINATED_ROUTE_PREFIX, ROUTES } from '../../constants';
+import { capitalize } from '../../helpers';
+import { useBreadcrumbs } from './use-breadcrumbs';
+
+const AllProviders = ({ children }: { children: ReactNode }) => (
+  <IntlProvider defaultLocale="en" locale="en">
+    <MemoryRouterProvider>{children}</MemoryRouterProvider>
+  </IntlProvider>
+);
+
+describe('useBreadcrumbs', () => {
+  it('returns the breadcrumbs items and its schema', async () => {
+    const currentSlug = '/current-slug';
+    const label = capitalize(
+      (currentSlug.split('/').pop() ?? currentSlug).replaceAll('-', ' ')
+    );
+
+    await act(async () => nextRouterMock.push(currentSlug));
+
+    const { result } = renderHook(() => useBreadcrumbs(), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(4);
+
+    expect(result.current.items).toHaveLength(2);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: currentSlug,
+      label,
+      slug: currentSlug,
+    });
+    expect(result.current.schema).toStrictEqual({
+      '@type': 'BreadcrumbList',
+      '@id': 'breadcrumbs',
+      itemListElement: [
+        {
+          '@type': 'ListItem',
+          item: {
+            '@id': ROUTES.HOME,
+            name: 'Home',
+          },
+          position: 1,
+        },
+        {
+          '@type': 'ListItem',
+          item: {
+            '@id': currentSlug,
+            name: label,
+          },
+          position: 2,
+        },
+      ],
+    });
+  });
+
+  it('can render the items for the 404 page', async () => {
+    await act(async () => nextRouterMock.push(ROUTES.NOT_FOUND));
+
+    const { result } = renderHook(() => useBreadcrumbs(), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(3);
+
+    expect(result.current.items).toHaveLength(2);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: ROUTES.NOT_FOUND,
+      label: '404: Not found',
+      slug: ROUTES.NOT_FOUND,
+    });
+  });
+
+  it('can render the items for the Blog page', async () => {
+    await act(async () => nextRouterMock.push(ROUTES.BLOG));
+
+    const { result } = renderHook(() => useBreadcrumbs(), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(3);
+
+    expect(result.current.items).toHaveLength(2);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: ROUTES.BLOG,
+      label: 'Blog',
+      slug: ROUTES.BLOG,
+    });
+  });
+
+  it('can render the items for the paginated routes', async () => {
+    const pageNumber = 3;
+    const currentSlug = `${ROUTES.BLOG}${PAGINATED_ROUTE_PREFIX}/${pageNumber}`;
+    await act(async () => nextRouterMock.push(currentSlug));
+
+    const { result } = renderHook(() => useBreadcrumbs(), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(4);
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect(result.current.items).toHaveLength(3);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: ROUTES.BLOG,
+      label: 'Blog',
+      slug: ROUTES.BLOG,
+    });
+    expect(result.current.items[2]).toStrictEqual({
+      id: currentSlug,
+      label: `Page ${pageNumber}`,
+      slug: currentSlug,
+    });
+  });
+
+  it('can render the items for the Projects page', async () => {
+    await act(async () => nextRouterMock.push(ROUTES.PROJECTS));
+
+    const { result } = renderHook(() => useBreadcrumbs(), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(3);
+
+    expect(result.current.items).toHaveLength(2);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: ROUTES.PROJECTS,
+      label: 'Projects',
+      slug: ROUTES.PROJECTS,
+    });
+  });
+
+  it('can render the items for the Search page', async () => {
+    const query = 'similique';
+    const currentSlug = `${ROUTES.SEARCH}?s=${query}`;
+    await act(async () => nextRouterMock.push(currentSlug));
+
+    const { result } = renderHook(() => useBreadcrumbs(), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(4);
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect(result.current.items).toHaveLength(3);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: ROUTES.SEARCH,
+      label: 'Search',
+      slug: ROUTES.SEARCH,
+    });
+    expect(result.current.items[2]).toStrictEqual({
+      id: currentSlug,
+      label: `Search results for "${query}"`,
+      slug: currentSlug,
+    });
+  });
+
+  it('can render the items for the Articles page', async () => {
+    const article = {
+      slug: '/the-article-slug',
+      title: 'qui ducimus rerum',
+    };
+    const currentSlug = `${ROUTES.ARTICLE}${article.slug}`;
+    await act(async () => nextRouterMock.push(currentSlug));
+
+    const { result } = renderHook(() => useBreadcrumbs(article.title), {
+      wrapper: AllProviders,
+    });
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect.assertions(4);
+
+    // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+    expect(result.current.items).toHaveLength(3);
+    expect(result.current.items[0]).toStrictEqual({
+      id: '/',
+      label: 'Home',
+      slug: '/',
+    });
+    expect(result.current.items[1]).toStrictEqual({
+      id: ROUTES.BLOG,
+      label: 'Blog',
+      slug: ROUTES.BLOG,
+    });
+    expect(result.current.items[2]).toStrictEqual({
+      id: currentSlug,
+      label: article.title,
+      slug: currentSlug,
+    });
+  });
+});
-- 
cgit v1.2.3

