Skip to content

docs(solid-testing-library): add solid testing library docs #1370

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
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions docs/dom-testing-library/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ install the wrapper:
- [Puppeteer Testing Library](pptr-testing-library/intro.mdx)
- [Testcafe Testing Library](testcafe-testing-library/intro.mdx)
- [Nightwatch Testing Library](nightwatch-testing-library/intro.mdx)
- [Solid Testing Library](solid-testing-library/intro.mdx)

## Ecosystem

Expand Down
181 changes: 181 additions & 0 deletions docs/solid-testing-library/api.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
---
id: api
title: API
sidebar_label: API
---

Due to being inspired by the [preact-testing-library][preact-docs] you can check
its page for more information.

[preact-docs]: ../preact-testing-library/intro.mdx

There are several key differences, to be aware of.

- [`render`](#render)
- [`renderHook`](#renderHook)
- [`renderDirective`](#renderDirective)
- [`Async methods`](#async-methods)
- [`Know issues`](#known-issues)

## `render`

The `render` function takes in a function that returns a Solid Component, rather
than simply the component itself.

```tsx
const results = render(() => <YourComponent />, options)
```

Solid.js does _not_ re-render, it merely executes side effects triggered by
reactive state that change the DOM, therefore there is no `rerender` method. You
can use global signals to manipulate your test component in a way that causes it
to update.

In addition to the original API, the render function of this testing library
supports a convenient `location` option that will set up an in-memory router
pointing at the specified location. Since this setup is not synchronous, you
need to first use asynchronous queries (`findBy`) after employing it:

```tsx
it('uses params', async () => {
const App = () => (
<>
<Route path="/ids/:id" component={() => <p>Id: {useParams()?.id}</p>} />
<Route path="/" component={() => <p>Start</p>} />
</>
)
const {findByText} = render(() => <App />, {location: 'ids/1234'})
expect(await findByText('Id: 1234')).not.toBeFalsy()
})
```

It uses `@solidjs/router`, so if you want to use a different router, you should
consider the `wrapper` option instead. If you attempt to use this without having
the package installed, you will receive an error message.

## `renderHook`

Solid.js external reactive state does not require any DOM elements to run in, so
our `renderHook` call to test hooks in the context of a component (if your hook
does not require the context of a component, `createRoot` should suffice to test
the reactive behavior; for convenience, we also have `createEffect`, which is
described in the [`Async methods`](#async-methods) section) has no `container`,
`baseElement` or queries in its options or return value. Instead, it has an
`owner` to be used with
[`runWithOwner`](https://www.solidjs.com/docs/latest/api#runwithowner) if
required. It also exposes a `cleanup` function, though this is already
automatically called after the test is finished.

```ts
function renderHook<Args extends any[], Result>(
hook: (...args: Args) => Result,
options: {
initialProps?: Args,
wrapper?: Component<{ children: JSX.Element }>
}
) => {
result: Result;
owner: Owner | null;
cleanup: () => void;
}
```

This can be used to easily test a hook / primitive:

```ts
const {result} = renderHook(createResult)
expect(result).toBe(true)
```

If you are using a `wrapper` with `renderHook`, make sure it will **always**
return `props.children` - especially if you are using a context with
asynchronous code together with `<Show>`, because this is required to get the
value from the hook and it is only obtained synchronously once and you will
otherwise only get `undefined` and wonder why this is the case.

## `renderDirective`

Solid.js supports
[custom directives](https://www.solidjs.com/docs/latest/api#use___), which is a
convenient pattern to tie custom behavior to elements, so we also have a
`renderDirective` call, which augments `renderHook` to take a directive as first
argument, accept an `initialValue` for the argument and a `targetElement`
(string, HTMLElement or function returning an HTMLElement) in the `options` and
also returns `arg` and `setArg` to read and manipulate the argument of the
directive.

```ts
function renderDirective<
Arg extends any,
Elem extends HTMLElement
>(
directive: (ref: Elem, arg: Accessor<Arg>) => void,
options?: {
...renderOptions,
initialValue: Arg,
targetElement:
| Lowercase<Elem['nodeName']>
| Elem
| (() => Elem)
}
): Result & { arg: Accessor<Arg>, setArg: Setter<Arg> };
```

This allows for very effective and concise testing of directives:

```ts
const {asFragment, setArg} = renderDirective(myDirective)
expect(asFragment()).toBe('<div data-directive="works"></div>')
setArg('perfect')
expect(asFragment()).toBe('<div data-directive="perfect"></div>')
```

## Async methods

Solid.js reactive changes are pretty instantaneous, so there is rarely need to
use `waitFor(…)`, `await findByRole(…)` and other asynchronous queries to test
the rendered result, except for transitions, suspense, resources and router
navigation.

Solid.js manages side effects with different variants of `createEffect`. While
you can use `waitFor` to test asynchronous effects, it uses polling instead of
allowing Solid's reactivity to trigger the next step. In order to simplify
testing those asynchronous effects, we have a `testEffect` helper that
complements the hooks for directives and hooks:

```ts
testEffect(fn: (done: (result: T) => void) => void, owner?: Owner): Promise<T>

// use it like this:
test("testEffect allows testing an effect asynchronously", () => {
const [value, setValue] = createSignal(0);
return testEffect(done => createEffect((run: number = 0) => {
if (run === 0) {
expect(value()).toBe(0);
setValue(1);
} else if (run === 1) {
expect(value()).toBe(1);
done();
}
return run + 1;
}));
});
```

It allows running the effect inside a defined owner that is received as an
optional second argument. This can be useful in combination with `renderHook`,
which gives you an owner field in its result. The return value is a Promise with
the value given to the `done()` callback. You can either await the result for
further assertions or return it to your test runner.

## Known issues

If you are using [`vitest`](https://vitest.dev/), then tests might fail, because
the packages `solid-js`, and `@solidjs/router` (if used) need to be loaded only
once, and they could be loaded both through the internal `vite` server and
through node. Typical bugs that happen because of this is that dispose is
supposedly undefined, or the router could not be loaded.

Since version 2.8.2, our vite plugin has gained the capability to configure
everything for testing, so you should only need extra configuration for globals,
coverage, etc.
53 changes: 53 additions & 0 deletions docs/solid-testing-library/intro.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
id: intro
title: Intro
sidebar_label: Introduction
---

[Solid Testing Library on GitHub][gh]

> Inspired completely by [preact-testing-library][preact-docs]

[preact-docs]: ../preact-testing-library/intro.mdx
[gh]: https://github.com/solidjs/solid-testing-library

```bash npm2yarn
npm install --save-dev @solidjs/testing-library
```

> This library is built on top of
> [`DOM Testing Library`](dom-testing-library/intro.mdx) which is where most of
> the logic behind the queries is.

## The Problem

You want to write tests for your Solid components so that they avoid including
implementation details, and are maintainable in the long run.

## This Solution

The Solid Testing Library is a very lightweight solution for testing Solid
components. Its primary guiding principle is:

> [The more your tests resemble the way your software is used, the more confidence they can give you.](https://twitter.com/kentcdodds/status/977018512689455106)

See the [Dom introduction][dom-solution-explainer] and [React
introduction][react-solution-explainer] for a more in-depth explanation.

[dom-solution-explainer]: ../dom-testing-library/intro.mdx#this-solution
[react-solution-explainer]: ../react-testing-library/intro.mdx#this-solution

**What this library is not**:

1. A test runner or framework.
2. Specific to a testing framework.

If you using Jest we recommend using
[solid-jest](https://github.com/solidjs/solid-jest) to properly resolve the
browser version of Solid as Jest will default to the server version when run in
Node.

💡 If you are using Jest or vitest, you may also be interested in installing
`@testing-library/jest-dom` so you can use [the custom jest matchers][jest-dom].

[jest-dom]: ../ecosystem-jest-dom
3 changes: 3 additions & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,6 @@
[[redirects]]
from = "/user-event/"
to = "/docs/user-event/intro"
[[redirects]]
from = "/solid/"
to = "/docs/solid-testing-library/intro"
6 changes: 6 additions & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ module.exports = {
'react-native-testing-library/setup',
],
},
{
'Solid Testing Library': [
'solid-testing-library/intro',
'solid-testing-library/api',
],
},
'cypress-testing-library/intro',
'pptr-testing-library/intro',
'testcafe-testing-library/intro',
Expand Down