diff --git a/docs/testing/converting-old-tests.md b/docs/testing/converting-old-tests.md
deleted file mode 100644
index 9a6d3d54b4..0000000000
--- a/docs/testing/converting-old-tests.md
+++ /dev/null
@@ -1,33 +0,0 @@
----
-title: Converting old tests
-category: Testing
-order: 4
----
-
-## Converting old tests
-
-This guide explains how to convert current InstUI tests written with the `@instructure/ui-test-utils` testing library to
-use Vitest + React Testing Library.
-
-### Running Vitest
-
-Vitest can be run from the project root with the `npm run test:vitest` command. It's configured in our CI pipeline so pushing a
-branch to remote runs these tests together with the legacy tests automatically.
-
-### Adding new tests to a package
-
-Current tests can be found next to the component source code in the `__tests__` subfolder. New tests should be added
-there in the `__new-tests__` directory. New test files should have the same names as the ones in `__tests__`.
-
-New tests should try to mirror old tests as close as possible, most of the time this will be trivial.
-
-### Skipping old tests
-
-Once a test is rewritten in the new framework the old counterpart should be skipped to indicate that it's converted and
-also to be resourceful with our CI. This can be done by adding `.skip` to individual tests or the whole suite. E.g.:
-`it('should...')` -> `it.skip('should...)` or `describe(')` -> `describe.skip('')`. If a whole
-suite is skipped there's no need to skip individual tests inside it.
-
-### Example
-
-The `ui-avatar` tests were added as an example/reference. They can be found [here](https://github.com/instructure/instructure-ui/tree/master/packages/ui-avatar/src/Avatar/__new-tests__).
diff --git a/docs/testing/integration-testing.md b/docs/testing/cypress-component-testing.md
similarity index 69%
rename from docs/testing/integration-testing.md
rename to docs/testing/cypress-component-testing.md
index 5a44a0d6aa..f61aee3020 100644
--- a/docs/testing/integration-testing.md
+++ b/docs/testing/cypress-component-testing.md
@@ -1,13 +1,13 @@
---
-title: Real-world component testing
+title: Cypress component testing
category: Testing
-order: 5
+order: 3
---
-# Real-world component testing
+# Cypress component testing
Sometimes unit test behaviour doesn't match how our components work in the browser (e.g. no ResizeObserver)
-InstUI uses [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/overview) for these cases. These are located at `instructure-ui/cypress/component/`.
+InstUI uses [Cypress Component Testing](https://docs.cypress.io/guides/component-testing/overview) in these cases to run tests in a real-world environment.
### Running tests
@@ -17,9 +17,14 @@ You can run them from the root with the following command:
npm run cy:component
```
+Run specific test file:
+```
+npm run cy:component -- --spec "cypress/component/Alerts.cy.tsx"
+```
### Creating new tests
-New tests should be added under `instructure-ui/cypress/component/[ComponentName].cy.tsx`
+New tests should be added under `cypress/component/`
+By convention we name test files after the component they are testing like `cypress/component/[ComponentName].cy.tsx`
Cypress tests usually have a structure like this:
@@ -28,7 +33,7 @@ Cypress tests usually have a structure like this:
type: code
---
import React from 'react'
-import { ComponentToTest } from '../../packages/ui'
+import { ComponentToTest } from '@instructure/ui'
import '../support/component'
describe('', () => {
@@ -38,9 +43,12 @@ describe('', () => {
})
})
```
-
You can read more about cypress testing from their [docs](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Writing-tests) and their [React Examples](https://docs.cypress.io/guides/component-testing/react/examples).
+### Example
+You can view our code base on GitHub. The component tests can be found [here](https://github.com/instructure/instructure-ui/tree/master/cypress/component).
+
+
### Debugging tests
To run cypress in a non-headless mode, use the following command:
@@ -78,3 +86,9 @@ describe('real events testing', () => {
})
})
```
+
+
+### Configuration Setup
+Here you can find the key configuration files and folder locations used for our test environment:
+- [cypress/support](https://github.com/instructure/instructure-ui/tree/master/cypress/support)
+- [cypress.config.ts](https://github.com/instructure/instructure-ui/blob/master/cypress.config.ts)
diff --git a/docs/testing/testing-components.md b/docs/testing/testing-components.md
deleted file mode 100644
index 17bc031e26..0000000000
--- a/docs/testing/testing-components.md
+++ /dev/null
@@ -1,46 +0,0 @@
----
-title: Testing
-category: Testing
-order: 1
----
-
-## Testing
-
-### Running Tests Locally
-
-1. Run `npm run test:watch -- --scope @instructure/[package-name]`.
-1. Edit the tests that were generated for you in `packages/[package-name]/src/components/[ComponentName]/__tests__`.
-1. Watch your tests run in the console.
-
-> For details on how to write component tests, see the [test utilities](#ui-test-utils) documentation.
-
-### Continuous Integration
-
-In CI, `npm run test` (`npm run ui-test`) is run. This runs the tests headless, without the debugging options enabled.
-The `npm run test` script also accepts the `--scope` argument if you want to run it for a single package or test file.
-
-### Debugging Tests
-
-1. Run `npm run test:watch -- --scope [package name from its package.json]`. This command should automatically open up the Chrome browser.
-2. In Chrome click the 'Debug' button at the top of the page (you may have to scroll up).
-3. Open the [Developer tools](https://developers.google.com/web/tools/chrome-devtools/debug/?hl=en) (`Command + Shift + C`).
-4. Now you can add breakpoints in the test code or the component code to debug issues. (`Command + P` in the 'Sources' tab).
-
-### Test Run Options
-
-Options that can be run with `npm run test`/`npm run test:watch`:
-
-- `--changed` will run the tests against any package that has changes (since the previous commit, including un-staged changes).
-- `--staged` will run tests against packages that have been staged but not yet committed.
-- `--scope [package name from its package.json (comma delimited)]` will run the tests against a single package.
-- `--path [test file paths (comma delimited)]` will run just the tests in a single test src file.
-- `--browser=Firefox` will run the tests in the Firefox browser.
-- `--browser=Safari` will run the tests in the Safari browser.
-- `--randomize` will run the tests in random order, to help catch interdependent or leaky tests.
-
-### Code Coverage
-
-Code coverage thresholds are configured in `karma.config.js` and code is instrumented
-via babel config (see [@instructure/ui-babel-preset](#ui-babel-preset)).
-If coverage numbers go below the configured values, the test run will fail.
-When you run `npm run test` (or `npm run test -- --scope [package name]`) a detailed coverage report is generated in the `coverage/` directory.
diff --git a/docs/testing/testing-overview.md b/docs/testing/testing-overview.md
new file mode 100644
index 0000000000..5f5659170a
--- /dev/null
+++ b/docs/testing/testing-overview.md
@@ -0,0 +1,27 @@
+---
+title: Testing
+category: Testing
+order: 1
+---
+
+## Testing
+
+This page provides an overview of the testing strategies we use to ensure the quality and stability of our components. We use a combination of modern tools to support our testing needs. Each tool serves a different purpose in our testing pyramid, from unit-level validations to full-blown visual and behavioral regression tests.
+
+### Technologies Used:
+
+#### Vitest + React Testing Library:
+
+These tools are our primary stack for writing component-level unit tests. They are lightweight and fast. [Vitest](https://vitest.dev/guide/) is one of the fastest modern testing framework. It offers a Jest-like API and runs in a Node.js environment, making it ideal for testing individual components and functions in isolation. Paired with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), Vitest encourages accessible and maintainable test practices by querying elements the way users interact with them. It's best suited for testing component logic, rendering conditions, and props/state changes without needing a full browser environment. For a deeper dive into Vitest unit testing, check out our [detailed guides and examples.](/#vitest-unit-testing)
+
+#### Cypress Component Testing:
+
+[Cypress Component Testing](https://docs.cypress.io/app/component-testing/get-started) allows you to mount individual components in a real browser environment for precise interaction testing. Unlike traditional unit tests, it renders with full CSS and browser APIs, offering more realistic behavior. This makes it ideal for testing user interactions like clicks, keyboard navigation, focus traps, and animations. While a bit heavier than Vitest, it provides greate visibility and debugging capabilities for complex UI logic. For a deeper dive into Cypress component testing, check out our [detailed guides and examples.](#cypress-component-testing)
+
+#### Chromatic Visual Regression Testing with Cypress:
+
+We use [Cypress](https://docs.cypress.io/app/tooling/visual-testing) end-to-end tests to generate structured layouts and capture screenshots of components. [Chromatic](https://www.chromatic.com/docs/diff-inspector/) handles the visual diffing and review process. It's especially effective for catching layout shifts, styling regressions, or broken UI elements that don’t trigger functional test failures. Tests are run after changes are pushed, comparing the new screenshots to a baseline stored from a previously verified state. When differences are detected, the test fails and provides side-by-side diffs for easy review. This type of testing is particularly valuable for pixel-perfect components, design systems, or any feature with strict visual requirements.
+
+#### Cypress Behavioral Regression Testing:
+
+[Cypress Behavioral Regression Testing](https://docs.cypress.io/app/end-to-end-testing/writing-your-first-end-to-end-test) focuses on simulating realistic user flows across complex components or pages to ensure key behaviors remain stable. These tests often replicate critical workflows like navigation, and keyboard interactions. They are useful after feature changes or refactors, as they verify that the components still behaves correctly from the user’s perspective. Since the tests run in a real browser, they provide visibility into full-stack behavior, including browser APIs. These more complex end-to-end style tests are helps catch integration issues or regressions that unit and visual tests may overlook.
diff --git a/docs/testing/ui-test-utils.md b/docs/testing/ui-test-utils.md
deleted file mode 100644
index dc5b509722..0000000000
--- a/docs/testing/ui-test-utils.md
+++ /dev/null
@@ -1,145 +0,0 @@
----
-title: Testing library
-category: Testing
-order: 2
----
-
-## About the `ui-test-utils`
-
-`@instructure/ui-test-utils` is a UI testing library created by Instructure's UI Development team. It provides developers with a framework for writing reliable, performant tests on the DOM:
-
-- Built-in implicit waits for DOM queries and utils to retry assertions
-- Helpers for common DOM assertions
-- Built-in test suite setup/teardown to prevent test case pollution
-- Stub/spy helpers to test callback props
-- A utility to generate a11y tests
-- A utility to make it easy to create UI component Page Object Models
-
-`ui-test-utils` wraps popular third-party testing libraries that can be swapped out as needed without replacing and rewriting everything.
-
-### Quick start
-
-#### Tech stack
-
-`ui-test-utils` is built to work with the [Mocha JavaScript testing framework](https://mochajs.org) and uses the [Chai assertion library](https://www.chaijs.com). If you would like to run your tests in the Chrome browser, `ui-test-utils` can be used with Webpack and the [Karma test runner](https://karma-runner.github.io).
-
-We don't recommend or officially support running tests using [jsdom](https://github.com/jsdom/jsdom). While you might be able to use `ui-test-utils` with [Jest](https://jestjs.io/) and jsdom, jsdom will likely require polyfills for native browser APIs that we use in our components.
-
-#### Installation
-
-To set up your environment to use `@instructure/ui-test-utils`, install the following:
-
-- To install a CLI, `ui-test` globally that you can use to run your tests:
- `npm install -g @instructure/ui-scripts`
-- To install the utils, the karma test runner and webpack:
- `npm install --save-dev @instructure/ui-test-utils @instructure/ui-karma-config webpack`
-
-#### Configuration
-
-Create a `karma.conf.js` file in your project root
-
-```js
----
-type: code
----
-// karma.conf.js
-const path = require('path')
-
-module.exports = require('@instructure/ui-karma-config')({
- coverageThreshold: {
- global: {
- statements: 87,
- branches: 70,
- functions: 80,
- lines: 87
- }
- },
- coverageDirectory: path.join(__dirname, '/coverage')
-})
-```
-
-#### Running tests
-
-Run the `ui-test` script to run all tests in your project in headless Chrome.
-
-> The test files should be placed inside `packages/` directory, supported extensions : .test.tsx, .test.ts, .test.js
-
-If you’d like to debug a test, you can run `ui-test --watch` to run the tests in Chrome and watch for changes to your source.
-
-To use the Chrome dev tools while your tests are running, find the Chrome browser that opens up when you run `ui-test --watch`, and then click on the “Debug” button and open Chrome dev tools.
-
-## Testing philosophy
-
-### What we want
-
-- **We want to test the semantic DOM**. Why? Because the DOM reflects what’s actually happening for the user.
-- **We have a need for speed**. We favor tests written at the smallest possible testable component level. They run faster and provide better feedback during development.
-- **We want robust tests**. Sure, we want a speedy test suite, `ui-test-utils` favors an “everything is async” approach, providing built-in waits and retries for DOM assertions and queries.
-
-### What we don’t want
-
-- **We don’t want to test UI implementation details**. Avoid asserting on CSS classes, non-semantic DOM elements, private child props, or component state. Tests that assert on implementation details are likely to break, even when there is no user-facing change. This is a blocker to being able to automate dependency upgrades and makes taking on technical debt/refactoring more costly.
-- **We don’t want flaky/brittle tests**. Async DOM updates like transitions or API calls can cause tests to fail inconsistently. Therefore, we favor “always waiting for everything”: Every DOM interaction should wait for a DOM assertion following it.
-- **We don’t want sloooow tests**. Long build times are usually the result of too many integration or end-to-end tests. We favor writing tests at the smallest possible component level.
-
-## Writing testable components
-
-### Write props-driven, controlled, stateless components
-
-Keep components that render markup and styles separate from business logic, state management code, API calls, and ideally even React context...
-
-- ...so that tests can be generated by configuring props/value combinations only
-- ...so that there is very little setup and mocking required to render and test the component in isolation
-
-### Build and test components in isolation
-
-Avoid global CSS styles/resets and the CSS cascade...
-
-- ...using a sandbox setup like [storybook](https://storybook.js.org/)
-- ...for faster feedback during development and to identify points of failure more easily
-
-### Clean up on unmount
-
-Clean up on unmount to prevent test case pollution. Always cancel/remove these:
-
-- `requestAnimationFrame`
-- `setTimeout`
-- Native event listeners
-- `XMLHttpRequest`
-- Injected DOM nodes when the UI component unmounts
-
-## Writing tests
-
-See [Writing tests](/#writing-tests) for details.
-
-## Test cookbook
-
-### Writing robust CSS selectors
-
-**Avoid using class names in your selectors**. They’re considered an implementation detail and may change multiple times over the course of a component’s life. It should also be noted that while `ui-test-utils` permits class name selectors, they may be disallowed in the future -- and who really wants to go back and redo tests?
-
-In addition, please **avoid selecting `div` or `span` elements**. These elements have no semantic value and are simply styling hooks. If you try to select them, `ui-test-utils` will throw a console error.
-
-So what _should_ you use in your selectors? Well, the first thing to note is that if you have a choice between selecting an element via CSS or the text it contains, we recommend going with the text: Basing your queries on text content will make them easier to debug in the test output, and it is unlikely you will have to update your tests when the component is refactored.
-
-However, if you need to use CSS selectors, we recommend targeting the **semantic parts of the rendered DOM**:
-
-- **Semantic element names**. A ``, for example, describes a specific type of content and is a tag that is unlikely to change over the course of the component’s life.
-
-```js
----
-type: code
----
-const header = await myComponent.find('header')
-```
-
-- **Accessibility attributes**. Attributes like `role`, `type`, or `alt` make great selector targets because they describe what the element does and are also unlikely to change with, say, a design update to the component. ARIA attributes are also great to use in selectors.
-
-```js
----
-type: code
----
-const dialog = myComponent.find('[role="dialog"]')
-
-const dialog = myComponent.find('[aria-label="Dialog"]')
-```
diff --git a/docs/testing/vitest-unit-testing.md b/docs/testing/vitest-unit-testing.md
new file mode 100644
index 0000000000..6db34e6d14
--- /dev/null
+++ b/docs/testing/vitest-unit-testing.md
@@ -0,0 +1,114 @@
+---
+title: Vitest unit testing
+category: Testing
+order: 2
+---
+
+## Vitest unit testing
+
+[Vitest](https://vitest.dev/guide/) is our fastest tool for in-memory testing of components and logic in a Node.js environment. We pair it with [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) to validate behavior, rendering, and edge cases—all without requiring a real browser.
+
+### Running Vitest
+
+Vitest can be run from the project root with the following command. It's configured in our CI pipeline so pushing a branch to remote runs these tests automatically.
+
+```
+npm run test:vitest
+```
+
+You can scope the tests you want to run by specifying the path and also enable watch mode to automatically rerun tests when files change.
+
+Run all tests of the selected component in wach mode:
+```
+npm run test:vitest-watch ui-avatar
+```
+Run specific test file in wach mode:
+```
+npm run test:vitest-watch ui-avatar/src/Avatar/__tests__/Avatar.test.tsx
+```
+> DO NOT forget to `rebuild` the changed package before testing!
+
+### Creating new tests
+
+Current tests can be found next to the component source code in the `__tests__` subfolder. New tests should also be added there.
+By convention we name test files after the component they are testing like:
+
+ `[component-package]/src/[ComponentName]/__tests__/[ComponentName].test.tsx`
+
+Vitest tests usually have a structure like this:
+
+```js
+---
+type: code
+---
+import { render, screen } from '@testing-library/react'
+import { vi } from 'vitest'
+
+import '@testing-library/jest-dom'
+import ComponentToTest from '../index'
+
+describe('', () => {
+ it('works as intended...', () => {
+ const onClickMock = vi.fn()
+ render()
+ // rest of the test comes here
+ })
+})
+```
+
+### Example
+You can view our code base on GitHub.
+
+The `ui-avatar` tests can be found [here](https://github.com/instructure/instructure-ui/tree/master/packages/ui-avatar/src/Avatar/__tests__).
+
+### Debugging tests
+
+If you need to debug a test in your IDE or print extra info, you can use:
+
+```
+console.log('Debug info')
+```
+
+To inspect the rendered DOM and current test state, you can also use:
+
+```
+screen.debug()
+```
+
+If you want to debug tests using breakpoints, see [IDE Integrations](https://vitest.dev/guide/ide.html).
+
+### Simulating Real User Interactions
+
+While fireEvent from React Testing Library works for basic interaction simulation, we use @testing-library/user-event for more realistic and accessible user interaction testing.
+[userEvent](https://testing-library.com/docs/user-event/intro/) is a companion library for Testing Library that provides more advanced simulation of browser interactions than the built-in fireEvent method.
+While fireEvent dispatches single DOM events, userEvent can fire multiple events and do additional checks along the way, making your tests closer to actual user experience.
+
+Use it in the following way:
+
+```js
+---
+type: code
+---
+import { render, screen } from '@testing-library/react'
+import { vi } from 'vitest'
+
+import '@testing-library/jest-dom'
+import userEvent from '@testing-library/user-event'
+import ComponentToTest from '../index'
+
+describe('', () => {
+ it('works as intended...', async () => {
+ const onKeyDownMock = vi.fn()
+ render()
+
+ const input = screen.getByTestId('input')
+ await userEvent.type(input, 'foo bar{enter}')
+ // rest of the test comes here
+ })
+})
+```
+
+### Configuration Setup
+In the root you can find the key configuration files and folder locations used for our test environment:
+- [vitest.config.mts](https://github.com/instructure/instructure-ui/blob/master/vitest.config.mts)
+- [vitest.setup.ts](https://github.com/instructure/instructure-ui/blob/master/vitest.setup.ts)
\ No newline at end of file
diff --git a/docs/testing/writing-tests.md b/docs/testing/writing-tests.md
deleted file mode 100644
index 723905d5c7..0000000000
--- a/docs/testing/writing-tests.md
+++ /dev/null
@@ -1,463 +0,0 @@
----
-title: Writing tests (legacy)
-category: Testing
-order: 3
----
-
-> This section uses [`@instructure/ui-test-utils`](/#ui-test-utils) testing library which will be sunset in our next
-> release in favor of Vitest + React Testing Library. See [our guide](/#converting-old-tests) on how to convert legacy tests to use the new
-> frameworks.
-
-## The anatomy of a test
-
-The following code example presents an annotated version of a typical test. The terms in the comments will be explained over the course of this section.
-
-```js
----
-type: code
----
-import React from 'react'
-import {
- mount,
- stub,
- wait,
- expect,
- findWithLabel,
- findWithText
-} from '@instructure/ui-test-utils'
-
-import MyComponent from '../index'
-import { setupFixtures, cleanupFixtures } from './fixtures'
-
-// test suite:
-describe('', async () => {
- // async/await
- // hook:
- beforeEach(async () => {
- await setupFixtures()
- })
-
- // hook:
- afterEach(async () => {
- await cleanupFixtures()
- })
-
- // test case:
- it('should do something', async () => {
- // stub:
- const handleClick = stub()
-
- // mounting a component:
- await mount(
-
- )
-
- // query:
- const button = await findWithLabel('My button label')
-
- // query result with event helper:
- await button.click()
-
- // assertion with wait:
- await wait(() => {
- expect(handleClick).to.have.been.calledOnce()
- })
-
- // query:
- const response = await findWithText(
- 'My response text',
- // query options:
- { exact: false }
- )
-
- // assertion:
- expect(response).to.exist()
- })
-})
-```
-
-### Test suites and test cases
-
-#### Test suites: `describe()`
-
-Test suites are how the Mocha framework groups tests. Create a suite by using a `describe()` function that returns all the tests included in the suite:
-
-```js
----
-type: code
----
-describe('when variant is set to rectangle', async () => {
- // all the tests related to the rectangle variant
-})
-```
-
-You can nest your tests in groups as deep as you deem necessary. The main benefit to thoughtful nesting is that your tests (especially those for complex components) will stay organized and be easier for other developers to maintain. In addition, grouping tests by category will make the console output from your tests will be easier to parse. Finally, by using `describe.only(‘...’)`, you can limit your tests to only run on a certain suite and test hooks ([see below](/#writing-tests/#the-anatomy-of-a-test-hooks-and-the-test-sandbox)) are scoped to all tests within the `describe` block where they are defined.
-
-#### Test cases: `it()`
-
-In Mocha, each test case (or each individual test) goes inside an `async it()` function. To keep your tests consistent and make your test output a pleasure to read, we recommend sticking to an `it('should [do something]')` format when writing test cases.
-
-### Async and await
-
-#### UI tests should be asynchronous.
-
-For readers without JavaScript backgrounds, this is a fancy way of saying that when you’re testing the DOM, you want to wait for something to happen before you move on to the next thing. Why? Because on the web, stuff happens all the time that makes tests that don’t wait (a.k.a., synchronous tests) brittle and unreliable -- stuff like the time it takes for animations to complete, or the time it takes to make an API call: You never know exactly how long that stuff will take, so the safest strategy is to make sure it’s done before you move on.
-
-So when you see the following query, what is going on under the hood?
-
-```js
----
-type: code
----
-const input = await container.find('input')
-```
-
-The `await` operator is waiting for a [JavaScript Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to be fulfilled. In this case, the Promise will be fulfilled if an `` can be found inside the container. `ui-test-utils` will make an initial attempt to locate the element. If the `` is found right away, great: The test moves on to the next line.
-
-However, if the `` is not found, `ui-test-utils` waits for any DOM change for the duration of our default timeout of 2s. (This duration can be overridden by passing in a `timeout` option to the `find` method.) If a DOM change is detected, the test looks for the input again. If no DOM change occurs, however, the test will fail when it reaches the end of the timeout.
-
-Because we’re using the Javascript keyword `await` in our test, we need to let the test runner know that this test is asynchronous, so we mark the test function with the keyword `async`. See the [Mocha documentation on asynchronous tests](https://mochajs.org/#using-async-await) for more details.
-
-### Hooks and the test sandbox
-
-Remember at nursery school when you’d sing that “clean up, clean up” song while you put your toys away? Well, that’s a big part of what hooks do. They’re handy functions built into [Mocha](https://mochajs.org/#hooks) that you can use to set up or clean up after your tests.
-
-```js
----
-type: code
----
-beforeEach(async () => {
- // don’t forget your async/await here too!
- await doSomethingAsync()
-})
-```
-
-> Not that you shouldn’t read this section carefully, but one of the advantages of using `ui-test-utils` is that **its built-in test sandbox will handle the majority of test cleanup for you**. As a result, you probably won’t need to use hooks very often.
-
-When you use `ui-test-utils`, a global `beforeEach` and `afterEach` hook is automatically set up for you to help ensure that your tests clean up after themselves. If you use the `stub` and `spy` utilities provided by `ui-test-utils`, they will be reset automatically between tests (via [sinon’s sandbox](https://sinonjs.org/releases/latest/sandbox/)). The `ui-test-utils` sandbox also ensures that any DOM changes made during a test run are cleaned up automatically.
-
-### Spies and stubs
-
-A stub is a function with pre-programmed behavior. Stubs can be used to test DOM interactions with a UI component via the component’s callback props. For more information, see https://sinonjs.org/releases/latest/stubs.
-
-A spy is a function that records arguments and returns values, etc. for any calls. Like stubs, spies can be used to test DOM interactions with a UI component via its callback props. For more information, see https://sinonjs.org/releases/latest/spies.
-
-> Note that when you use stubs and spies in `ui-test-utils`, the built-in test sandbox will handle the clean-up for you. Yay!
-
-### Mounting your component
-
-To assert on a component, you must first get it mounted -- meaning rendered into the document.
-
-```js
----
-type: code
----
-import { mount, find } from '@instructure/ui-test-utils'
-import MyComponent from '../index'
-
-it('should mount', async () => {
- await mount()
-
- const button = await findWithLabel('my button label')
- expect(button).to.exist()
-})
-```
-
-### Queries
-
-To prove that something is happening in a component, you need to find stuff within that component, so you can see what’s going on in its rendered DOM. This is where queries come in: They allow you to dive into the component’s DOM and return results you can make assertions against.
-
-> We recommend using `findWithLabel` and `findWithText` whenever possible. Basing your queries on text content (as opposed to an HTML element) will make them easier to debug and you shouldn’t have to update your tests when the component is refactored.
-
-| Query | Description | Example |
-| ------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
-| find | Finds a single element that matches a CSS selector | `await find("[role='menu']")` |
-| findAll | Finds all elements that match a CSS selector | `await findAll('button')` |
-| findWithText | Finds an element that contains text content that matches the argument | `await findWithText('Collection 2')` |
-| findWithTitle | Finds an element that has a title (element, for SVG, or attribute for other elements) that matches the argument | `await findWithTitle('Collection 2')` |
-| findWithLabel | Finds an element that is labeled by text that matches the argument | `await findWithLabel('Item1')` |
-
-#### Query Arguments
-
-The query functions above accept three arguments: an HTML element, a CSS selector or text, and an options object.
-
-If you pass in an HTML element, the query will search within (and including) that element for matches. By default, the query will search the entire HTML document.
-
-#### Query Selectors
-
-You can pass any built-in CSS selector to a query:
-
-```js
----
-type: code
----
-const allSVGs = await subject.findAll('svg')
-const visibleFrame = await subject.find('iframe:not([title="always-hidden"])')
-```
-
-> OK, any CSS selector EXCEPT one that includes `div` or `span`. Querying these elements will cause an error because they don’t have semantic value, and their use therefore falls under the category of an implementation detail.
-
-In addition to valid native [CSS selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors), `ui-test-utils` uses the [Sizzle CSS selector engine](https://sizzlejs.com) to make the following custom selectors available for use in tests:
-
-| Selector | Description | Example |
-| --------- | ------------------------------------------------- | ----------------------------- |
-| visible | Filters queries to elements that are visible | `await findAll(':visible')` |
-| tabbable | Filters queries to elements that can be tabbed to | `await findAll(':tabbable')` |
-| focusable | Filters queries to elements that can be focused | `await findAll(':focusable')` |
-| clickable | Filters queries to elements that can be clicked | `await findAll(':clickable')` |
-
-#### Query Options
-
-You can pass the following options to your query:
-
-| Option | Default | Description |
-| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------- |
-| collapseWhitespace | `true` | Whether newlines, tabs and multiple spaces should be collapsed into a single space before attempting to match |
-| customMethods | | custom methods to apply to the query results |
-| exact | `true` | Whether a text match can be a partial and non-case-sensitive or exact |
-| expectEmpty | false | Whether you expect any results or not |
-| ignore | `'script,style'` | elements to ignore |
-| timeout | `1900` | how long to wait/retry |
-| trim | `true` | whether text should be trimmed of whitespace before attempting to match |
-
-#### Querying for an element that should not be there
-
-The query option you will most frequently use will be `expectEmpty`. When you need to make a negative assertion, set `expectEmpty` to `true`, so that the query function knows that the desired result of the query is for nothing to be found. Otherwise, your test will time out and fail.
-
-```js
----
-type: code
----
-const foo = await FooLocator.find({ expectEmpty: true })
-```
-
-As a general rule, it’s best to avoid negative assertions (asserting that something should not exist or happen): In UI code, things often happen asynchronously, and the element might appear or the behavior might happen after you’ve made your assertion -- meaning your test could pass when it really shouldn’t.
-
-#### Query Results
-
-The above query methods are applied to query results, so that it’s easy to run additional queries within them.
-
-```js
----
-type: code
----
-const form = await find('form')
-
-// call helper methods on the results of a find query
-const formNode = form.node()
-
-// query methods are attached to the results
-// call them to query within the result’s DOM node
-const inputs = await form.findAll('input')
-
-// call helper methods on each result of a findAll query
-const inputNodes = inputs.map((input) => input.node())
-```
-
-#### Helpers
-
-In addition to the query methods, query results have the following helpers applied to them:
-
-| Helper function | Description |
-| ------------------------------- | -------------------------------------------------------------------------------------------------- |
-| `ancestors([selector])` | Returns all of the ancestor nodes that match [selector] |
-| `attribute([attribute])` | Returns the attribute value of the node |
-| `bounds([property])` | Returns the value of the bounding rect property (e.g. 'left') |
-| `checked()` | Returns true if the node is checked |
-| `classNames()` | Returns the class list of the node as an array |
-| `clickable()` | Returns true if the element is visible and clickable (pointer events aren’t disabled) |
-| `contains([selector\|element])` | Returns true if the node contains an element that matches [selector] or if it contains [element] |
-| `containsFocus()` | Returns true if the node contains or is the active element |
-| `debug()` | Logs the HTML markup of the result to the console |
-| `descendants([selector])` | Returns all of the descendant nodes that match [selector] |
-| `disabled()` | Returns true if the node is disabled |
-| `empty()` | Returns true if the node has no children (including text nodes) |
-| `exists()` | Returns true if the node is rendered in the document |
-| `focusable()` | Returns true if the element is focusable |
-| `focused()` | Returns true if the node is the active element (focused) |
-| `hasClass([className])` | Returns true if the node has [className] in its class list |
-| `id()` | Returns the id of the node |
-| `label()` | Returns the accessible label of the element |
-| `matches([selector])` | Returns true if the node matches the [selector] string |
-| `node()` | Returns the DOM node found by the query |
-| `parent()` | Returns the parent DOM node |
-| `readonly()` | Returns true if the node is readonly |
-| `rect()` | Returns the bounding client rect of the node |
-| `role()` | Returns the role of the node |
-| `selected()` | Returns true if the node is selected |
-| `style([property])` | Returns the computed style property of the node |
-| `tabbable()` | Returns true if the element is focusable and has a non-negative tab index |
-| `tagName()` | Returns the tagName of the node |
-| `text()` | Returns the text content of the node |
-| `title()` | Returns the title (attribute or element, if SVG) |
-| `toString()` | Returns the HTML markup of the result as a string |
-| `typeIn([string])` | Simulates typing [string] into the node (assumes that it is an input, textarea or contenteditable) |
-| `value()` | Returns the value of the node |
-| `visible()` | Returns true if the element is visible on screen |
-
-### Events
-
-A DOM interaction, or event, is fired either due to real user interaction with a browser, or programmatically via a UI test.
-
-Note that React uses [synthetic events](https://reactjs.org/docs/events.html). However, we recommend testing by firing and verifying native DOM events when possible (the fact that we're using React events is an implementation detail).
-
-`ui-test-utils` provides access to all native [web events](https://developer.mozilla.org/en-US/docs/Web/Events) as helper methods on query results.
-
-```js
----
-type: code
----
-const component = await ComponentLocator.find()
-await component.mouseDown()
-```
-
-For keyboard events (`keyDown`, `keyPress`, `keyUp`), `ui-test-utils` uses the [keycodes library](https://github.com/timoxley/keycode) to make it easy to specify which key (as the first argument), using a string label.
-
-```js
----
-type: code
----
-await component.keyDown('enter')
-```
-
-The second argument for keyboard events, and the first for all others, is an optional `Event` initialization object that is passed into the native `Event` constructor.
-
-```js
----
-type: code
----
-await component.click({ button: 0, bubbles: false })
-```
-
-### Assertions
-
-If you’ve ever accused Professor Plum of murder in the library with the lead pipe while playing Clue, you’ve made an assertion. The assertion is the grand conclusion of your test, where you state what you think will happen based on the results of your query.
-
-`ui-test-utils` uses chai’s `expect()` assertion style (https://www.chaijs.com) to make assertions on components. In addition, the following custom assertions are available to make assertions on a query result:
-
-- `expect(await find([selector]))`
-- `.to.have.tagName([tagName])`
-- `.to.have.id([id])`
-- `.to.have.style([property], [value])`
-- `.to.have.attribute([attribute], [value]).to.have.label([label])`
-- `.to.have.title([title])`
-- `.to.have.value([value])`
-- `.to.exist()`
-- `.to.have.className([className])`
-- `.to.be.disabled()`
-- `.to.be.readonly()`
-- `.to.have.role([role])`
-- `.to.be.selected()`
-- `.to.be.checked()`
-- `.to.match([selector])`
-- `.to.be.focused()`
-- `.to.have.focus()`
-- `.to.be.focusable()`
-- `.to.be.tabbable()`
-- `.to.be.visible()`
-- `.to.be.clickable()`
-- `.to.have.text([text])`
-- `.to.have.bounds([property], [value])`
-- `.to.have.exactly([count]).descendants([selector])`
-- `.to.have.exactly([count]).ancestors([selector])`
-- `.to.contain([selector|element])`
-- `.to.be.empty()`
-
-### Testing for accessibility
-
-Once you've mounted your component in the document you can run tools like [axe-core](https://www.deque.com/axe/) to verify that the rendered markup meets accessibility requirements. `ui-test-utils` provides a wrapper utility for running `axe-core`:
-
-```js
----
-type: code
----
-import { accessible, mount, expect } from '@instructure/ui-test-utils'
-
-it('should be accessible', async () => {
- await mount()
- expect(await accessible()).to.be.true()
-})
-```
-
-### Testing responsive components
-
-Testing the behavior of responsive components often requires changing the browser viewport size. `ui-test-utils` provides a utility to do just that:
-
-```js
----
-type: code
----
-import { viewport, stub } from '@instructure/ui-test-utils'
-
-it('should do something when the viewport size changes', async () => {
- const handleResize = stub()
- await mount()
-
- // sets the viewport to 320px x 480px
- viewport.set(320, 480)
-
- expect(handleResize).to.have.been.calledOnce()
-})
-```
-
-The test sandbox is configured to call `viewport.reset()` in between each test, so you don’t have to worry about resetting the viewport size.
-
-### Locators: page object models for components
-
-**A locator is a collection of query methods and CSS selectors that makes your tests easier to maintain.**
-
-If you’re familiar with the concept of [Page Object Models](https://martinfowler.com/bliki/PageObject.html), it might be helpful to think of locators as POMs for UI components. Locators ideally live with their components, making it easy to keep them up-to-date when the component implementation is updated.
-
-The only required argument in a locator is a _semantic_ (don’t use classes, divs, or spans) CSS selector:
-
-```js
----
-type: code
----
-import { locator } from '@instructure/ui-test-utils'
-export const fooLocator = locator('[data-foo]')
-```
-
-Import this basic locator into your tests file, and you can now use it as follows:
-
-```js
----
-type: code
----
-const foo = await fooLocator.find()
-```
-
-`foo` will now give you the first matching element with a `data-foo` attribute.
-
-> **When should you make a locator?**
->
-> Make a locator for any component that has something inside it you need to repeatedly interact with in your tests. Locators should not be added for components that are presentational (grid or flex layout components, for example). Because these components are comprised of styled `
` or `` elements with no semantic value, they (and their CSS classes) can be considered implementation details.
-
-Locators are more than just selector shortcuts: They can take an object of custom methods (functions you can run on the results) as their second argument. Here’s a shortened example from Instructure UI’s `ToggleDetails` component:
-
-```js
----
-type: code
----
-import { locator } from '@instructure/ui-test-utils'
-import ToggleDetails from './index'
-
-const ToggleLocator = locator('[aria-expanded][aria-controls]')
-
-export const customMethods = {
- clickToggle: async (element, ...args) =>
- (await ToggleLocator.find(element)).click(...args)
-}
-
-export default locator(ToggleDetails.selector, customMethods)
-```
-
-This locator first defines the CSS selector it will use to find the element that does the toggling in `ToggleDetails`. Then it defines a custom method that test writers can use to click this element. The `ToggleDetails.selector` in the export is a `ui-test-utils` feature that will add a data attribute to the root element in the `ToggleDetails` component that the library uses as a hook when the test writer uses the locator:
-
-```js
----
-type: code
----
-import ToggleDetailsLocator from '../locator'
-const toggleDetails = await ToggleDetailsLocator.find()
-```