Back to Repositories

Testing Component Stack Trace Generation in Preact

This test suite validates the component stack trace functionality in Preact’s debug mode, ensuring proper error tracking and component hierarchy visualization during development. It verifies how Preact handles and displays component stack traces when errors occur in the component lifecycle.

Test Coverage Overview

The test suite provides comprehensive coverage of Preact’s component stack trace functionality.

Key areas tested include:
  • Basic component stack printing for nested components
  • Stack trace generation for component construction errors
  • Proper owner-based component hierarchy display
  • Validation of table element rendering errors
  • Babel plugin integration verification

Implementation Analysis

The testing approach utilizes Jest’s describe/it pattern with setup and teardown hooks for consistent test environments. The implementation leverages sinon for console method stubbing and custom helper functions for DOM manipulation.

Notable patterns include:
  • Mock console error/warning capture
  • Component hierarchy testing through nested renders
  • Stack trace parsing and validation
  • Error boundary testing scenarios

Technical Details

Testing tools and configuration:
  • Jest test framework
  • Sinon for method stubbing
  • Custom scratch element setup
  • Preact debug mode integration
  • Babel JSX transformation
  • Custom helper utilities for DOM setup/teardown

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices for component debugging.

Notable practices include:
  • Isolated test environments with proper cleanup
  • Comprehensive error scenario coverage
  • Consistent setup/teardown patterns
  • Error message validation
  • Component hierarchy verification
  • Proper assertion patterns

preactjs/preact

debug/test/browser/component-stack.test.js

            
import { createElement, render, Component } from 'preact';
import 'preact/debug';
import { setupScratch, teardown } from '../../../test/_util/helpers';

/** @jsx createElement */

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

	let errors = [];
	let warnings = [];

	const getStack = arr => arr[0].split('\n\n')[1];

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

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

	afterEach(() => {
		console.error.restore();
		console.warn.restore();
		teardown(scratch);
	});

	it('should print component stack', () => {
		function Foo() {
			return <Thrower />;
		}

		class Thrower extends Component {
			constructor(props) {
				super(props);
				this.setState({ foo: 1 });
			}

			render() {
				return <div>foo</div>;
			}
		}

		render(<Foo />, scratch);

		let lines = getStack(warnings).split('\n');
		expect(lines[0].indexOf('Thrower') > -1).to.equal(true);
		expect(lines[1].indexOf('Foo') > -1).to.equal(true);
	});

	it('should only print owners', () => {
		function Foo(props) {
			return <div>{props.children}</div>;
		}

		function Bar() {
			return (
				<Foo>
					<Thrower />
				</Foo>
			);
		}

		class Thrower extends Component {
			render() {
				return (
					<table>
						<td>
							<tr>foo</tr>
						</td>
					</table>
				);
			}
		}

		render(<Bar />, scratch);

		let lines = getStack(errors).split('\n');
		expect(lines[0].indexOf('tr') > -1).to.equal(true);
		expect(lines[1].indexOf('Thrower') > -1).to.equal(true);
		expect(lines[2].indexOf('Bar') > -1).to.equal(true);
	});

	it('should not print a warning when "@babel/plugin-transform-react-jsx-source" is installed', () => {
		function Thrower() {
			throw new Error('foo');
		}

		try {
			render(<Thrower />, scratch);
		} catch {}

		expect(warnings.join(' ')).to.not.include(
			'@babel/plugin-transform-react-jsx-source'
		);
	});
});