diff --git a/src/__tests__/__snapshots__/tests.ts.snap b/src/__tests__/__snapshots__/tests.ts.snap index 371885a..cd4d07f 100644 --- a/src/__tests__/__snapshots__/tests.ts.snap +++ b/src/__tests__/__snapshots__/tests.ts.snap @@ -1,26 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`reactFromMarkupContainer E2E tests Should rehydrate a basic component 1`] = ` -" -
- rehydrated component -
" -`; - -exports[`reactFromMarkupContainer E2E tests Should rehydrate valid HTML markup 1`] = ` -" -
-

paragraph

-
" -`; +exports[`reactFromHtml E2E tests Should rehydrate a basic component 1`] = `"
rehydrated component
"`; -exports[`reactFromMarkupContainer E2E tests Should work for nested markup containers 1`] = ` +exports[`reactFromHtml E2E tests Should work for nested rehydratables 1`] = ` " -
- rehydrated component -
- rehydrated component - rehydrated component -
-
" +
+
+ Hello, World! +
+
+ " `; diff --git a/src/__tests__/tests.ts b/src/__tests__/tests.ts index b5f39a6..dad62c6 100644 --- a/src/__tests__/tests.ts +++ b/src/__tests__/tests.ts @@ -1,8 +1,8 @@ /* eslint-env jest */ import * as React from "react"; -import reactFromMarkupContainer from ".."; +import reactFromHtml from ".."; -describe("reactFromMarkupContainer E2E tests", async () => { +describe("reactFromHtml E2E tests", async () => { it("Should rehydrate a basic component", async () => { const componentName: string = "myComponent"; @@ -13,63 +13,46 @@ describe("reactFromMarkupContainer E2E tests", async () => { const rehydrators = { [componentName]: rehydrator }; const documentElement = document.createElement("div"); - documentElement.innerHTML = ` -
-
-
`; + documentElement.innerHTML = `
`; - await reactFromMarkupContainer(documentElement, rehydrators, { + await reactFromHtml(documentElement, rehydrators, { extra: {}, }); expect(documentElement.innerHTML).toMatchSnapshot(); }); - it("Should rehydrate valid HTML markup", async () => { - const documentElement = document.createElement("div"); - - documentElement.innerHTML = ` -
-

paragraph

-
`; - - await reactFromMarkupContainer(documentElement, {}, { extra: {} }); - - expect(documentElement.innerHTML).toMatchSnapshot(); - }); - - it("Should work for nested markup containers", async () => { + it("Should work for nested rehydratables", async () => { const componentName: string = "mycomponentName"; const mockCall = jest.fn(); const rehydrators = { - [componentName]: async () => { + [componentName]: async (node: HTMLElement) => { mockCall(); - return React.createElement("span", {}, "rehydrated component"); + await reactFromHtml(node, rehydrators, { extra: {} }); + + return React.createElement("span", { + dangerouslySetInnerHTML: { __html: node.innerHTML }, + }); }, }; const documentElement = document.createElement("div"); documentElement.innerHTML = ` -
-
-
-
-
-
-
-
-
-
-
`; - - await reactFromMarkupContainer(documentElement, rehydrators, { +
+
+ Hello, World! +
+
+ `; + + await reactFromHtml(documentElement, rehydrators, { extra: {}, }); expect(documentElement.innerHTML).toMatchSnapshot(); - expect(mockCall).toBeCalledTimes(3); + expect(mockCall).toBeCalledTimes(2); }); }); diff --git a/src/rehydrator.ts b/src/rehydrator.ts index 736d40b..f200d3e 100644 --- a/src/rehydrator.ts +++ b/src/rehydrator.ts @@ -23,7 +23,8 @@ const rehydratableToReactElement = async ( return rehydrator( el, - children => rehydrateChildren(children, rehydrators, options), + async children => + (await rehydrateChildren(children, rehydrators, options)).rehydrated, options.extra ); }; @@ -44,11 +45,34 @@ const createCustomHandler = ( return false; }; -const rehydrateChildren = ( +const createReactRoot = (el: Node) => { + const container = document.createElement("div"); + + if (el.parentNode) { + el.parentNode.replaceChild(container, el); + } + + container.appendChild(el); + container.classList.add("rehydration-root"); + + return container; +}; + +const rehydrateChildren = async ( el: Node, rehydrators: IRehydrator, options: IOptions -) => domElementToReact(el, createCustomHandler(rehydrators, options)); +) => { + const container = createReactRoot(el); + + return { + container, + rehydrated: await domElementToReact( + container, + createCustomHandler(rehydrators, options) + ), + }; +}; const render = ({ rehydrated, @@ -67,14 +91,23 @@ const render = ({ ReactDOM.render(rehydrated as React.ReactElement, root); }; +const createQuerySelector = (rehydratableIds: string[]) => + rehydratableIds.reduce( + (acc: string, rehydratableId: string) => + `${acc ? `${acc}, ` : ""}[data-rehydratable*="${rehydratableId}"]`, + "" + ); + export default async ( container: Element, rehydrators: IRehydrator, options: IOptions ) => { + const selector = createQuerySelector(Object.keys(rehydrators)); + const roots = Array.from( // TODO: allow setting a container identifier so multiple rehydration instances can exist - container.querySelectorAll("[data-react-from-html-container]") + container.querySelectorAll(selector) ).reduce((acc: Element[], root: Element) => { // filter roots that are contained within other roots if (!acc.some(r => r.contains(root))) { @@ -92,13 +125,12 @@ export default async ( if (container.contains(root)) { renders.push(async () => { try { - const rehydrated = await rehydrateChildren( - root, - rehydrators, - options - ); + const { + container: rootContainer, + rehydrated, + } = await rehydrateChildren(root, rehydrators, options); - return { root, rehydrated }; + return { root: rootContainer, rehydrated }; } catch (e) { /* tslint:disable-next-line no-console */ console.error("Rehydration failure", e);