@coherent.js/testing
v1.0.0-beta.5
Published
Testing utilities for Coherent.js applications
Maintainers
Readme
@coherent.js/testing
Complete testing utilities for Coherent.js applications.
Installation
npm install --save-dev @coherent.js/testingFeatures
- ✅ Test Renderer - Render components in test environment
- ✅ Query Utilities - Find elements by testId, text, className
- ✅ Event Simulation - Simulate user interactions
- ✅ Async Testing - Wait for conditions and elements
- ✅ Mock Functions - Create mocks and spies
- ✅ Custom Matchers - Coherent.js-specific assertions
- ✅ Snapshot Testing - Component snapshot support
- ✅ User Events - Realistic user interaction simulation
Quick Start
import { describe, it, expect } from 'vitest';
import { renderComponent, extendExpect } from '@coherent.js/testing';
// Extend expect with custom matchers
extendExpect(expect);
describe('MyComponent', () => {
it('should render correctly', () => {
const component = {
div: {
'data-testid': 'my-div',
text: 'Hello World'
}
};
const { getByTestId } = renderComponent(component);
expect(getByTestId('my-div')).toHaveText('Hello World');
});
});API Reference
Rendering
renderComponent(component, options)
Render a component for testing.
const { getByTestId, getByText } = renderComponent({
div: { 'data-testid': 'test', text: 'Hello' }
});renderComponentAsync(component, props, options)
Render an async component.
const result = await renderComponentAsync(asyncComponent, { name: 'World' });createTestRenderer(component, options)
Create a test renderer for component updates.
const renderer = createTestRenderer(component);
renderer.render();
renderer.update(newComponent);shallowRender(component)
Shallow render (top-level only).
const shallow = shallowRender(component);Queries
getByTestId(testId)
Get element by test ID (throws if not found).
const element = getByTestId('submit-btn');queryByTestId(testId)
Query element by test ID (returns null if not found).
const element = queryByTestId('submit-btn');getByText(text)
Get element by text content.
const element = getByText('Submit');getByClassName(className)
Get element by class name.
const element = getByClassName('btn-primary');getAllByTagName(tagName)
Get all elements by tag name.
const items = getAllByTagName('li');Events
fireEvent(element, eventType, eventData)
Simulate an event.
fireEvent(button, 'click');
fireEvent(input, 'change', { target: { value: 'test' } });userEvent
Realistic user interactions.
await userEvent.click(button);
await userEvent.type(input, 'Hello');
await userEvent.clear(input);Async Utilities
waitFor(condition, options)
Wait for a condition to be true.
await waitFor(() => getByText('Loaded').exists, { timeout: 2000 });waitForElement(queryFn, options)
Wait for an element to appear.
const element = await waitForElement(() => queryByTestId('loaded'));waitForElementToBeRemoved(queryFn, options)
Wait for an element to disappear.
await waitForElementToBeRemoved(() => queryByTestId('loading'));act(callback)
Batch updates.
await act(async () => {
// Perform updates
});Mocks & Spies
createMock(implementation)
Create a mock function.
const mock = createMock((x) => x * 2);
mock(5); // returns 10
expect(mock).toHaveBeenCalledWith(5);
expect(mock).toHaveBeenCalledTimes(1);createSpy(object, method)
Spy on an object method.
const spy = createSpy(obj, 'method');
obj.method('test');
expect(spy).toHaveBeenCalledWith('test');
spy.mockRestore();Custom Matchers
Extend expect with Coherent.js-specific matchers:
import { extendExpect } from '@coherent.js/testing';
extendExpect(expect);Available matchers:
toHaveText(text)- Element has exact texttoContainText(text)- Element contains texttoHaveClass(className)- Element has classtoBeInTheDocument()- Element existstoBeVisible()- Element has visible contenttoBeEmpty()- Element is emptytoContainHTML(html)- HTML contains stringtoHaveAttribute(attr, value)- Element has attributetoHaveTagName(tagName)- Element has tag nametoRenderSuccessfully()- Component renderedtoBeValidHTML()- HTML is validtoHaveBeenCalled()- Mock was calledtoHaveBeenCalledWith(...args)- Mock called with argstoHaveBeenCalledTimes(n)- Mock called n times
Utilities
within(container)
Scope queries to a container.
const container = getByTestId('container');
const scoped = within(container);
scoped.getByText('Inner text');screen
Global query utility.
screen.setResult(result);
screen.getByTestId('test');
screen.debug();cleanup()
Clean up after tests.
afterEach(() => {
cleanup();
});Examples
Testing a Button Component
import { renderComponent, fireEvent, createMock } from '@coherent.js/testing';
it('should handle click events', () => {
const handleClick = createMock();
const button = {
button: {
'data-testid': 'my-btn',
text: 'Click me',
onclick: handleClick
}
};
const { getByTestId } = renderComponent(button);
fireEvent(getByTestId('my-btn'), 'click');
expect(handleClick).toHaveBeenCalled();
});Testing Async Components
import { renderComponentAsync, waitForElement } from '@coherent.js/testing';
it('should load data', async () => {
const AsyncComponent = async () => {
const data = await fetchData();
return { div: { text: data.message } };
};
const result = await renderComponentAsync(AsyncComponent);
const element = await waitForElement(() => result.queryByText('Loaded'));
expect(element).toBeInTheDocument();
});Testing Forms
import { renderComponent, userEvent } from '@coherent.js/testing';
it('should submit form', async () => {
const handleSubmit = createMock();
const form = {
form: {
onsubmit: handleSubmit,
children: [
{ input: { 'data-testid': 'name-input', type: 'text' } },
{ button: { 'data-testid': 'submit-btn', text: 'Submit' } }
]
}
};
const { getByTestId } = renderComponent(form);
await userEvent.type(getByTestId('name-input'), 'John');
await userEvent.click(getByTestId('submit-btn'));
expect(handleSubmit).toHaveBeenCalled();
});Snapshot Testing
import { renderComponent } from '@coherent.js/testing';
it('should match snapshot', () => {
const component = {
div: {
className: 'card',
children: [
{ h2: { text: 'Title' } },
{ p: { text: 'Content' } }
]
}
};
const result = renderComponent(component);
expect(result.toSnapshot()).toMatchSnapshot();
});Best Practices
1. Use Test IDs
Add data-testid attributes for reliable querying:
const component = {
button: {
'data-testid': 'submit-button',
text: 'Submit'
}
};2. Clean Up After Tests
Always clean up to avoid test interference:
afterEach(() => {
cleanup();
});3. Use Custom Matchers
Extend expect for better assertions:
extendExpect(expect);
expect(element).toHaveText('Hello');4. Test User Interactions
Use userEvent for realistic interactions:
await userEvent.click(button);
await userEvent.type(input, 'text');5. Wait for Async Updates
Use waitFor for async operations:
await waitFor(() => getByText('Loaded').exists);Integration with Testing Frameworks
Vitest
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { renderComponent, extendExpect, cleanup } from '@coherent.js/testing';
extendExpect(expect);
describe('MyComponent', () => {
afterEach(cleanup);
it('should work', () => {
// Test code
});
});Jest
import { renderComponent, extendExpect, cleanup } from '@coherent.js/testing';
extendExpect(expect);
afterEach(cleanup);
test('should work', () => {
// Test code
});License
MIT
