It is a requirement that every component has tests before it can be out of preview.
To verify data outputs, we use Vitest. For example, a date picker component could have unit tests that verify:
To test interactions, we use Playwright.
For example, a date picker component might have tests that verify:
Each component folder contains a <component-name>.driver.ts
file. This file contains a createTestDriver
function that returns a set of functions that can be used to test the pieces of the component.
import type { Locator, Page } from "@playwright/test";
export type DriverLocator = Locator | Page;
export function createTestDriver<T extends DriverLocator>(rootLocator: T) {
const getRoot = () => {
return rootLocator.locator("[data-qds-otp-root]");
};
const getItems = () => {
return rootLocator.locator("[data-qds-otp-item]");
};
const getInput = () => {
return rootLocator.locator("[data-qds-otp-hidden-input]");
};
const getItemAt = (index: number) => {
return rootLocator.locator(`[data-qds-otp-item="${index}"]`);
};
const getHighlightedItem = () => {
return rootLocator.locator("[data-highlighted]");
};
const getCaretAt = (index: number) => {
return rootLocator.locator(`[data-qds-otp-caret="${index}"]`);
};
return {
...rootLocator,
locator: rootLocator,
getRoot,
getItems,
getInput,
getItemAt,
getCaretAt,
getHighlightedItem
};
}
In the example above, the createTestDriver
function is used to create a driver for the OTP component.
Playwright is an awesome tool for verifying interactions, but it is not intentionally designed for component testing.
Unfortunately, Playwright does not support component testing, it has been experimental for a couple of years, and the maintainers have stated that it is not a priority.
Instead, we have a separate application, apps/docs/components
, that is used to test the components in isolation.
Whenever you make a new showcase example in the docs, you've created a new isolated route in the testing app!
async function setup(page: Page, exampleName: string) {
await page.goto(`http://localhost:6174/base/otp/${exampleName}`);
const driver = createTestDriver(page);
const input = driver.getInput();
await input.focus();
await setupEventListeners(input);
return driver;
}
The route structure for the component tests app is
base/component-name/example-name
.
When writing tests, we use the Given, When, Then convention.
Given - The initial state of the component.
When - The action taken on the component.
Then - The expected outcome of the action.
test(`GIVEN a checkbox that is initially checked
WHEN the trigger is clicked
THEN the indicator should be hidden`, async ({ page }) => {
const d = await setup(page, "hero");
// initial setup
await d.getTrigger().click();
await expect(d.getIndicator()).toBeVisible();
await d.getTrigger().click();
await expect(d.getIndicator()).toBeHidden();
});
Test Driven Development (TDD) is a development process that emphasizes writing out the intended behavior before writing the code.
The process is as follows:
It is preferred to write the tests before the code, but it is not required.
For a more visual explanation, check out this talk by Shai Reznik. Angular is in the video, but the principles apply to any language and framework.