Back to Repositories

Testing Children Utility Implementation in preactjs/preact

This test suite validates the implementation of React’s Children utilities in Preact’s compatibility layer. It ensures proper handling of child components and elements through methods like count, only, map, and forEach.

Test Coverage Overview

The test suite provides comprehensive coverage of React’s Children API implementation:

  • Children.count() validation for empty and populated children
  • Children.only() enforcement of single child requirement
  • Children.map() iteration and transformation capabilities
  • Children.forEach() traversal functionality

Implementation Analysis

The testing approach utilizes Jest’s describe/it blocks for organized test grouping. Each Children method is tested in isolation with specific test cases that verify both standard usage and edge cases. The implementation leverages Preact’s compatibility layer to ensure React-like behavior.

Technical Details

  • Testing Framework: Jest
  • Helper Utilities: setupScratch, teardown, serializeHtml
  • DOM Utilities: div, span helpers
  • Component Testing: Functional components with Children API usage

Best Practices Demonstrated

The test suite exhibits strong testing practices through:

  • Proper test isolation with beforeEach/afterEach hooks
  • Comprehensive edge case coverage
  • Clear test case organization
  • Consistent assertion patterns
  • Effective use of helper utilities

preactjs/preact

compat/test/browser/Children.test.js

            
import {
	setupScratch,
	teardown,
	serializeHtml
} from '../../../test/_util/helpers';
import { div, span } from '../../../test/_util/dom';
import React, { createElement, Children, render } from 'preact/compat';

describe('Children', () => {
	/** @type {HTMLDivElement} */
	let scratch;

	beforeEach(() => {
		scratch = setupScratch();
	});

	afterEach(() => {
		teardown(scratch);
	});

	describe('.count', () => {
		let count;
		function Foo(props) {
			count = Children.count(props.children);
			return <div>{count}</div>;
		}

		it('should return 0 for no children', () => {
			render(<Foo />, scratch);
			expect(count).to.equal(0);
		});

		it('should return number of children', () => {
			render(
				<Foo>
					<div />
					foo
				</Foo>,
				scratch
			);
			expect(count).to.equal(2);
		});
	});

	describe('.only', () => {
		let actual;
		function Foo(props) {
			actual = Children.only(props.children);
			return <div>{actual}</div>;
		}

		it('should only allow 1 child', () => {
			render(<Foo>foo</Foo>, scratch);
			expect(actual).to.equal('foo');
		});

		it('should throw if no children are passed', () => {
			// eslint-disable-next-line prefer-arrow-callback
			expect(function () {
				render(<Foo />, scratch);
			}).to.throw();
		});

		it('should throw if more children are passed', () => {
			// eslint-disable-next-line prefer-arrow-callback
			expect(function () {
				render(
					<Foo>
						foo
						<span />
					</Foo>,
					scratch
				);
			}).to.throw();
		});
	});

	describe('.map', () => {
		function Foo(props) {
			let children = Children.map(props.children, child => (
				<span>{child}</span>
			));
			return <div>{children}</div>;
		}

		it('should iterate over children', () => {
			render(
				<Foo>
					foo<div>bar</div>
				</Foo>,
				scratch
			);
			let expected = div([span('foo'), span(div('bar'))]);
			expect(serializeHtml(scratch)).to.equal(expected);
		});

		it('should work with no children', () => {
			render(<Foo />, scratch);
			expect(serializeHtml(scratch)).to.equal('<div></div>');
		});

		it('should work with children as zero number', () => {
			const testNumber = 0;

			render(<Foo>{testNumber}</Foo>, scratch);
			expect(serializeHtml(scratch)).to.equal('<div><span>0</span></div>');
		});

		it('should flatten result', () => {
			const ProblemChild = ({ children }) => {
				return React.Children.map(children, child => {
					return React.Children.map(child.props.children, x => x);
				}).filter(React.isValidElement);
			};

			const App = () => {
				return (
					<ProblemChild>
						<div>
							<div>1</div>
							<div>2</div>
						</div>
					</ProblemChild>
				);
			};

			render(<App />, scratch);

			expect(scratch.textContent).to.equal('12');
		});

		it('should call with indices', () => {
			const assertion = [];
			const ProblemChild = ({ children }) => {
				return React.Children.map(children, (child, i) => {
					assertion.push(i);
					return React.Children.map(child.props.children, (x, j) => {
						assertion.push(j);
						return x;
					});
				}).filter(React.isValidElement);
			};

			const App = () => {
				return (
					<ProblemChild>
						<div>
							<div>1</div>
							<div>2</div>
						</div>
						<div>
							<div>3</div>
							<div>4</div>
						</div>
					</ProblemChild>
				);
			};

			render(<App />, scratch);
			expect(scratch.textContent).to.equal('1234');
			expect(assertion.length).to.equal(6);
		});
	});

	describe('.forEach', () => {
		function Foo(props) {
			let children = [];
			Children.forEach(props.children, child =>
				children.push(<span>{child}</span>)
			);
			return <div>{children}</div>;
		}

		it('should iterate over children', () => {
			render(
				<Foo>
					foo<div>bar</div>
				</Foo>,
				scratch
			);
			let expected = div([span('foo'), span(div('bar'))]);
			expect(serializeHtml(scratch)).to.equal(expected);
		});
	});
});