lightsoutAlpha

the-render-pattern

a component test that renders outside the setup factory or destructures queries from render()

Agent checkadvisory by defaultreacttests

The argument

The Render Pattern

Render inside the setup() factory; query and assert in the test. For a component, render() is the act, but by convention it lives in the arrange factory — the one accepted exception to "the act lives in the test". Query from screen — never destructure queries from render().

import { expect, describe, test, jest } from '@jest/globals';
import { render, screen } from '@testing-library/preact';
import userEvent from '@testing-library/user-event';
import { NotificationBanner } from './NotificationBanner';

// Mocked Imports
// -------------------------
const mockUseAppStore = jest.fn<(selector: (state: unknown) => unknown) => unknown>();

jest.mock('@store/appStore', () => ({
	useAppStore: (selector: (state: unknown) => unknown) => mockUseAppStore(selector),
}));
// -------------------------

const setupNotificationBanner = ({ isVisible = true }: { isVisible?: boolean } = {}) => {
	const onDismiss = jest.fn<() => void>();
	mockUseAppStore.mockReturnValue(isVisible);
	render(<NotificationBanner onDismiss={onDismiss} />);

	return { onDismiss };
};

describe('NotificationBanner', () => {
	test('does not render the banner when not visible', () => {
		setupNotificationBanner({ isVisible: false });

		const banner = screen.queryByRole('alert');

		expect(banner).not.toBeInTheDocument();
	});

	test('renders the notification message when visible', () => {
		setupNotificationBanner({ isVisible: true });

		const message = screen.getByText('Action required');

		expect(message).toBeInTheDocument();
	});

	test('calls the dismiss handler when the dismiss button is clicked', async () => {
		const { onDismiss } = setupNotificationBanner({ isVisible: true });
		const user = userEvent.setup();

		const dismissButton = screen.getByRole('button', { name: /dismiss/i });
		await user.click(dismissButton);

		expect(onDismiss).toHaveBeenCalledTimes(1);
	});
});

The proof

failsrc/notifications/NotificationBanner.unit.test.tsx
import { expect, describe, test } from '@jest/globals';
import { render } from '@testing-library/react';
import { NotificationBanner } from './NotificationBanner';

describe('NotificationBanner', () => {
	test('renders the notification message when visible', () => {
		// render in the test body, and queries destructured from its result
		const { getByText } = render(<NotificationBanner isVisible message="Action required" />);

		expect(getByText('Action required')).toBeInTheDocument();
	});
});
passsrc/notifications/NotificationBanner.unit.test.tsx
import { expect, describe, test } from '@jest/globals';
import { render, screen } from '@testing-library/react';
import { NotificationBanner } from './NotificationBanner';

const setupNotificationBanner = ({ isVisible = true }: { isVisible?: boolean } = {}) => {
	render(<NotificationBanner isVisible={isVisible} message="Action required" />);
};

describe('NotificationBanner', () => {
	test('renders the notification message when visible', () => {
		setupNotificationBanner({ isVisible: true });

		const message = screen.getByText('Action required');

		expect(message).toBeInTheDocument();
	});
});

Turn it down

Both lines go in your lightsout.config.json.

"standards-checks": { "the-render-pattern": "advisory" }
"standards-checks": { "the-render-pattern": "off" }