Back to Repositories

Testing Debug Compatibility Features in PreactJS

This test suite validates the compatibility features of Preact’s debug module, focusing on portal rendering and PropTypes validation. The tests ensure proper integration with development tools and maintain compatibility with React-like prop-type checking.

Test Coverage Overview

The test suite covers critical compatibility aspects of Preact’s debug functionality.

  • Portal rendering validation
  • PropTypes integration testing
  • Forward ref implementation verification
  • Error and warning handling scenarios

Implementation Analysis

The testing approach utilizes Jest’s describe/it pattern with custom setup and teardown helpers. The implementation focuses on isolating component behavior through mocked console outputs and controlled DOM environments.

  • Mock console error and warning tracking
  • Isolated component rendering
  • Reference forwarding validation

Technical Details

  • Jest test framework
  • Sinon for stub/mock functionality
  • Custom scratch element setup
  • PropTypes validation library
  • Preact debug and compat modules
  • DOM manipulation utilities

Best Practices Demonstrated

The test suite exemplifies robust testing practices for component libraries.

  • Proper test isolation and cleanup
  • Comprehensive error capture
  • Edge case handling
  • Clear test case organization
  • Effective use of beforeEach/afterEach hooks

preactjs/preact

debug/test/browser/debug-compat.test.js

            
import { createElement, render, createRef } from 'preact';
import { setupScratch, teardown } from '../../../test/_util/helpers';
import './fakeDevTools';
import 'preact/debug';
import * as PropTypes from 'prop-types';

// eslint-disable-next-line no-duplicate-imports
import { resetPropWarnings } from 'preact/debug';
import { forwardRef, createPortal } from 'preact/compat';

const h = createElement;
/** @jsx createElement */

describe('debug compat', () => {
	let scratch;
	let root;
	let errors = [];
	let warnings = [];

	beforeEach(() => {
		errors = [];
		warnings = [];
		scratch = setupScratch();
		sinon.stub(console, 'error').callsFake(e => errors.push(e));
		sinon.stub(console, 'warn').callsFake(w => warnings.push(w));

		root = document.createElement('div');
		document.body.appendChild(root);
	});

	afterEach(() => {
		/** @type {*} */
		console.error.restore();
		console.warn.restore();
		teardown(scratch);

		document.body.removeChild(root);
	});

	describe('portals', () => {
		it('should not throw an invalid render argument for a portal.', () => {
			function Foo(props) {
				return <div>{createPortal(props.children, root)}</div>;
			}
			expect(() => render(<Foo>foobar</Foo>, scratch)).not.to.throw();
		});
	});

	describe('PropTypes', () => {
		beforeEach(() => {
			resetPropWarnings();
		});

		it('should not fail if ref is passed to comp wrapped in forwardRef', () => {
			// This test ensures compat with airbnb/prop-types-exact, mui exact prop types util, etc.

			const Foo = forwardRef(function Foo(props, ref) {
				return <h1 ref={ref}>{props.text}</h1>;
			});

			Foo.propTypes = {
				text: PropTypes.string.isRequired,
				ref(props) {
					if ('ref' in props) {
						throw new Error(
							'ref should not be passed to prop-types valiation!'
						);
					}
				}
			};

			const ref = createRef();

			render(<Foo ref={ref} text="123" />, scratch);

			expect(console.error).not.been.called;

			expect(ref.current).to.not.be.undefined;
		});
	});
});