Skip to content

Commit

Permalink
Merge pull request #21 from grrowl/react-19
Browse files Browse the repository at this point in the history
React 19 support
  • Loading branch information
grrowl authored Jan 19, 2025
2 parents 88bdb6d + 0c00140 commit 636a158
Show file tree
Hide file tree
Showing 4 changed files with 1,854 additions and 656 deletions.
131 changes: 54 additions & 77 deletions index.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,49 @@
import test from "tape";
import flattenChildren from "./index";
import TestRenderer, { ReactTestRendererTree } from "react-test-renderer";
import { cleanup, render } from "@testing-library/react";
import "jsdom-global/register";
import React, { Fragment, FunctionComponent, ReactNode } from "react";
import { isElement } from "react-is";
import test from "tape";
import flattenChildren from "./index";

const Assert: FunctionComponent<{
assert: (result: ReturnType<typeof flattenChildren>) => void;
children: ReactNode
}> = (props: any) => {
children: ReactNode;
}> = (props) => {
const result = flattenChildren(props.children);
props.assert(result);
return <div>{result}</div>;
return (
<div data-testid="assert-container">
{React.Children.map(result, (child) =>
React.isValidElement(child)
? React.cloneElement(child, {
"data-reactkey": child.key,
} as React.HTMLAttributes<HTMLElement>)
: child
)}
</div>
);
};

function getRenderedChildren(rendererTree: ReactTestRendererTree | null) {
if (!rendererTree || !rendererTree.rendered) throw new Error("No render");

// if rendered is an array, return the array of children from each tree
if (Array.isArray(rendererTree.rendered)) {
return rendererTree.rendered.reduce((acc: Array<any>, tree: ReactTestRendererTree) => {
if (tree.props && tree.props.children) {
return acc.concat(tree.props.children);
} else {
throw new Error("No rendered props.children in one of the trees");
}
}, []);
}

// if rendered is a single tree
if (!rendererTree.rendered.props || !rendererTree.rendered.props.children)
throw new Error("No rendered props.children");


return rendererTree.rendered.props.children as Array<any>;
function getRenderedChildren(container: HTMLElement) {
const assertContainer = container.querySelector(
'[data-testid="assert-container"]'
);
if (!assertContainer) throw new Error("No assert container found");
return Array.from(assertContainer.children);
}

test("simple children", function(t) {
test("simple children", function (t) {
t.plan(5);

TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
// this inner function tests the return value of flattenChildren
t.equal(result.length, 4, "array length");

t.equal(
isElement(result[0]) && result[0].key,
".0",
"0th element key"
);
t.equal(isElement(result[0]) && result[0].key, ".0", "0th element key");
t.equal(result[1], "two", "1st text child");
t.equal(
isElement(result[2]) && result[2].key,
".2",
"2nd element key"
);
t.equal(isElement(result[2]) && result[2].key, ".2", "2nd element key");
t.equal(result[3], "10", "3rd number child");
}}
>
Expand All @@ -67,29 +56,19 @@ test("simple children", function(t) {
);
});

test("conditional children", function(t) {
test.onFinish(cleanup);

test("conditional children", function (t) {
t.plan(4);

TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
t.equal(result.length, 3, "array length");

t.equal(
isElement(result[0]) && result[0].key,
".0",
"0th element key"
);
t.equal(
isElement(result[1]) && result[1].key,
".2",
"2nd element key"
);
t.equal(
isElement(result[2]) && result[2].key,
".4",
"4th element key"
);
t.equal(isElement(result[0]) && result[0].key, ".0", "0th element key");
t.equal(isElement(result[1]) && result[1].key, ".2", "2nd element key");
t.equal(isElement(result[2]) && result[2].key, ".4", "4th element key");
}}
>
<span>one</span>
Expand All @@ -101,12 +80,12 @@ test("conditional children", function(t) {
);
});

test("keyed children", function(t) {
test("keyed children", function (t) {
t.plan(2);

TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
t.equal(result.length, 5, "array length");
t.deepEqual(
result.map((c: any) => c.key),
Expand All @@ -124,12 +103,12 @@ test("keyed children", function(t) {
);
});

test("fragment children", function(t) {
test("fragment children", function (t) {
t.plan(2);

TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
t.equal(result.length, 3, "array length");
t.deepEqual(
result.map((c: any) => c.key),
Expand All @@ -149,12 +128,12 @@ test("fragment children", function(t) {
);
});

test("keyed fragment children", function(t) {
test("keyed fragment children", function (t) {
t.plan(2);

TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
t.equal(result.length, 3, "array length");
t.deepEqual(
result.map((c: any) => c.key),
Expand All @@ -174,12 +153,12 @@ test("keyed fragment children", function(t) {
);
});

test("array children", function(t) {
test("array children", function (t) {
t.plan(2);

TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
t.equal(result.length, 5, "array length");
t.deepEqual(
result.map((c: any) => c.key),
Expand All @@ -195,12 +174,12 @@ test("array children", function(t) {
);
});

test("renders through to react", function(t) {
test("renders through to react", function (t) {
t.plan(3);

const result = TestRenderer.create(
const { container } = render(
<Assert
assert={result => {
assert={(result) => {
t.equal(result.length, 6, "array length");
}}
>
Expand All @@ -216,15 +195,13 @@ test("renders through to react", function(t) {
</Fragment>
<span>foot</span>
</Assert>
).toTree();
);

// and this tests that the array returned by flattenChildren will be rendered
// correctly by React
const children = getRenderedChildren(result);
const children = getRenderedChildren(container);

t.equal(children.length, 6, "props.children.length");
t.deepEqual(
children.map((c: any) => c.key),
Array.from(children).map((child) => child.getAttribute("data-reactkey")),
[".0", ".$apple..$one", ".$apple..$two", ".2", ".$banana..$three", ".5"],
"element keys"
);
Expand Down
20 changes: 13 additions & 7 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
/* Returns React children into an array, flattening fragments. */
import {
ReactNode,
ReactChild,
Children,
ReactElement,
ReactNode,
cloneElement,
isValidElement,
cloneElement
} from "react";
import { isFragment } from "react-is";

function isFragmentWithChildren(
node: unknown
): node is ReactElement<{ children: ReactNode }> {
return isFragment(node);
}

export default function flattenChildren(
children: ReactNode,
depth: number = 0,
keys: (string | number)[] = []
): ReactChild[] {
): ReactNode[] {
return Children.toArray(children).reduce(
(acc: ReactChild[], node, nodeIndex) => {
if (isFragment(node)) {
(acc: ReactNode[], node, nodeIndex) => {
if (isFragmentWithChildren(node)) {
acc.push.apply(
acc,
flattenChildren(
Expand All @@ -28,7 +34,7 @@ export default function flattenChildren(
if (isValidElement(node)) {
acc.push(
cloneElement(node, {
key: keys.concat(String(node.key)).join('.')
key: keys.concat(String(node.key)).join("."),
})
);
} else if (typeof node === "string" || typeof node === "number") {
Expand Down
Loading

0 comments on commit 636a158

Please sign in to comment.