Skip to content

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 5 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions docs/qwik-testing-library/api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
---
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, the component will be
rendered into a `<host>` appended to `document.body`.

```tsx
import {render} from '@noma.to/qwik-testing-library'
import {MyComponent} from './MyComponent'

const result = await render(<MyComponent />, renderOptions)
```

### 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()
```
101 changes: 101 additions & 0 deletions docs/qwik-testing-library/example.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
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

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 () => {
// 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 userEvent.click(incrementBtn)
await userEvent.click(incrementBtn)

// assert that the counter is now 2
await waitFor(() => expect(screen.getByText(/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
be a good thing: your components should be tested in isolation, so you will be
forced to mock your server functions.

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 />)

await waitFor(() =>
expect(screen.queryAllByRole('listitem')).toHaveLength(2),
)
})
})
```

Notice how the mocked function is ending with `Qrl` instead of `$`, despite
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 `server$` function doesn't end with `$`, the Qwik optimizer might not
rename it to `Qrl`.
34 changes: 34 additions & 0 deletions docs/qwik-testing-library/faq.mdx
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
41 changes: 41 additions & 0 deletions docs/qwik-testing-library/intro.mdx
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.
Loading