Back to Repositories

Testing Component Unmount Lifecycle Implementation in Preact

This test suite examines the componentWillUnmount lifecycle method implementation in Preact components. It verifies proper component unmounting behavior and DOM cleanup for top-level components, ensuring reliable component lifecycle management.

Test Coverage Overview

The test suite provides comprehensive coverage of componentWillUnmount lifecycle behavior in Preact components.

Key areas tested include:
  • Top-level component unmounting sequences
  • Component replacement scenarios
  • DOM element removal timing
  • Multiple component lifecycle method interactions

Implementation Analysis

The testing approach uses Jest and Sinon for spy-based verification of lifecycle method execution. It implements multiple test scenarios using class-based components with defined lifecycle methods, focusing on unmounting behavior during component replacement and removal.

Technical patterns include:
  • Spy-based method call verification
  • Component state assertions
  • DOM element presence validation

Technical Details

Testing tools and setup:
  • Jest as the primary test framework
  • Sinon for method spying and assertions
  • Custom scratch element setup and teardown helpers
  • JSX with createElement implementation
  • Component class inheritance structure

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through isolated component testing and comprehensive lifecycle verification.

Notable practices include:
  • Clean test setup and teardown
  • Isolated component testing
  • Explicit assertion messaging
  • Comprehensive edge case coverage
  • Clear test case organization

preactjs/preact

test/browser/lifecycles/componentWillUnmount.test.js

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

/** @jsx createElement */

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

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

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

	describe('top-level componentWillUnmount', () => {
		it('should invoke componentWillUnmount for top-level components', () => {
			class Foo extends Component {
				componentDidMount() {}
				componentWillUnmount() {}
				render() {
					return 'foo';
				}
			}
			class Bar extends Component {
				componentDidMount() {}
				componentWillUnmount() {}
				render() {
					return 'bar';
				}
			}
			sinon.spy(Foo.prototype, 'componentDidMount');
			sinon.spy(Foo.prototype, 'componentWillUnmount');
			sinon.spy(Foo.prototype, 'render');

			sinon.spy(Bar.prototype, 'componentDidMount');
			sinon.spy(Bar.prototype, 'componentWillUnmount');
			sinon.spy(Bar.prototype, 'render');

			render(<Foo />, scratch);
			expect(Foo.prototype.componentDidMount, 'initial render').to.have.been
				.calledOnce;

			render(<Bar />, scratch);
			expect(Foo.prototype.componentWillUnmount, 'when replaced').to.have.been
				.calledOnce;
			expect(Bar.prototype.componentDidMount, 'when replaced').to.have.been
				.calledOnce;

			render(<div />, scratch);
			expect(Bar.prototype.componentWillUnmount, 'when removed').to.have.been
				.calledOnce;
		});

		it('should only remove dom after componentWillUnmount was called', () => {
			class Foo extends Component {
				componentWillUnmount() {
					expect(document.getElementById('foo')).to.not.equal(null);
				}

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

			render(<Foo />, scratch);
			render(null, scratch);
		});
	});
});