Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React 19 support #21

Merged
merged 5 commits into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading