From 7faa6b35c907dd451fb1ccc605ec8368bec27d55 Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Mon, 1 Nov 2021 11:01:36 -1000 Subject: [PATCH] feat: add *BySomeTags and *ByEveryTag hooks --- src/hooks.ts | 66 ++++++-- src/index.ts | 6 +- ...useAllPrismicDocumentsByEveryTag.test.tsx} | 10 +- .../useAllPrismicDocumentsBySomeTags.test.tsx | 145 ++++++++++++++++++ ...=> usePrismicDocumentsByEveryTag.test.tsx} | 14 +- test/usePrismicDocumentsBySomeTags.test.tsx | 143 +++++++++++++++++ 6 files changed, 360 insertions(+), 24 deletions(-) rename test/{useAllPrismicDocumentsByTags.test.tsx => useAllPrismicDocumentsByEveryTag.test.tsx} (93%) create mode 100644 test/useAllPrismicDocumentsBySomeTags.test.tsx rename test/{usePrismicDocumentsByTags.test.tsx => usePrismicDocumentsByEveryTag.test.tsx} (92%) create mode 100644 test/usePrismicDocumentsBySomeTags.test.tsx diff --git a/src/hooks.ts b/src/hooks.ts index 27025ca..9326c1d 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -319,6 +319,7 @@ export const useAllPrismicDocumentsByTag = < /** * A hook that queries documents from the Prismic repository with specific tags. + * A document must be tagged with at least one of the queried tags to be included. * * @remarks * An additional `@prismicio/client` instance can be provided at `params.client`. @@ -329,18 +330,65 @@ export const useAllPrismicDocumentsByTag = < * @returns The composable payload {@link ClientHookReturnType} * @see Underlying `@prismicio/client` method {@link proto.getByTags} */ -export const usePrismicDocumentsByTags = < +export const usePrismicDocumentsBySomeTags = < TDocument extends prismicT.PrismicDocument, >( ...args: [ - tag: ClientMethodParameters<"getByTags">[0], - params?: ClientMethodParameters<"getByTags">[1] & HookOnlyParameters, + tag: ClientMethodParameters<"getBySomeTags">[0], + params?: ClientMethodParameters<"getBySomeTags">[1] & HookOnlyParameters, ] ): ClientHookReturnType> => - useStatefulPrismicClientMethod(proto.getByTags, args); + useStatefulPrismicClientMethod(proto.getBySomeTags, args); /** - * A hook that queries all documents from the Prismic repository with specific tags. + * A hook that queries all documents from the Prismic repository with specific + * tags. A document must be tagged with at least one of the queried tags to be included. + * + * @remarks + * An additional `@prismicio/client` instance can be provided at `params.client`. + * @typeParam TDocument - Type of Prismic documents returned + * @param tags - A list of tags that must be included on a document + * @param params - Parameters to filter and sort results + * + * @returns The composable payload {@link ClientHookReturnType} + * @see Underlying `@prismicio/client` method {@link proto.getAllByTags} + */ +export const useAllPrismicDocumentsBySomeTags = < + TDocument extends prismicT.PrismicDocument, +>( + ...args: [ + tag: ClientMethodParameters<"getAllBySomeTags">[0], + params?: ClientMethodParameters<"getAllBySomeTags">[1] & HookOnlyParameters, + ] +): ClientHookReturnType => + useStatefulPrismicClientMethod(proto.getAllBySomeTags, args); + +/** + * A hook that queries documents from the Prismic repository with specific tags. + * A document must be tagged with all of the queried tags to be included. + * + * @remarks + * An additional `@prismicio/client` instance can be provided at `params.client`. + * @typeParam TDocument - Type of Prismic documents returned + * @param tags - A list of tags that must be included on a document + * @param params - Parameters to filter, sort, and paginate results + * + * @returns The composable payload {@link ClientHookReturnType} + * @see Underlying `@prismicio/client` method {@link proto.getByTags} + */ +export const usePrismicDocumentsByEveryTag = < + TDocument extends prismicT.PrismicDocument, +>( + ...args: [ + tag: ClientMethodParameters<"getByEveryTag">[0], + params?: ClientMethodParameters<"getByEveryTag">[1] & HookOnlyParameters, + ] +): ClientHookReturnType> => + useStatefulPrismicClientMethod(proto.getByEveryTag, args); + +/** + * A hook that queries all documents from the Prismic repository with specific + * tags. A document must be tagged with all of the queried tags to be included. * * @remarks * An additional `@prismicio/client` instance can be provided at `params.client`. @@ -351,12 +399,12 @@ export const usePrismicDocumentsByTags = < * @returns The composable payload {@link ClientHookReturnType} * @see Underlying `@prismicio/client` method {@link proto.getAllByTags} */ -export const useAllPrismicDocumentsByTags = < +export const useAllPrismicDocumentsByEveryTag = < TDocument extends prismicT.PrismicDocument, >( ...args: [ - tag: ClientMethodParameters<"getAllByTags">[0], - params?: ClientMethodParameters<"getAllByTags">[1] & HookOnlyParameters, + tag: ClientMethodParameters<"getAllByEveryTag">[0], + params?: ClientMethodParameters<"getAllByEveryTag">[1] & HookOnlyParameters, ] ): ClientHookReturnType => - useStatefulPrismicClientMethod(proto.getAllByTags, args); + useStatefulPrismicClientMethod(proto.getAllByEveryTag, args); diff --git a/src/index.ts b/src/index.ts index f4d3aa8..c7017c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,18 +32,20 @@ export type { PrismicToolbarProps } from "./PrismicToolbar"; export { useAllPrismicDocuments, + useAllPrismicDocumentsByEveryTag, useAllPrismicDocumentsByIDs, + useAllPrismicDocumentsBySomeTags, useAllPrismicDocumentsByTag, - useAllPrismicDocumentsByTags, useAllPrismicDocumentsByType, useAllPrismicDocumentsByUIDs, useFirstPrismicDocument, usePrismicDocumentByID, usePrismicDocumentByUID, usePrismicDocuments, + usePrismicDocumentsByEveryTag, usePrismicDocumentsByIDs, + usePrismicDocumentsBySomeTags, usePrismicDocumentsByTag, - usePrismicDocumentsByTags, usePrismicDocumentsByType, usePrismicDocumentsByUIDs, useSinglePrismicDocument, diff --git a/test/useAllPrismicDocumentsByTags.test.tsx b/test/useAllPrismicDocumentsByEveryTag.test.tsx similarity index 93% rename from test/useAllPrismicDocumentsByTags.test.tsx rename to test/useAllPrismicDocumentsByEveryTag.test.tsx index 1a00c98..7455928 100644 --- a/test/useAllPrismicDocumentsByTags.test.tsx +++ b/test/useAllPrismicDocumentsByEveryTag.test.tsx @@ -16,7 +16,7 @@ import { createRepositoryResponse } from "./__testutils__/createRepositoryRespon import { getMasterRef } from "./__testutils__/getMasterRef"; import { md5 } from "./__testutils__/md5"; -import { PrismicProvider, useAllPrismicDocumentsByTags } from "../src"; +import { PrismicProvider, useAllPrismicDocumentsByEveryTag } from "../src"; const server = mswNode.setupServer(); test.before(() => server.listen({ onUnhandledRequest: "error" })); @@ -51,7 +51,7 @@ test.serial("returns documents with matching IDs", async (t) => { ); const { result, waitForValueToChange } = renderHook( - () => useAllPrismicDocumentsByTags(tags), + () => useAllPrismicDocumentsByEveryTag(tags), { wrapper }, ); @@ -83,7 +83,7 @@ test.serial("supports params", async (t) => { ); const { result, waitForValueToChange } = renderHook( - () => useAllPrismicDocumentsByTags(tags, params), + () => useAllPrismicDocumentsByEveryTag(tags, params), { wrapper }, ); @@ -110,7 +110,7 @@ test.serial("supports explicit client", async (t) => { ); const { result, waitForValueToChange } = renderHook(() => - useAllPrismicDocumentsByTags(tags, { client }), + useAllPrismicDocumentsByEveryTag(tags, { client }), ); await waitForValueToChange(() => result.current[1].state === "loaded"); @@ -134,7 +134,7 @@ test.serial("returns failed state on error", async (t) => { ); const { result, waitForValueToChange } = renderHook( - () => useAllPrismicDocumentsByTags(["tag", "tag2"]), + () => useAllPrismicDocumentsByEveryTag(["tag", "tag2"]), { wrapper }, ); diff --git a/test/useAllPrismicDocumentsBySomeTags.test.tsx b/test/useAllPrismicDocumentsBySomeTags.test.tsx new file mode 100644 index 0000000..2258af1 --- /dev/null +++ b/test/useAllPrismicDocumentsBySomeTags.test.tsx @@ -0,0 +1,145 @@ +/* eslint-disable react/display-name */ +/* eslint-disable react/prop-types */ + +import test from "ava"; +import * as React from "react"; +import * as msw from "msw"; +import * as mswNode from "msw/node"; +import * as prismic from "@prismicio/client"; +import { renderHook, cleanup } from "@testing-library/react-hooks"; + +import { createClient } from "./__testutils__/createClient"; +import { createMockQueryHandler } from "./__testutils__/createMockQueryHandler"; +import { createMockRepositoryHandler } from "./__testutils__/createMockRepositoryHandler"; +import { createQueryResponsePages } from "./__testutils__/createQueryResponsePages"; +import { createRepositoryResponse } from "./__testutils__/createRepositoryResponse"; +import { getMasterRef } from "./__testutils__/getMasterRef"; +import { md5 } from "./__testutils__/md5"; + +import { PrismicProvider, useAllPrismicDocumentsBySomeTags } from "../src"; + +const server = mswNode.setupServer(); +test.before(() => server.listen({ onUnhandledRequest: "error" })); +test.after(() => server.close()); + +// We must clean up after each test. We also must run each test serially to +// ensure the clean up process only occurs between tests. +test.afterEach(() => { + cleanup(); +}); + +const createWrapper = (client: prismic.Client): React.ComponentType => { + return (props) => ; +}; + +test.serial("returns documents with matching IDs", async (t) => { + const client = createClient(t); + const wrapper = createWrapper(client); + const repositoryResponse = createRepositoryResponse(); + const queryResponsePages = createQueryResponsePages(); + const documents = queryResponsePages.flatMap((page) => page.results); + const tags = documents[0].tags; + const ref = getMasterRef(repositoryResponse); + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, queryResponsePages, { + ref, + q: `[${prismic.predicate.any("document.tags", tags)}]`, + pageSize: 100, + }), + ); + + const { result, waitForValueToChange } = renderHook( + () => useAllPrismicDocumentsBySomeTags(tags), + { wrapper }, + ); + + await waitForValueToChange(() => result.current[1].state === "loaded"); + + t.deepEqual(result.current[0], documents); +}); + +test.serial("supports params", async (t) => { + const client = createClient(t); + const wrapper = createWrapper(client); + const repositoryResponse = createRepositoryResponse(); + const queryResponsePages = createQueryResponsePages(); + const documents = queryResponsePages.flatMap((page) => page.results); + const tags = documents[0].tags; + const ref = getMasterRef(repositoryResponse); + + const params = { + pageSize: 2, + }; + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, queryResponsePages, { + ref, + q: `[${prismic.predicate.any("document.tags", tags)}]`, + pageSize: params.pageSize.toString(), + }), + ); + + const { result, waitForValueToChange } = renderHook( + () => useAllPrismicDocumentsBySomeTags(tags, params), + { wrapper }, + ); + + await waitForValueToChange(() => result.current[1].state === "loaded"); + + t.deepEqual(result.current[0], documents); +}); + +test.serial("supports explicit client", async (t) => { + const client = createClient(t); + const repositoryResponse = createRepositoryResponse(); + const queryResponsePages = createQueryResponsePages(); + const documents = queryResponsePages.flatMap((page) => page.results); + const tags = documents[0].tags; + const ref = getMasterRef(repositoryResponse); + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, queryResponsePages, { + ref, + q: `[${prismic.predicate.any("document.tags", tags)}]`, + pageSize: 100, + }), + ); + + const { result, waitForValueToChange } = renderHook(() => + useAllPrismicDocumentsBySomeTags(tags, { client }), + ); + + await waitForValueToChange(() => result.current[1].state === "loaded"); + + t.deepEqual(result.current[0], documents); +}); + +test.serial("returns failed state on error", async (t) => { + const client = createClient(t); + const wrapper = createWrapper(client); + const repositoryResponse = { + message: "invalid access token", + oauth_initiate: "oauth_initiate", + oauth_token: "oauth_token", + }; + + server.use( + msw.rest.get(prismic.getEndpoint(md5(t.title)), (_req, res, ctx) => { + return res(ctx.status(403), ctx.json(repositoryResponse)); + }), + ); + + const { result, waitForValueToChange } = renderHook( + () => useAllPrismicDocumentsBySomeTags(["tag", "tag2"]), + { wrapper }, + ); + + await waitForValueToChange(() => result.current[1].state === "failed"); + + t.true(result.current[1].error instanceof prismic.ForbiddenError); + t.is(result.current[0], undefined); +}); diff --git a/test/usePrismicDocumentsByTags.test.tsx b/test/usePrismicDocumentsByEveryTag.test.tsx similarity index 92% rename from test/usePrismicDocumentsByTags.test.tsx rename to test/usePrismicDocumentsByEveryTag.test.tsx index 32e95e7..7d8e5a7 100644 --- a/test/usePrismicDocumentsByTags.test.tsx +++ b/test/usePrismicDocumentsByEveryTag.test.tsx @@ -16,7 +16,7 @@ import { createRepositoryResponse } from "./__testutils__/createRepositoryRespon import { getMasterRef } from "./__testutils__/getMasterRef"; import { md5 } from "./__testutils__/md5"; -import { PrismicProvider, usePrismicDocumentsByTags } from "../src"; +import { PrismicProvider, usePrismicDocumentsByEveryTag } from "../src"; const server = mswNode.setupServer(); test.before(() => server.listen({ onUnhandledRequest: "error" })); @@ -50,7 +50,7 @@ test.serial("returns documents with matching types", async (t) => { ); const { result, waitForValueToChange } = renderHook( - () => usePrismicDocumentsByTags(tags), + () => usePrismicDocumentsByEveryTag(tags), { wrapper }, ); @@ -82,7 +82,7 @@ test.serial("supports params", async (t) => { ); const { result, waitForValueToChange } = renderHook( - () => usePrismicDocumentsByTags(tags, params), + () => usePrismicDocumentsByEveryTag(tags, params), { wrapper }, ); @@ -108,7 +108,7 @@ test.serial("supports explicit client", async (t) => { ); const { result, waitForValueToChange } = renderHook(() => - usePrismicDocumentsByTags(tags, { client }), + usePrismicDocumentsByEveryTag(tags, { client }), ); await waitForValueToChange(() => result.current[1].state === "loaded"); @@ -132,13 +132,11 @@ test.serial("returns failed state on error", async (t) => { ); const { result, waitForValueToChange } = renderHook( - () => usePrismicDocumentsByTags(["tag", "tag2"]), + () => usePrismicDocumentsByEveryTag(["tag", "tag2"]), { wrapper }, ); - await waitForValueToChange( - () => result.current[1].state === "failed", - ); + await waitForValueToChange(() => result.current[1].state === "failed"); t.true(result.current[1].error instanceof prismic.ForbiddenError); t.is(result.current[0], undefined); diff --git a/test/usePrismicDocumentsBySomeTags.test.tsx b/test/usePrismicDocumentsBySomeTags.test.tsx new file mode 100644 index 0000000..f9ee45b --- /dev/null +++ b/test/usePrismicDocumentsBySomeTags.test.tsx @@ -0,0 +1,143 @@ +/* eslint-disable react/display-name */ +/* eslint-disable react/prop-types */ + +import test from "ava"; +import * as React from "react"; +import * as msw from "msw"; +import * as mswNode from "msw/node"; +import * as prismic from "@prismicio/client"; +import { renderHook, cleanup } from "@testing-library/react-hooks"; + +import { createClient } from "./__testutils__/createClient"; +import { createMockQueryHandler } from "./__testutils__/createMockQueryHandler"; +import { createMockRepositoryHandler } from "./__testutils__/createMockRepositoryHandler"; +import { createQueryResponsePages } from "./__testutils__/createQueryResponsePages"; +import { createRepositoryResponse } from "./__testutils__/createRepositoryResponse"; +import { getMasterRef } from "./__testutils__/getMasterRef"; +import { md5 } from "./__testutils__/md5"; + +import { PrismicProvider, usePrismicDocumentsBySomeTags } from "../src"; + +const server = mswNode.setupServer(); +test.before(() => server.listen({ onUnhandledRequest: "error" })); +test.after(() => server.close()); + +// We must clean up after each test. We also must run each test serially to +// ensure the clean up process only occurs between tests. +test.afterEach(() => { + cleanup(); +}); + +const createWrapper = (client: prismic.Client): React.ComponentType => { + return (props) => ; +}; + +test.serial("returns documents with matching types", async (t) => { + const client = createClient(t); + const wrapper = createWrapper(client); + const repositoryResponse = createRepositoryResponse(); + const queryResponsePages = createQueryResponsePages(); + const documents = queryResponsePages[0].results; + const tags = documents[0].tags; + const ref = getMasterRef(repositoryResponse); + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, queryResponsePages, { + ref, + q: `[${prismic.predicate.any("document.tags", tags)}]`, + }), + ); + + const { result, waitForValueToChange } = renderHook( + () => usePrismicDocumentsBySomeTags(tags), + { wrapper }, + ); + + await waitForValueToChange(() => result.current[1].state === "loaded"); + + t.deepEqual(result.current[0], queryResponsePages[0]); +}); + +test.serial("supports params", async (t) => { + const client = createClient(t); + const wrapper = createWrapper(client); + const repositoryResponse = createRepositoryResponse(); + const queryResponsePages = createQueryResponsePages(); + const documents = queryResponsePages[0].results; + const tags = documents[0].tags; + const ref = getMasterRef(repositoryResponse); + + const params = { + pageSize: 2, + }; + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, queryResponsePages, { + ref, + q: `[${prismic.predicate.any("document.tags", tags)}]`, + pageSize: params.pageSize.toString(), + }), + ); + + const { result, waitForValueToChange } = renderHook( + () => usePrismicDocumentsBySomeTags(tags, params), + { wrapper }, + ); + + await waitForValueToChange(() => result.current[1].state === "loaded"); + + t.deepEqual(result.current[0], queryResponsePages[0]); +}); + +test.serial("supports explicit client", async (t) => { + const client = createClient(t); + const repositoryResponse = createRepositoryResponse(); + const queryResponsePages = createQueryResponsePages(); + const documents = queryResponsePages[0].results; + const tags = documents[0].tags; + const ref = getMasterRef(repositoryResponse); + + server.use( + createMockRepositoryHandler(t, repositoryResponse), + createMockQueryHandler(t, queryResponsePages, { + ref, + q: `[${prismic.predicate.any("document.tags", tags)}]`, + }), + ); + + const { result, waitForValueToChange } = renderHook(() => + usePrismicDocumentsBySomeTags(tags, { client }), + ); + + await waitForValueToChange(() => result.current[1].state === "loaded"); + + t.deepEqual(result.current[0], queryResponsePages[0]); +}); + +test.serial("returns failed state on error", async (t) => { + const client = createClient(t); + const wrapper = createWrapper(client); + const repositoryResponse = { + message: "invalid access token", + oauth_initiate: "oauth_initiate", + oauth_token: "oauth_token", + }; + + server.use( + msw.rest.get(prismic.getEndpoint(md5(t.title)), (_req, res, ctx) => { + return res(ctx.status(403), ctx.json(repositoryResponse)); + }), + ); + + const { result, waitForValueToChange } = renderHook( + () => usePrismicDocumentsBySomeTags(["tag", "tag2"]), + { wrapper }, + ); + + await waitForValueToChange(() => result.current[1].state === "failed"); + + t.true(result.current[1].error instanceof prismic.ForbiddenError); + t.is(result.current[0], undefined); +});