Skip to content

Commit

Permalink
feat: complete implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore committed Jul 29, 2021
1 parent ba15a01 commit 530be7d
Show file tree
Hide file tree
Showing 36 changed files with 4,786 additions and 707 deletions.
2,847 changes: 2,155 additions & 692 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,34 @@
"test": "npm run lint && npm run unit"
},
"dependencies": {
"@prismicio/client": "^6.0.0-alpha.2",
"@prismicio/client": "^6.0.0-alpha.8",
"@prismicio/helpers": "^2.0.0-alpha.5",
"@prismicio/richtext": "^2.0.0-alpha.6",
"react": "^17.0.2",
"react-test-renderer": "^17.0.2"
},
"devDependencies": {
"@prismicio/types": "^0.1.0",
"@prismicio/types": "^0.1.4",
"@testing-library/react": "^12.0.0",
"@testing-library/react-hooks": "^7.0.1",
"@types/jsdom": "^16.2.13",
"@types/node-fetch": "^2.5.12",
"@types/react": "^17.0.11",
"@types/react-test-renderer": "^17.0.1",
"@types/sinon": "^10.0.2",
"@typescript-eslint/eslint-plugin": "^4.26.1",
"@typescript-eslint/parser": "^4.26.1",
"ava": "^3.15.0",
"browser-env": "^3.3.0",
"eslint": "^7.29.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"global-jsdom": "^8.1.0",
"jsdom": "^16.6.0",
"msw": "^0.33.0",
"node-fetch": "^2.6.1",
"nyc": "^15.1.0",
"prettier": "^2.3.2",
"sinon": "^11.1.1",
Expand Down
8 changes: 7 additions & 1 deletion src/PrismicToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,15 @@ export const PrismicToolbar = ({
React.useEffect(() => {
const script = document.createElement("script");
script.src = `https://static.cdn.prismic.io/prismic.js?repositoryName=${repositoryName}${
type == "new" ? "&type=new" : ""
type === "new" ? "&type=new" : ""
}`;
script.defer = true;

// Used to distinguish the toolbar element from other elements.
script.dataset.prismicToolbar = "";
script.dataset.repositoryName = repositoryName;
script.dataset.type = type;

document.body.appendChild(script);
}, [repositoryName, type]);

Expand Down
2 changes: 1 addition & 1 deletion src/SliceZone.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";
import * as prismicT from "@prismicio/types";

import { __PRODUCTION__ } from "./buildOptimizations";
import { __PRODUCTION__ } from "./lib/__PRODUCTION__";

/**
* The minimum required properties to represent a Prismic Slice for the `<SliceZone>` component.
Expand Down
29 changes: 23 additions & 6 deletions src/createClientHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ const getParamHookDependencies = (
];
};

/**
* Determiens if a value is a `@prismicio/client` params object.
*
* @param value The value to check.
*
* @return `true` if `value` is a `@prismicio/client` params object, `false` otherwise.
*/
const isParams = (
value: unknown,
): value is ClientMethodParameters<"get">[0] & HookOnlyParameters => {
// This is a *very* naive check.
return typeof value === "object" && value !== null && !Array.isArray(value);
};

/**
* Creates a React hook that forwards arguments to a specific method of a `@prismicio/client` instance. The created hook has its own internal state manager to report async status, such as pending or error statuses.
*
Expand All @@ -100,10 +114,13 @@ export const createClientHook = <
"state" | "error"
>,
] => {
const params:
| (ClientMethodParameters<"get">[0] & HookOnlyParameters)
| undefined = args[args.length - 1];
const client = usePrismicClient(params?.client);
const lastArg = args[args.length - 1];
const { client: explicitClient, ...params } = isParams(lastArg)
? lastArg
: ({} as HookOnlyParameters);
const argsWithoutParams = isParams(lastArg) ? args.slice(0, -1) : args;

const client = usePrismicClient(explicitClient);

const [state, dispatch] = React.useReducer<
React.Reducer<
Expand All @@ -116,13 +133,13 @@ export const createClientHook = <
() => {
dispatch(["start"]);
method
.apply(client, args)
.call(client, ...argsWithoutParams, params)
.then((result) => dispatch(["succeed", result]))
.catch((error) => dispatch(["fail", error]));
},
// We must disable exhaustive-deps to optimize providing `params` deps.
// eslint-disable-next-line react-hooks/exhaustive-deps
[client, ...args.slice(-1), ...getParamHookDependencies(params)],
[client, ...args.slice(0, -1), ...getParamHookDependencies(params)],
);

return React.useMemo(
Expand Down
4 changes: 2 additions & 2 deletions src/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const useAllPrismicDocuments = createClientHook<
[params?: ClientMethodParameters<"getAll">[0] & HookOnlyParameters]
>(proto.getAll);

export const usePrismicDocumentsByID = createClientHook<
export const usePrismicDocumentByID = createClientHook<
typeof proto.getByID,
[
id: ClientMethodParameters<"getByID">[0],
Expand All @@ -47,7 +47,7 @@ export const useAllPrismicDocumentsByIDs = createClientHook<
]
>(proto.getAllByIDs);

export const usePrismicDocumentsByUID = createClientHook<
export const usePrismicDocumentByUID = createClientHook<
typeof proto.getByUID,
[
documentType: ClientMethodParameters<"getByUID">[0],
Expand Down
4 changes: 2 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export {
useAllPrismicDocumentsByTags,
useAllPrismicDocumentsByType,
useFirstPrismicDocument,
usePrismicDocumentByID,
usePrismicDocumentByUID,
usePrismicDocuments,
usePrismicDocumentsByID,
usePrismicDocumentsByIDs,
usePrismicDocumentsByTag,
usePrismicDocumentsByTags,
usePrismicDocumentsByType,
usePrismicDocumentsByUID,
useSinglePrismicDocument,
} from "./hooks";
File renamed without changes.
2 changes: 1 addition & 1 deletion src/lib/invariant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
* SOFTWARE.
*/

import { __PRODUCTION__ } from "../buildOptimizations";
import { __PRODUCTION__ } from "./__PRODUCTION__";

const prefix = "Invariant failed";

Expand Down
79 changes: 79 additions & 0 deletions test/PrismicToolbar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import test from "ava";
import * as React from "react";
import browserEnv from "browser-env";

import { renderJSON } from "./__testutils__/renderJSON";
import { md5 } from "./__testutils__/md5";

import { PrismicToolbar } from "../src";

/** Retrieves the toolbar script for a specific repository. */
const getToolbarScript = (repositoryName: string): Element | null => {
return document.body.querySelector(
`[data-prismic-toolbar=""][data-repository-name="${repositoryName}"]`,
);
};

// Polyfill the runtime with a simulated DOM.
test.before(() => {
browserEnv();
});

test.serial(
"adds a script element with the correct attributes to document.body",
(t) => {
const repositoryName = md5(t.title);

renderJSON(<PrismicToolbar repositoryName={repositoryName} type="new" />);

const script = getToolbarScript(repositoryName);

if (script instanceof HTMLScriptElement) {
t.is(
script.getAttribute("src"),
`https://static.cdn.prismic.io/prismic.js?repositoryName=${repositoryName}&type=new`,
);
t.is(script.getAttribute("defer"), "");
t.is(script.dataset.repositoryName, repositoryName);
t.is(script.dataset.type, "new");
} else {
t.fail("The toolbar script element was not found");
}
},
);

test.serial("uses the new toolbar by default", (t) => {
const repositoryName = md5(t.title);

renderJSON(<PrismicToolbar repositoryName={repositoryName} />);

const script = getToolbarScript(repositoryName);

if (script instanceof HTMLScriptElement) {
t.is(
script.getAttribute("src"),
`https://static.cdn.prismic.io/prismic.js?repositoryName=${repositoryName}&type=new`,
);
t.is(script.dataset.type, "new");
} else {
t.fail("The toolbar script element was not found");
}
});

test('uses the legacy toolbar if type is set to "legacy"', (t) => {
const repositoryName = md5(t.title);

renderJSON(<PrismicToolbar repositoryName={repositoryName} type="legacy" />);

const script = getToolbarScript(repositoryName);

if (script instanceof HTMLScriptElement) {
t.is(
script.getAttribute("src"),
`https://static.cdn.prismic.io/prismic.js?repositoryName=${repositoryName}`,
);
t.is(script.dataset.type, "legacy");
} else {
t.fail("The toolbar script element was not found");
}
});
12 changes: 12 additions & 0 deletions test/__testutils__/createClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as ava from "ava";
import * as prismic from "@prismicio/client";
import fetch from "node-fetch";

import { md5 } from "./md5";

export const createClient = (t: ava.ExecutionContext): prismic.Client => {
const endpoint = prismic.getEndpoint(md5(t.title));
const client = prismic.createClient(endpoint, { fetch });

return client;
};
28 changes: 28 additions & 0 deletions test/__testutils__/createDocument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as prismicT from "@prismicio/types";

export const createDocument = <
TDocument extends prismicT.PrismicDocument = prismicT.PrismicDocument,
>(
fields?: Partial<TDocument>,
): TDocument => {
const id = Math.random().toString();
const uid = Math.random().toString();

return {
id,
uid,
type: "type",
href: "href",
tags: ["tag"],
slugs: ["slug"],
lang: "lang",
alternate_languages: [],
first_publication_date: "first_publication_date",
last_publication_date: "last_publication_date",
linked_documents: [],
...fields,
data: {
...fields?.data,
},
} as TDocument;
};
79 changes: 79 additions & 0 deletions test/__testutils__/createMockQueryHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as ava from "ava";
import * as msw from "msw";
import * as prismic from "@prismicio/client";
import * as prismicT from "@prismicio/types";

import { createQueryResponse } from "./createQueryResponse";
import { md5 } from "./md5";

const castArray = <A>(a: A | A[]): A[] => (Array.isArray(a) ? a : [a]);

export const createMockQueryHandler = <
TDocument extends prismicT.PrismicDocument = prismicT.PrismicDocument,
>(
t: ava.ExecutionContext,
pagedResponses: Partial<prismic.Query<TDocument>>[] = [createQueryResponse()],
requiredSearchParams?: Record<
string,
string | number | (string | number)[] | undefined
>,
debug = true,
): msw.RestHandler => {
const repositoryName = md5(t.title);
const endpoint = `https://${repositoryName}.cdn.prismic.io/api/v2/documents/search`;

return msw.rest.get(endpoint, (req, res, ctx) => {
const page = Number.parseInt(req.url.searchParams.get("page") ?? "1");

let requestMatches = true;

if (requiredSearchParams) {
const requiredSearchParamsInstance = new URLSearchParams();
for (const k in requiredSearchParams) {
castArray(
requiredSearchParams[k as keyof typeof requiredSearchParams],
).forEach(
(l) =>
l !== undefined &&
requiredSearchParamsInstance.append(k, l.toString()),
);
}

if (!("page" in requiredSearchParams) && page > 1) {
requiredSearchParamsInstance.append("page", page.toString());
}

// TODO: Remove when the Authorization header can be used
// @see Related issue - {@link https://github.com/prismicio/issue-tracker-wroom/issues/351}
const searchParamsWithoutAccessToken = new URLSearchParams(
req.url.searchParams,
);
searchParamsWithoutAccessToken.delete("access_token");

if (debug) {
t.is(
requiredSearchParamsInstance.toString(),
// TODO: Uncomment when the Authorization header can be used
// @see Related issue - {@link https://github.com/prismicio/issue-tracker-wroom/issues/351}
// req.url.searchParams.toString()
searchParamsWithoutAccessToken.toString(),
);
}

requestMatches =
requiredSearchParamsInstance.toString() ===
// TODO: Uncomment when the Authorization header can be used
// @see Related issue - {@link https://github.com/prismicio/issue-tracker-wroom/issues/351}
// req.url.searchParams.toString()
searchParamsWithoutAccessToken.toString();
}

if (requestMatches) {
const response = pagedResponses[page - 1];

return res(ctx.json(response));
}

return res(ctx.status(404));
});
};
18 changes: 18 additions & 0 deletions test/__testutils__/createMockRepositoryHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as ava from "ava";
import * as msw from "msw";
import * as prismic from "@prismicio/client";

import { createRepositoryResponse } from "./createRepositoryResponse";
import { md5 } from "./md5";

export const createMockRepositoryHandler = (
t: ava.ExecutionContext,
response = createRepositoryResponse(),
): msw.RestHandler => {
const repositoryName = md5(t.title);
const endpoint = prismic.getEndpoint(repositoryName);

return msw.rest.get(endpoint, (_req, res, ctx) => {
return res(ctx.json(response));
});
};
Loading

0 comments on commit 530be7d

Please sign in to comment.