Repository navigation
testing library formula create patterns
title: "Test Patterns — Formula Create Screen Coverage" type: pattern-guide domain: frontend framework: react testing: "vitest + @testing-library/react" component: "FormulaConfigurationCreateForm" tags: [testing-library, carbon, vitest, tanstack-form, mocking, patterns] created: 2026-09-16
The formula create screen (FormulaConfigurationCreateForm) coverage improvement effort expanded test suites from 57.9% to ~93%+ across three files. This guide captures the patterns and workarounds discovered during that effort.
jsdom does not fire React's synthetic onChange on native radio inputs. Carbon's RadioButton.handleOnChange chain breaks when tests use fireEvent.click or fireEvent.change on radio inputs.
Carbon's RadioButtonGroup manages its own internal state via event handlers that expect React's synthetic event system. jsdom's native radio input events bypass this chain.
Mock @carbon/react with a simplified RadioButtonGroup that captures the onChange prop into a test-accessible callback:
vi.mock("@carbon/react", async () => {
const actual = await vi.importActual("@carbon/react");
return {
...actual,
RadioButtonGroup: ({ onChange, children, ...props }) => {
mocks.radioGroupOnChange = onChange;
return <div data-testid={props["data-testid"]}>{children}</div>;
},
};
});Then call the captured callback directly in tests:
// Trigger the radio group change
mocks.radioGroupOnChange!("area-a");
// Assert the side effect
expect(mockNavigate).toHaveBeenCalled();fireEvent.click and fireEvent.change on radio inputs do not trigger Carbon's internal handler chain in jsdom. The component state never updates. Direct callback invocation is the only reliable approach.
form.setFieldValue("area", value) triggers a re-render, but form.state.values.area (direct read) does not propagate the updated value to child components in the test environment.
TanStack Form v1's state propagation works through React's reconciliation cycle. In jsdom test environments, the direct state read may not reflect the updated value before assertions run.
Verify the side effect that proves the handler ran, rather than asserting the value directly:
// Instead of asserting the value directly
// BAD: expect(form.state.values.area).toBe("area-a");
// Verify the side effect
mockSetIsReviewing.mockImplementation((val) => {
if (!val) {
// This triggers a re-render that proves the handler ran
}
});
// Assert the visible outcome
expect(screen.getByRole("button", { name: /submit/i })).toBeTruthy();Carbon wraps the native button element. userEvent.click on a Carbon Button with type="submit" does not trigger the parent form's onSubmit handler.
Carbon's button component intercepts the click event and does not propagate it to the native form submission flow in jsdom.
Use fireEvent.submit on the form element directly:
const form = screen.getByRole("form", { name: /formula/i });
fireEvent.submit(form);userEvent.click fires pointer events that Carbon intercepts. The native form submission never fires. fireEvent.submit bypasses Carbon's wrapper and triggers the form's onSubmit directly.
When a mock component renders multiple instances (e.g., 3 FormulaSection components each with data-testid="formula-section-area"), getByTestId("formula-section-area") throws because it finds multiple elements.
Use getAllByTestId and select by index:
const sections = screen.getAllByTestId("formula-section-area");
expect(sections[0]).toBeTruthy();
expect(sections[1]).toBeTruthy();Add unique identifiers to distinguish instances:
// Component
<div data-testid={`formula-section-area-${index}`}>
// Test
const section0 = screen.getByTestId("formula-section-area-0");Complex child components with multiple callbacks (onChange, onValidationError) need controlled simulation of user interactions.
Create a minimal mock that captures callback refs and provides trigger buttons:
vi.mock("../FormulaInput", () => ({
default: ({ onChange, onValidationError, expressionRef }) => {
mocks.formulaInputOnChange = onChange;
mocks.formulaInputOnValidationError = onValidationError;
mocks.formulaInputExpressionRef = expressionRef;
return (
<div data-testid="formula-input-mock">
<button onClick={() => onChange("AREA * 2")}>Set Expression</button>
<button onClick={() => onValidationError("Invalid expression")}>
Trigger Error
</button>
</div>
);
},
}));Then simulate user interactions in tests:
// Simulate user entering expression
screen.getByRole("button", { name: /set expression/i }).click();
// Assert the callback was called with correct value
expect(mockOnChange).toHaveBeenCalledWith("AREA * 2");- Tests verify callback contracts without rendering complex child components
- Mock captures all callback invocations for assertion
- Trigger buttons simulate realistic user interactions
- Mock stays minimal and focused on the interface contract
-
Fix Guide — Interacting with Carbon ComboBox in Tests — Real
userEventinteraction for Carbon ComboBox -
Fix Guide — testing-library/prefer-user-event — When to use
userEventvsfireEvent -
Fix Guide — React Testing Library act(...) Warnings — Wrapping state updates in
act()
After applying these patterns:
-
vitest run— all tests pass -
eslint --max-warnings=0— clean -
tsc --noEmit— clean - Coverage meets target (~93%+ for formula create screen files)
This wiki documents NR Waste Plus for users and developers. Verify security and deployment guidance against the current application configuration before applying it elsewhere.
- Report an issue or ask a question in the GitHub repository.
- Follow the Contributing Guidelines to propose an update.
- Read the Code of Conduct.
Collection indexes support human browsing and machine retrieval. Keep both current when adding or moving wiki pages.
NR Waste Plus Wiki | GitHub Repository