Back to Repositories

Testing componentWillUpdate Lifecycle Implementation in preactjs/preact

This test suite examines the componentWillUpdate lifecycle method in Preact components. It validates the method’s behavior during component initialization, prop updates, and state changes through comprehensive unit tests.

Test Coverage Overview

The test suite provides thorough coverage of componentWillUpdate lifecycle scenarios:

  • Initial render behavior verification
  • Props update triggering from parent components
  • State change response validation
  • Edge case handling for component updates

Implementation Analysis

The testing approach utilizes Jest and Sinon for spy functionality to track method calls. It implements nested component structures to test parent-child prop propagation and employs controlled state updates through componentDidMount callbacks.

Key patterns include component class inheritance, state management, and proper teardown procedures.

Technical Details

Testing tools and setup:

  • Jest as the primary testing framework
  • Sinon for method spying and assertions
  • Custom scratch element setup for DOM rendering
  • Preact test-utils for rerender functionality
  • Dedicated setup and teardown routines

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases with clear assertions
  • Proper cleanup between tests
  • Controlled component state management
  • Comprehensive lifecycle method verification
  • Explicit expectations for props and state changes

preactjs/preact

test/browser/lifecycles/componentWillUpdate.test.js

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

/** @jsx createElement */

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

	/** @type {() => void} */
	let rerender;

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

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

	describe('#componentWillUpdate', () => {
		it('should NOT be called on initial render', () => {
			class ReceivePropsComponent extends Component {
				componentWillUpdate() {}
				render() {
					return <div />;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have
				.been.called;
		});

		it('should be called when rerender with new props from parent', () => {
			let doRender;
			class Outer extends Component {
				constructor(p, c) {
					super(p, c);
					this.state = { i: 0 };
				}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render(props, { i }) {
					return <Inner i={i} {...props} />;
				}
			}
			class Inner extends Component {
				componentWillUpdate(nextProps, nextState) {
					expect(nextProps).to.be.deep.equal({ i: 1 });
					expect(nextState).to.be.deep.equal({});
				}
				render() {
					return <div />;
				}
			}
			sinon.spy(Inner.prototype, 'componentWillUpdate');
			sinon.spy(Outer.prototype, 'componentDidMount');

			// Initial render
			render(<Outer />, scratch);
			expect(Inner.prototype.componentWillUpdate).not.to.have.been.called;

			// Rerender inner with new props
			doRender();
			rerender();
			expect(Inner.prototype.componentWillUpdate).to.have.been.called;
		});

		it('should be called on new state', () => {
			let doRender;
			class ReceivePropsComponent extends Component {
				componentWillUpdate() {}
				componentDidMount() {
					doRender = () => this.setState({ i: this.state.i + 1 });
				}
				render() {
					return <div />;
				}
			}
			sinon.spy(ReceivePropsComponent.prototype, 'componentWillUpdate');
			render(<ReceivePropsComponent />, scratch);
			expect(ReceivePropsComponent.prototype.componentWillUpdate).not.to.have
				.been.called;

			doRender();
			rerender();
			expect(ReceivePropsComponent.prototype.componentWillUpdate).to.have.been
				.called;
		});
	});
});