Skip to content

Commit

Permalink
[New] childrenOf/childrenOfType/childrenSequenceOf: support fra…
Browse files Browse the repository at this point in the history
…gments via `renderableChildren` helper

This includes children of fragments as renderable children.

Fixes #71.
  • Loading branch information
knowler authored and ljharb committed Sep 10, 2020
1 parent 6539947 commit 492c439
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"dependencies": {
"array.prototype.find": "^2.1.1",
"array.prototype.flatmap": "^1.2.3",
"function.prototype.name": "^1.1.2",
"is-regex": "^1.1.1",
"object-is": "^1.1.2",
Expand Down
9 changes: 8 additions & 1 deletion src/helpers/renderableChildren.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import React from 'react';
import { isFragment } from 'react-is';
import flatMap from 'array.prototype.flatmap';

export default function renderableChildren(childrenProp) {
return React.Children.toArray(childrenProp).filter((child) => child === 0 || child);
return flatMap(React.Children.toArray(childrenProp), (child) => {
if (isFragment(child)) {
return renderableChildren(child.props.children);
}
return child === 0 || child ? child : [];
});
}
60 changes: 60 additions & 0 deletions test/childrenOf.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import callValidator from './_callValidator';
function SFC() {}
class Component extends React.Component {} // eslint-disable-line react/prefer-stateless-function

const describeIfFragments = React.Fragment ? describe : describe.skip;

describe('childrenOf', () => {
function assertPasses(validator, element, propName, componentName) {
expect(callValidator(validator, element, propName, componentName)).to.equal(null);
Expand Down Expand Up @@ -298,4 +300,62 @@ describe('childrenOf', () => {
'Component!',
));
});

describeIfFragments('it passes through fragments to validates its children instead', () => {
it('fails when an empty fragment is provided, but children are required', () => assertFails(
childrenOf(node).isRequired,
(
<div><></></div>
),
'children',
));

it('passes when an empty fragment is provided and children are optional', () => assertPasses(
childrenOf(node),
(
<div><></></div>
),
'children',
));

it('passes when a valid child is provided within a fragment', () => assertPasses(
childrenOf(number),
(
<div>
<>
{0}
</>
</div>
),
'children',
'number',
));

it('fails when a invalid child is provided within a fragment', () => assertFails(
childrenOf(number),
(
<div>
<>
<span />
</>
</div>
),
'children',
'number!',
));

it('passes when fragments are mixed with other nodes', () => assertPasses(
childrenOf(node),
(
<div>
<span />
<>
<span />
</>
</div>
),
'children',
'node!',
));
});
});
170 changes: 170 additions & 0 deletions test/childrenOfType.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import callValidator from './_callValidator';
function SFC() {}
class Component extends React.Component {} // eslint-disable-line react/prefer-stateless-function

const describeIfFragments = React.Fragment ? describe : describe.skip;

describe('childrenOfType', () => {
it('throws when not given a type', () => {
expect(() => childrenOfType()).to.throw(TypeError);
Expand Down Expand Up @@ -350,6 +352,174 @@ describe('childrenOfType', () => {
));
});

describeIfFragments('with children of the specified types passed as a React fragment', () => {
it('passes with *', () => assertPasses(
childrenOfType('*').isRequired,
(
<div>
<>
<span key="one" />
<span key="two" />
<span key="three" />
</>
</div>
),
'children',
'*!',
));

it('passes with *', () => assertPasses(
childrenOfType('*').isRequired,
(
<div>
<>
<span key="one" />
<span key="two" />
<span key="three" />
</>
</div>
),
'children',
'*! required',
));

it('passes with a DOM element', () => assertPasses(
childrenOfType('span'),
(
<div>
<>
<span key="one" />
<span key="two" />
<span key="three" />
</>
</div>
),
'children',
'span!',
));

it('passes with an SFC', () => assertPasses(
childrenOfType(SFC),
(
<div>
<>
<SFC key="one" default="Foo" />
<SFC key="two" default="Foo" />
<SFC key="three" default="Foo" />
</>
</div>
),
'children',
'SFC!',
));

it('passes with a Component', () => assertPasses(
childrenOfType(Component),
(
<div>
<>
<Component key="one" default="Foo" />
<Component key="two" default="Foo" />
<Component key="three" default="Foo" />
</>
</div>
),
'children',
'Component!',
));

it('passes with multiple types', () => assertPasses(
childrenOfType(SFC, Component, 'span'),
(
<div>
<>
<span key="one" default="Foo" />
<Component key="two" default="Foo" />
<SFC key="three" default="Foo" />
</>
</div>
),
'children',
'all three',
));

it('passes with multiple types when required', () => assertPasses(
childrenOfType(SFC, Component, 'span').isRequired,
(
<div>
<>
<span key="one" default="Foo" />
<Component key="two" default="Foo" />
<SFC key="three" default="Foo" />
</>
</div>
),
'children',
'all three required',
));

it('passes with multiple types including *', () => assertPasses(
childrenOfType(SFC, '*'),
(
<div>
<>
<span key="one" default="Foo" />
<Component key="two" default="Foo" />
<SFC key="three" default="Foo" />
text children
</>
</div>
),
'children',
'SFC and *',
));

it('passes with multiple types including * when required', () => assertPasses(
childrenOfType(SFC, '*').isRequired,
(
<div>
<>
<span key="one" default="Foo" />
<Component key="two" default="Foo" />
<SFC key="three" default="Foo" />
text children
</>
</div>
),
'children',
'SFC and *',
));

it('passes with a fragment of specified types and a specified type', () => assertPasses(
childrenOfType(SFC, 'span'),
(
<div>
<>
<SFC key="one" default="Foo" />
<span key="two" default="Foo" />
</>
<span />
</div>
),
'children',
'SFC and span',
));

it('fails when the unspecified type is within a fragment', () => assertFails(
childrenOfType('span'),
(
<div>
<>
<SFC key="one" default="Foo" />
<Component key="two" default="Foo" />
</>
</div>
),
'children',
'span!',
));
});

describe('when an unspecified type is provided as a child', () => {
it('fails expecting a DOM element', () => assertFails(
childrenOfType('span'),
Expand Down
68 changes: 68 additions & 0 deletions test/childrenSequenceOf.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { childrenSequenceOf } from '..';

import callValidator from './_callValidator';

const describeIfFragments = React.Fragment ? describe : describe.skip;

describe('childrenSequenceOf', () => {
it('is a function', () => {
expect(typeof childrenSequenceOf).to.equal('function');
Expand Down Expand Up @@ -322,4 +324,70 @@ describe('childrenSequenceOf', () => {
'children',
);
});

describeIfFragments('it passes through fragments to validates its children instead', () => {
it('passes when part of the sequence is in a fragment', () => assertPasses(
childrenSequenceOf({ validator: number, min: 2 }),
(
<div>
{1}
<>{2}</>
</div>
),
'children',
));

it('fails when there is an invalid child in a fragment', () => assertFails(
childrenSequenceOf({ validator: number, min: 2 }),
(
<div>
{1}
<>2</>
</div>
),
'children',
));

it('passes when the entire sequence is in a fragment', () => assertPasses(
childrenSequenceOf({ validator: number, min: 2 }),
(
<div>
<>
{1}
{2}
{3}
</>
</div>
),
'children',
));

it('passes with a complex sequence in a fragment', () => assertPasses(
childrenSequenceOf({ validator: number }, { validator: string }, { validator: number }),
(
<div>
<>
{1}
2
{3}
</>
</div>
),
'children',
));

it('fails with an invalid sequence in a fragment', () => assertFails(
childrenSequenceOf({ validator: number }, { validator: string }, { validator: number }),
(
<div>
<>
{1}
{2}
3
</>
</div>
),
'children',
));
});
});

0 comments on commit 492c439

Please sign in to comment.