Back to Repositories

Testing Component Lifecycle Options in Preact Compatibility Layer

This test suite validates the compatibility options in Preact, focusing on component lifecycle events and event handling. It ensures proper integration between Preact’s compatibility layer and React-like functionality through comprehensive mount, update, and unmount testing.

Test Coverage Overview

The test suite provides thorough coverage of Preact’s compatibility options, particularly focusing on component lifecycle events.

Key areas tested include:
  • Component mounting behavior and initial render
  • Event handling and state updates
  • Component unmounting and cleanup
  • Reference handling via createRef

Implementation Analysis

The testing approach utilizes Jest’s describe/it pattern combined with Preact’s test utilities. It implements a ClassApp component that maintains count state and handles click events, demonstrating both class component patterns and event handling.

Technical implementation features:
  • Spy utilities for tracking vnode and event operations
  • Component lifecycle testing
  • Event dispatch simulation
  • Reference management testing

Technical Details

Testing infrastructure includes:
  • Jest as the testing framework
  • Preact/test-utils for rerender functionality
  • Custom helpers for scratch element management
  • Spy utilities for monitoring internal operations
  • Event simulation utilities

Best Practices Demonstrated

The test suite exemplifies several testing best practices in React-like environments.

Notable practices include:
  • Proper test setup and teardown
  • Isolation of test cases
  • Comprehensive lifecycle coverage
  • Clear test case organization
  • Effective use of ref handling
  • State management testing

preactjs/preact

compat/test/browser/compat.options.test.js

            
import { vnodeSpy, eventSpy } from '../../../test/_util/optionSpies';
import React, {
	createElement,
	render,
	Component,
	createRef
} from 'preact/compat';
import { setupRerender } from 'preact/test-utils';
import {
	setupScratch,
	teardown,
	createEvent
} from '../../../test/_util/helpers';

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

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

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

	/** @type {import('../../src/index').PropRef<HTMLButtonElement | null>} */
	let buttonRef;

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

		vnodeSpy.resetHistory();
		eventSpy.resetHistory();

		buttonRef = createRef();
	});

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

	class ClassApp extends Component {
		constructor() {
			super();
			this.state = { count: 0 };
			increment = () =>
				this.setState(({ count }) => ({
					count: count + 1
				}));
		}

		render() {
			return (
				<button ref={buttonRef} onClick={increment}>
					{this.state.count}
				</button>
			);
		}
	}

	it('should call old options on mount', () => {
		render(<ClassApp />, scratch);

		expect(vnodeSpy).to.have.been.called;
	});

	it('should call old options on event and update', () => {
		render(<ClassApp />, scratch);
		expect(scratch.innerHTML).to.equal('<button>0</button>');

		buttonRef.current.dispatchEvent(createEvent('click'));
		rerender();
		expect(scratch.innerHTML).to.equal('<button>1</button>');

		expect(vnodeSpy).to.have.been.called;
		expect(eventSpy).to.have.been.called;
	});

	it('should call old options on unmount', () => {
		render(<ClassApp />, scratch);
		render(null, scratch);

		expect(vnodeSpy).to.have.been.called;
	});
});