-
Notifications
You must be signed in to change notification settings - Fork 727
feat(qwik-testing-library): add documentation for Qwik Testing Library #1432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6d790ba
feat(qwik-testing-library): add documentation for the new Qwik Testin…
ianlet e888514
chore: apply review suggestions
ianlet fe483a9
Merge branch 'main' into main
ianlet 3522eed
chore: adjust user event setup
ianlet 98b3fab
Update docs/qwik-testing-library/example.mdx
timdeschryver File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
--- | ||
id: api | ||
title: API | ||
sidebar_label: API | ||
--- | ||
|
||
`@noma.to/qwik-testing-library` re-exports everything from | ||
[`@testing-library/dom`][@testing-library/dom], as well as: | ||
|
||
- [`render`](#render) | ||
- [`cleanup`](#cleanup) | ||
|
||
[@testing-library/dom]: ../dom-testing-library/api.mdx | ||
|
||
## `render` | ||
|
||
Render your component to the DOM with Qwik. By default, when no options are | ||
provided, the component will be rendered into a `<host>` appended to | ||
`document.body`. | ||
|
||
```tsx | ||
import {render} from '@noma.to/qwik-testing-library' | ||
import {MockProvider} from './MockProvider' | ||
import {MyComponent} from './MyComponent' | ||
|
||
const result = await render(<MyComponent />, { | ||
baseElement: document.body, | ||
container: document.createElement('host'), | ||
wrapper: MockProvider, | ||
}) | ||
``` | ||
|
||
### Render Options | ||
|
||
You may also customize how Qwik Testing Library renders your component. Most of | ||
the time, you shouldn't need to modify these options. | ||
|
||
| Option | Description | Default | | ||
| ------------- | --------------------------------------------------- | -------------------------------- | | ||
| `container` | The container in which the component is rendered. | `document.createElement('host')` | | ||
| `baseElement` | The base element for queries and [`debug`](#debug). | `document.body` | | ||
| `queries` | [Custom Queries][custom-queries]. | N/A | | ||
| `wrapper` | The wrapper to provide a context to the component. | N/A | | ||
|
||
[custom-queries]: ../dom-testing-library/api-custom-queries.mdx | ||
|
||
#### `wrapper` | ||
|
||
You can wrap your component into a wrapper to provide a context and other | ||
functionalities needed by the component under test. | ||
|
||
```tsx | ||
import {render} from '@noma.to/qwik-testing-library' | ||
import {QwikCityMockProvider} from '@builder.io/qwik-city' | ||
|
||
await render(<MyComponent />, {wrapper: QwikCityMockProvider}) | ||
``` | ||
|
||
### Render Results | ||
|
||
| Result | Description | | ||
| ----------------------------- | ---------------------------------------------------------- | | ||
| [`baseElement`](#baseelement) | The base DOM element used for queries. | | ||
| [`container`](#container) | The DOM element the component is mounted to. | | ||
| [`asFragment`](#asFragment) | Convert the DOM element to a `DocumentFragment`. | | ||
| [`debug`](#debug) | Log elements using [`prettyDOM`][pretty-dom]. | | ||
| [`unmount`](#unmount) | Unmount and destroy the component. | | ||
| [`...queries`](#queries) | [Query functions][query-functions] bound to `baseElement`. | | ||
|
||
[pretty-dom]: ../dom-testing-library/api-debugging.mdx#prettydom | ||
[query-functions]: ../queries/about.mdx | ||
|
||
#### `baseElement` | ||
|
||
The base DOM element that queries are bound to. Corresponds to | ||
`renderOptions.baseElement`. If you do not use `renderOptions.baseElement`, this | ||
will be `document.body`. | ||
|
||
#### `container` | ||
|
||
The DOM element the component is mounted in. Corresponds to | ||
`renderOptions.container`. If you do not use `renderOptions.container`, this | ||
will be `document.createElement('host')`. In general, avoid using `container` | ||
directly to query for elements; use [testing-library queries][query-functions] | ||
instead. | ||
|
||
#### `asFragment` | ||
|
||
Returns a `DocumentFragment` of your rendered component. This can be useful if | ||
you need to avoid live bindings and see how your component reacts to events. | ||
|
||
```tsx | ||
import {component$} from '@builder.io/qwik'; | ||
import {render} from '@testing-library/react'; | ||
import {userEvent} from "@testing-library/user-event"; | ||
|
||
const TestComponent = component$(() => { | ||
const count = useSignal(0); | ||
|
||
return ( | ||
<button onClick$={() => (count.value++))}> | ||
Click to increase: {count} | ||
</button> | ||
) | ||
}); | ||
|
||
const {getByText, asFragment} = await render(<TestComponent />) | ||
const firstRender = asFragment() | ||
|
||
userEvent.click(getByText(/Click to increase/)) | ||
|
||
// This will snapshot only the difference between the first render, and the | ||
// state of the DOM after the click event. | ||
// See https://github.com/jest-community/snapshot-diff | ||
expect(firstRender).toMatchDiffSnapshot(asFragment()) | ||
``` | ||
|
||
#### `debug` | ||
|
||
Log the `baseElement` or a given element using [`prettyDOM`][pretty-dom]. | ||
|
||
:::tip | ||
|
||
If your `baseElement` is the default `document.body`, we recommend using | ||
[`screen.debug`][screen-debug]. | ||
|
||
::: | ||
|
||
```tsx | ||
import {render, screen} from '@noma.to/qwik-testing-library' | ||
|
||
const {debug} = await render(<MyComponent />) | ||
|
||
const button = screen.getByRole('button') | ||
|
||
// log `document.body` | ||
screen.debug() | ||
|
||
// log your custom the `baseElement` | ||
debug() | ||
|
||
// log a specific element | ||
screen.debug(button) | ||
debug(button) | ||
``` | ||
|
||
[screen-debug]: ../dom-testing-library/api-debugging.mdx#screendebug | ||
|
||
#### `unmount` | ||
|
||
Unmount and destroy the Qwik component. | ||
|
||
```tsx | ||
const {unmount} = await render(<MyComponent />) | ||
|
||
unmount() | ||
``` | ||
|
||
#### Queries | ||
|
||
[Query functions][query-functions] bound to the [`baseElement`](#baseelement). | ||
If you passed [custom queries][custom-queries] to `render`, those will be | ||
available instead of the default queries. | ||
|
||
:::tip | ||
|
||
If your [`baseElement`](#baseelement) is the default `document.body`, we | ||
recommend using [`screen`][screen] rather than bound queries. | ||
|
||
::: | ||
|
||
```tsx | ||
import {render, screen} from '@noma.to/qwik-testing-library' | ||
|
||
const {getByRole} = await render(<MyComponent />) | ||
|
||
// query `document.body` | ||
const button = screen.getByRole('button') | ||
|
||
// query using a custom `target` or `baseElement` | ||
const button = getByRole('button') | ||
``` | ||
|
||
[screen]: ../queries/about.mdx#screen | ||
|
||
## `cleanup` | ||
|
||
Destroy all components and remove any elements added to `document.body` or | ||
`renderOptions.baseElement`. | ||
|
||
```tsx | ||
import {render, cleanup} from '@noma.to/qwik-testing-library' | ||
|
||
// Default: runs after each test | ||
afterEach(() => { | ||
cleanup() | ||
}) | ||
|
||
await render(<MyComponent />) | ||
|
||
// Called manually for more control | ||
cleanup() | ||
``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
--- | ||
id: example | ||
title: Example | ||
sidebar_label: Example | ||
--- | ||
|
||
Below are some examples of how to use the Qwik Testing Library to tests your | ||
Qwik components. | ||
|
||
You can also learn more about the [**queries**][tl-queries-docs] and [**user | ||
events**][tl-user-events-docs] to help you write your tests. | ||
|
||
[tl-queries-docs]: ../queries/about.mdx | ||
[tl-user-events-docs]: ../user-event/intro.mdx | ||
|
||
## Qwikstart | ||
timdeschryver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
This is a minimal setup to get you started, with line-by-line explanations. | ||
|
||
```tsx title="counter.spec.tsx" | ||
// import qwik-testing methods | ||
import {screen, render, waitFor} from '@noma.to/qwik-testing-library' | ||
// import the userEvent methods to interact with the DOM | ||
import {userEvent} from '@testing-library/user-event' | ||
// import the component to be tested | ||
import {Counter} from './counter' | ||
|
||
// describe the test suite | ||
describe('<Counter />', () => { | ||
// describe the test case | ||
it('should increment the counter', async () => { | ||
// setup user event | ||
const user = userEvent.setup() | ||
// render the component into the DOM | ||
await render(<Counter />) | ||
|
||
// retrieve the 'increment count' button | ||
const incrementBtn = screen.getByRole('button', {name: /increment count/}) | ||
// click the button twice | ||
await user.click(incrementBtn) | ||
await user.click(incrementBtn) | ||
|
||
// assert that the counter is now 2 | ||
expect(await screen.findByText(/count:2/)).toBeInTheDocument() | ||
}) | ||
}) | ||
``` | ||
|
||
## Qwik City - `server$` calls | ||
|
||
If one of your Qwik components uses `server$` calls, your tests might fail with | ||
a rather cryptic message (e.g. | ||
`QWIK ERROR __vite_ssr_import_0__.myServerFunctionQrl is not a function` or | ||
`QWIK ERROR Failed to parse URL from ?qfunc=DNpotUma33o`). | ||
|
||
We're happy to discuss it on [Discord][discord], but we consider this failure to | ||
ianlet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
be a good thing: your components should be tested in isolation, so you will be | ||
forced to mock your server functions. | ||
|
||
[discord]: https://qwik.dev/chat | ||
|
||
Here is an example of how to test a component that uses `server$` calls: | ||
|
||
```ts title="~/server/blog-post.ts" | ||
import {server$} from '@builder.io/qwik-city' | ||
import {BlogPost} from '~/lib/blog-post' | ||
|
||
export const getLatestPosts$ = server$(function (): Promise<BlogPost> { | ||
// get the latest posts | ||
return Promise.resolve([]) | ||
}) | ||
``` | ||
|
||
```tsx title="~/components/latest-post-list.tsx" | ||
import {render, screen, waitFor} from '@noma.to/qwik-testing-library' | ||
import {LatestPostList} from './latest-post-list' | ||
|
||
vi.mock('~/server/blog-posts', () => ({ | ||
// the mocked function should end with `Qrl` instead of `$` | ||
getLatestPostsQrl: () => { | ||
return Promise.resolve([ | ||
{id: 'post-1', title: 'Post 1'}, | ||
{id: 'post-2', title: 'Post 2'}, | ||
]) | ||
}, | ||
})) | ||
|
||
describe('<LatestPostList />', () => { | ||
it('should render the latest posts', async () => { | ||
await render(<LatestPostList />) | ||
|
||
expect(await screen.findAllByRole('listitem')).toHaveLength(2) | ||
}) | ||
}) | ||
``` | ||
|
||
Notice how the mocked function is ending with `Qrl` instead of `$`, despite | ||
ianlet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
being named as `getLatestPosts$`. This is caused by the Qwik optimizer renaming | ||
it to `Qrl`. So, we need to mock the `Qrl` function instead of the original `$` | ||
one. | ||
|
||
If your function doesn't end with `$`, the Qwik optimizer will not rename it to | ||
`Qrl`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
--- | ||
id: faq | ||
title: FAQ | ||
sidebar_label: FAQ | ||
--- | ||
|
||
- [How do I test file upload?](#how-do-i-test-file-upload) | ||
|
||
--- | ||
|
||
## How do I test file upload? | ||
|
||
Use the [upload][] utility from `@testing-library/user-event`. It works well in | ||
both [jsdom][] and [happy-dom][]. | ||
|
||
```tsx | ||
test('upload file', async () => { | ||
const user = userEvent.setup() | ||
|
||
await render(<Uploader />) | ||
const file = new File(['hello'], 'hello.png', {type: 'image/png'}) | ||
const input = screen.getByLabelText(/upload file/i) | ||
|
||
await user.upload(input, file) | ||
|
||
expect(input.files[0]).toBe(file) | ||
expect(input.files.item(0)).toBe(file) | ||
expect(input.files).toHaveLength(1) | ||
}) | ||
``` | ||
|
||
[upload]: ../user-event/api-utility.mdx#upload | ||
[jsdom]: https://github.com/jsdom/jsdom | ||
[happy-dom]: https://github.com/capricorn86/happy-dom |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
--- | ||
id: intro | ||
title: Intro | ||
sidebar_label: Introduction | ||
--- | ||
|
||
[Qwik Testing Library on GitHub][gh] | ||
|
||
[gh]: https://github.com/ianlet/qwik-testing-library | ||
|
||
```bash npm2yarn | ||
npm run qwik add testing-library | ||
``` | ||
|
||
> This library is built on top of [`dom-testing-library`][dom-testing-library] | ||
> which is where most of the logic behind the queries is. | ||
|
||
[dom-testing-library]: ../dom-testing-library/intro.mdx | ||
|
||
## The Problem | ||
|
||
You want to write maintainable tests for your [Qwik][qwik] components. | ||
|
||
[qwik]: https://qwik.dev/ | ||
|
||
## This Solution | ||
|
||
The Qwik Testing Library is a lightweight library for testing Qwik components. | ||
It provides functions on top of `qwik` and `@testing-library/dom` so you can | ||
mount Qwik components and query their rendered output in the DOM. Its primary | ||
guiding principle is: | ||
|
||
> [The more your tests resemble the way your software is used, the more | ||
> confidence they can give you.][guiding-principle] | ||
|
||
[guiding-principle]: https://twitter.com/kentcdodds/status/977018512689455106 | ||
|
||
**What this library is not**: | ||
|
||
1. A test runner or framework. | ||
2. Specific to a testing framework. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.