diff options
Diffstat (limited to 'src/utils')
| -rw-r--r-- | src/utils/hooks/index.ts | 3 | ||||
| -rw-r--r-- | src/utils/hooks/use-articles-list/index.ts | 1 | ||||
| -rw-r--r-- | src/utils/hooks/use-articles-list/use-articles-list.test.tsx (renamed from src/utils/hooks/use-posts-list/use-posts-list.test.tsx) | 51 | ||||
| -rw-r--r-- | src/utils/hooks/use-articles-list/use-articles-list.ts (renamed from src/utils/hooks/use-posts-list/use-posts-list.ts) | 21 | ||||
| -rw-r--r-- | src/utils/hooks/use-is-mounted.tsx | 17 | ||||
| -rw-r--r-- | src/utils/hooks/use-pagination/use-pagination.ts | 8 | ||||
| -rw-r--r-- | src/utils/hooks/use-posts-list/index.ts | 1 | ||||
| -rw-r--r-- | src/utils/hooks/use-redirection.tsx | 31 | ||||
| -rw-r--r-- | src/utils/hooks/use-redirection/index.ts | 1 | ||||
| -rw-r--r-- | src/utils/hooks/use-redirection/use-redirection.test.ts | 80 | ||||
| -rw-r--r-- | src/utils/hooks/use-redirection/use-redirection.ts | 41 |
11 files changed, 183 insertions, 72 deletions
diff --git a/src/utils/hooks/index.ts b/src/utils/hooks/index.ts index da4ed9e..1e0bfe3 100644 --- a/src/utils/hooks/index.ts +++ b/src/utils/hooks/index.ts @@ -1,5 +1,6 @@ export * from './use-ackee'; export * from './use-article'; +export * from './use-articles-list'; export * from './use-boolean'; export * from './use-breadcrumb'; export * from './use-comments'; @@ -7,13 +8,11 @@ export * from './use-data-from-api'; export * from './use-form'; export * from './use-github-api'; export * from './use-headings-tree'; -export * from './use-is-mounted'; export * from './use-local-storage'; export * from './use-match-media'; export * from './use-on-click-outside'; export * from './use-on-route-change'; export * from './use-pagination'; -export * from './use-posts-list'; export * from './use-prism'; export * from './use-prism-theme'; export * from './use-redirection'; diff --git a/src/utils/hooks/use-articles-list/index.ts b/src/utils/hooks/use-articles-list/index.ts new file mode 100644 index 0000000..5f42aeb --- /dev/null +++ b/src/utils/hooks/use-articles-list/index.ts @@ -0,0 +1 @@ +export * from './use-articles-list'; diff --git a/src/utils/hooks/use-posts-list/use-posts-list.test.tsx b/src/utils/hooks/use-articles-list/use-articles-list.test.tsx index f23ddde..6191ed6 100644 --- a/src/utils/hooks/use-posts-list/use-posts-list.test.tsx +++ b/src/utils/hooks/use-articles-list/use-articles-list.test.tsx @@ -6,11 +6,13 @@ import { it, jest, } from '@jest/globals'; -import { act, renderHook } from '@testing-library/react'; +import { act, renderHook, waitFor } from '@testing-library/react'; import type { ReactNode } from 'react'; import { SWRConfig } from 'swr'; -import { fetchPostsList } from '../../../services/graphql'; -import { usePostsList } from './use-posts-list'; +import { wpPostsFixture } from '../../../../tests/fixtures'; +import { getConnection } from '../../../../tests/utils/graphql'; +import { convertPostPreviewToArticlePreview } from '../../../services/graphql'; +import { useArticlesList } from './use-articles-list'; const wrapper = ({ children }: { children?: ReactNode }) => { const map = new Map(); @@ -38,7 +40,7 @@ const wrapper = ({ children }: { children?: ReactNode }) => { ); }; -describe('usePostsList', () => { +describe('useArticlesList', () => { beforeEach(() => { /* Not sure why it is needed, but without it Jest was complaining with `You * are trying to import a file after the Jest environment has been torn @@ -55,10 +57,9 @@ describe('usePostsList', () => { it('can return the first new result index when loading more posts', async () => { const perPage = 5; - const { result } = renderHook( - () => usePostsList({ fetcher: fetchPostsList, perPage }), - { wrapper } - ); + const { result } = renderHook(() => useArticlesList({ perPage }), { + wrapper, + }); expect.assertions(2); @@ -71,4 +72,38 @@ describe('usePostsList', () => { // Assuming there is more than one page. expect(result.current.firstNewResultIndex).toBe(perPage + 1); }); + + it('converts a WordPress post connection to an article connection', async () => { + const perPage = 1; + const { result } = renderHook(() => useArticlesList({ perPage }), { + wrapper, + }); + const connection = getConnection({ + after: null, + data: wpPostsFixture, + first: perPage, + }); + + expect.hasAssertions(); + + await waitFor(() => { + expect(result.current.articles).toBeDefined(); + }); + + expect(result.current.articles).toStrictEqual([ + { + edges: connection.edges.map((edge) => { + return { + cursor: edge.cursor, + node: convertPostPreviewToArticlePreview(edge.node), + }; + }), + pageInfo: { + endCursor: connection.pageInfo.endCursor, + hasNextPage: connection.pageInfo.hasNextPage, + total: connection.pageInfo.total, + }, + }, + ]); + }); }); diff --git a/src/utils/hooks/use-posts-list/use-posts-list.ts b/src/utils/hooks/use-articles-list/use-articles-list.ts index bb77f31..8a52702 100644 --- a/src/utils/hooks/use-posts-list/use-posts-list.ts +++ b/src/utils/hooks/use-articles-list/use-articles-list.ts @@ -1,4 +1,8 @@ import { useCallback, useState } from 'react'; +import { + convertPostPreviewToArticlePreview, + fetchPostsList, +} from '../../../services/graphql'; import type { ArticlePreview, GraphQLConnection, @@ -11,9 +15,8 @@ import { usePagination, type UsePaginationReturn, } from '../use-pagination'; -import { convertPostPreviewToArticlePreview } from 'src/services/graphql'; -export type usePostsListReturn = Omit< +export type useArticlesListReturn = Omit< UsePaginationReturn<WPPostPreview>, 'data' > & { @@ -27,9 +30,9 @@ export type usePostsListReturn = Omit< firstNewResultIndex: Maybe<number>; }; -export const usePostsList = ( - config: UsePaginationConfig<WPPostPreview> -): usePostsListReturn => { +export const useArticlesList = ( + config: Omit<UsePaginationConfig<WPPostPreview>, 'fetcher'> +): useArticlesListReturn => { const { data, error, @@ -42,7 +45,7 @@ export const usePostsList = ( isValidating, loadMore, size, - } = usePagination(config); + } = usePagination({ ...config, fetcher: fetchPostsList }); const [firstNewResultIndex, setFirstNewResultIndex] = useState<Maybe<number>>(undefined); @@ -53,15 +56,15 @@ export const usePostsList = ( }, [config.perPage, loadMore, size]); const articles: Maybe<GraphQLConnection<ArticlePreview>[]> = data?.map( - (page): GraphQLConnection<ArticlePreview> => { + ({ edges, pageInfo }): GraphQLConnection<ArticlePreview> => { return { - edges: page.edges.map((edge): GraphQLEdge<ArticlePreview> => { + edges: edges.map((edge): GraphQLEdge<ArticlePreview> => { return { cursor: edge.cursor, node: convertPostPreviewToArticlePreview(edge.node), }; }), - pageInfo: page.pageInfo, + pageInfo, }; } ); diff --git a/src/utils/hooks/use-is-mounted.tsx b/src/utils/hooks/use-is-mounted.tsx deleted file mode 100644 index 4d85d45..0000000 --- a/src/utils/hooks/use-is-mounted.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { RefObject, useEffect, useState } from 'react'; - -/** - * Check if an HTML element is mounted. - * - * @param {RefObject<HTMLElement>} ref - A React reference to an HTML element. - * @returns {boolean} True if the HTML element is mounted. - */ -export const useIsMounted = (ref: RefObject<HTMLElement>): boolean => { - const [isMounted, setIsMounted] = useState<boolean>(false); - - useEffect(() => { - if (ref.current) setIsMounted(true); - }, [ref]); - - return isMounted; -}; diff --git a/src/utils/hooks/use-pagination/use-pagination.ts b/src/utils/hooks/use-pagination/use-pagination.ts index 2a40aa4..29d5ba2 100644 --- a/src/utils/hooks/use-pagination/use-pagination.ts +++ b/src/utils/hooks/use-pagination/use-pagination.ts @@ -11,7 +11,7 @@ export type UsePaginationFetcherInput = GraphQLEdgesInput & { search?: string; }; -export type UsePaginationConfig<T> = { +export type UsePaginationConfig<T> = Pick<GraphQLEdgesInput, 'after'> & { /** * The initial data. */ @@ -86,6 +86,7 @@ export type UsePaginationReturn<T> = { * @returns {UsePaginationReturn} An object with pagination data and helpers. */ export const usePagination = <T>({ + after, fallback, fetcher, perPage, @@ -97,12 +98,11 @@ export const usePagination = <T>({ return { first: perPage, - after: - pageIndex === 0 ? undefined : previousPageData?.pageInfo.endCursor, + after: pageIndex === 0 ? after : previousPageData?.pageInfo.endCursor, search: searchQuery, }; }, - [perPage, searchQuery] + [after, perPage, searchQuery] ); const { data, error, isLoading, isValidating, setSize, size } = diff --git a/src/utils/hooks/use-posts-list/index.ts b/src/utils/hooks/use-posts-list/index.ts deleted file mode 100644 index 664c142..0000000 --- a/src/utils/hooks/use-posts-list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './use-posts-list'; diff --git a/src/utils/hooks/use-redirection.tsx b/src/utils/hooks/use-redirection.tsx deleted file mode 100644 index 5a677e2..0000000 --- a/src/utils/hooks/use-redirection.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useRouter } from 'next/router'; -import { useEffect } from 'react'; - -export type RouterQuery = { - param: string; - value: string; -}; - -export type UseRedirectionProps = { - /** - * The router query. - */ - query: RouterQuery; - /** - * The redirection url. - */ - redirectTo: string; -}; - -/** - * Redirect to another url when router query match the given parameters. - * - * @param {UseRedirectionProps} props - The redirection parameters. - */ -export const useRedirection = ({ query, redirectTo }: UseRedirectionProps) => { - const router = useRouter(); - - useEffect(() => { - if (router.query[query.param] === query.value) router.push(redirectTo); - }, [query, redirectTo, router]); -}; diff --git a/src/utils/hooks/use-redirection/index.ts b/src/utils/hooks/use-redirection/index.ts new file mode 100644 index 0000000..c81c82c --- /dev/null +++ b/src/utils/hooks/use-redirection/index.ts @@ -0,0 +1 @@ +export * from './use-redirection'; diff --git a/src/utils/hooks/use-redirection/use-redirection.test.ts b/src/utils/hooks/use-redirection/use-redirection.test.ts new file mode 100644 index 0000000..c14ac4c --- /dev/null +++ b/src/utils/hooks/use-redirection/use-redirection.test.ts @@ -0,0 +1,80 @@ +import { describe, it } from '@jest/globals'; +import { renderHook } from '@testing-library/react'; +import nextRouterMock from 'next-router-mock'; +import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider'; +import { useRedirection } from './use-redirection'; + +describe('useRedirection', () => { + it('redirects to another page', async () => { + const initialPath = '/initial-path'; + const redirectPath = '/redirect-path'; + + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + expect.assertions(2); + + await nextRouterMock.push('/initial-path'); + + expect(nextRouterMock.asPath).toBe(initialPath); + + renderHook(() => useRedirection({ to: redirectPath }), { + wrapper: MemoryRouterProvider, + }); + + expect(nextRouterMock.asPath).toBe(redirectPath); + }); + + it('can replace the url in the history', async () => { + const initialPath = '/initial-path'; + const redirectPath = '/redirect-path'; + + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + expect.assertions(2); + + await nextRouterMock.push('/initial-path'); + + expect(nextRouterMock.asPath).toBe(initialPath); + + renderHook(() => useRedirection({ isReplacing: true, to: redirectPath }), { + wrapper: MemoryRouterProvider, + }); + + expect(nextRouterMock.asPath).toBe(redirectPath); + + /* Ideally we should check if when we use `back()` the current path is + * still the redirectPath but it is not yet implemented in the mock. */ + }); + + it('can conditionally redirect to another page', async () => { + const paths = { + initial: '/initial-path', + matching: '/matching-path', + redirect: '/redirect-path', + }; + + // eslint-disable-next-line @typescript-eslint/no-magic-numbers + expect.assertions(3); + + await nextRouterMock.push('/initial-path'); + + expect(nextRouterMock.asPath).toBe(paths.initial); + + const { rerender } = renderHook( + () => + useRedirection({ + to: paths.redirect, + whenPathMatches: (path) => path === paths.matching, + }), + { + wrapper: MemoryRouterProvider, + } + ); + + expect(nextRouterMock.asPath).toBe(paths.initial); + + await nextRouterMock.push(paths.matching); + + rerender(); + + expect(nextRouterMock.asPath).toBe(paths.redirect); + }); +}); diff --git a/src/utils/hooks/use-redirection/use-redirection.ts b/src/utils/hooks/use-redirection/use-redirection.ts new file mode 100644 index 0000000..1592a33 --- /dev/null +++ b/src/utils/hooks/use-redirection/use-redirection.ts @@ -0,0 +1,41 @@ +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; + +export type UseRedirectionConfig = { + /** + * Should the url be replaced in the history? + * + * @default false + */ + isReplacing?: boolean; + /** + * The destination. + */ + to: string; + /** + * Redirect only when the current path matches the condition. + * + * @param {string} path - The current slug. + * @returns {boolean} True if the path matches. + */ + whenPathMatches?: (path: string) => boolean; +}; + +export const useRedirection = ({ + isReplacing = false, + to, + whenPathMatches, +}: UseRedirectionConfig) => { + const router = useRouter(); + + useEffect(() => { + const shouldRedirect = whenPathMatches + ? whenPathMatches(router.asPath) + : true; + + if (shouldRedirect) { + if (isReplacing) router.replace(to, undefined, { shallow: true }); + else router.push(to); + } + }, [isReplacing, router, to, whenPathMatches]); +}; |
