Skip to content

Commit 4253cbe

Browse files
DylanPierceyalexkrolick
authored andcommitted
docs: add marko testing library (#159)
* docs: add marko testing library * fix: address review comments * docs: document event handler testing * fix: address review comments
1 parent 103e4c4 commit 4253cbe

File tree

4 files changed

+421
-0
lines changed

4 files changed

+421
-0
lines changed

docs/marko-testing-library/api.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
id: api
3+
title: API
4+
---
5+
6+
`Marko Testing Library` re-exports everything from `DOM Testing Library` as well
7+
as these methods:
8+
9+
- [`render`](#render)
10+
- [`cleanup`](#cleanup)
11+
12+
---
13+
14+
## `render`
15+
16+
```javascript
17+
function render(
18+
template, // A Marko template to render
19+
input, // Input for the above template
20+
options // You won't often use this, expand below for docs on options
21+
)
22+
```
23+
24+
Render into a container which is appended to `document.body`.
25+
26+
```javascript
27+
import { render } from '@marko/testing-library'
28+
import MyTemplate from './my-template.marko'
29+
30+
render(MyTemplate)
31+
```
32+
33+
```javascript
34+
import { render, cleanup } from '@marko/testing-library'
35+
import 'jest-dom/extend-expect'
36+
import Greeting from './greeting.marko'
37+
38+
afterEach(cleanup)
39+
40+
test('renders a message', async () => {
41+
const { container, getByText } = await render(Greeting, { name: 'Marko' })
42+
expect(getByText(/Marko/)).toBeInTheDocument()
43+
expect(container.firstChild).toMatchInlineSnapshot(`
44+
<h1>Hello, Marko!</h1>
45+
`)
46+
})
47+
```
48+
49+
> Note
50+
>
51+
> The [cleanup](#cleanup) function should be called between tests to remove the
52+
> created DOM nodes and keep the tests isolated.
53+
54+
### `render` Options
55+
56+
You won't often need to specify options, but if you ever do, here are the
57+
available options which you could provide as the third argument to `render`.
58+
59+
#### `container`
60+
61+
By default for client side tests, `Marko Testing Library` will create a `div`
62+
and append that `div` to the `document.body` and this is where your component
63+
will be rendered. If you provide your own HTMLElement `container` via this
64+
option, it will not be appended to the `document.body` automatically.
65+
66+
For example: If you are unit testing a `tablebody` element, it cannot be a child
67+
of a `div`. In this case, you can specify a `table` as the render `container`.
68+
69+
```javascript
70+
const table = document.createElement('table')
71+
72+
const { container } = await render(MyTableBody, null, {
73+
container: document.body.appendChild(table),
74+
})
75+
```
76+
77+
## `render` Result
78+
79+
The `render` method returns a promise which resolves with an object that has a
80+
few properties:
81+
82+
### `...queries`
83+
84+
The most important feature of `render` is that the queries from
85+
[DOM Testing Library](dom-testing-library/api-queries.md) are automatically
86+
returned with their first argument bound to the results of rendering your
87+
component.
88+
89+
See [Queries](dom-testing-library/api-queries.md) for a complete list.
90+
91+
**Example**
92+
93+
```javascript
94+
const { getByLabelText, queryAllByTestId } = await render(MyTemplate)
95+
```
96+
97+
### `debug`
98+
99+
This method is a shortcut for logging the `prettyDOM` for all children inside of
100+
the `container`.
101+
102+
```javascript
103+
import { render } from '@marko/testing-library'
104+
import Greeting from './greeting.marko'
105+
106+
const { debug } = await render(Greeting, { name: 'World' })
107+
debug()
108+
109+
// <h1>Hello World</h1>
110+
// you can also pass an element: debug(getByTestId('messages'))
111+
```
112+
113+
This is a simple wrapper around `prettyDOM` which is also exposed and comes from
114+
[`DOM Testing Library`](https://github.com/testing-library/dom-testing-library/blob/master/README.md#prettydom).
115+
116+
### `rerender`
117+
118+
A Marko components `input` can change at any time from a parent component.
119+
Although often this input is passed through your component declaratively,
120+
sometimes it is necessary to ensure that your components reacts appropriately to
121+
new data. You can simulate your component receiving new `input` by passing new
122+
data to the `rerender` helper.
123+
124+
```javascript
125+
import { render } from '@marko/testing-library'
126+
import Greeting from './greeting.marko'
127+
128+
const { rerender, debug } = await render(Greeting, { name: 'World' })
129+
130+
// re-render the same component with different props
131+
await rerender({ name: 'Marko' })
132+
133+
debug()
134+
// <h1>Hello Marko</h1>
135+
```
136+
137+
### `emitted`
138+
139+
Marko components also communicate with their parents through events. It is
140+
recommended to also test that your components emit the right events at the right
141+
time.
142+
143+
The `emitted` helper does just that. Calling the helper will return all emitted
144+
events since the last call to the helper. You can also pass in an event type to
145+
filter the results.
146+
147+
```javascript
148+
import { render, fireEvent } from '@marko/testing-library'
149+
import Counter from './counter.marko'
150+
151+
const { getByText, emitted } = await render(Counter)
152+
153+
const button = getByText('Increment')
154+
155+
fireEvent.click(button)
156+
fireEvent.click(button)
157+
158+
// Assuming the `Counter` component forwards these button clicks as `increment` events
159+
expect(emitted('increment')).toHaveProperty('length', 2)
160+
161+
fireEvent.click(button)
162+
163+
// Note: the tracked events are cleared every time you read them.
164+
// Below we are snapshoting the events after our last assertion,
165+
// the return value will include an array with all of the arguments for each increment event.
166+
expect(emitted('increment')).toMatchInlineSnapshot(`
167+
Array [
168+
Array [
169+
Object {
170+
"count": 3,
171+
},
172+
],
173+
]
174+
`)
175+
176+
// Without an event type will give you all events with their type and arguments.
177+
expect(emitted()).toMatchInlineSnapshot(`
178+
Array [
179+
Object {
180+
"args": Array [
181+
Object {
182+
"count": 0,
183+
},
184+
],
185+
"type": "increment",
186+
},
187+
Object {
188+
"args": Array [
189+
Object {
190+
"count": 1,
191+
},
192+
],
193+
"type": "increment",
194+
},
195+
Object {
196+
"args": Array [
197+
Object {
198+
"count": 3,
199+
},
200+
],
201+
"type": "increment",
202+
}
203+
]
204+
`)
205+
```
206+
207+
### `container`
208+
209+
The containing DOM node of your rendered Marko Component. For server side tests
210+
this is a [JSDOM.fragment](), and for client side tests this will be whatever is
211+
passed as the `container` render option.
212+
213+
> Tip: To get the root element of your rendered element, use
214+
> `container.firstChild`.
215+
216+
> 🚨 If you find yourself using `container` to query for rendered elements then
217+
> you should reconsider! The other queries are designed to be more resilient to
218+
> changes that will be made to the component you're testing. Avoid using
219+
> `container` to query for elements!
220+
221+
## `cleanup`
222+
223+
With client side tests your components are rendered into a placeholder
224+
HTMLElement. To ensure that your components are properly removed, and destroyed,
225+
you can call `cleanup` at any time which will remove any attached components.
226+
227+
```javascript
228+
import { cleanup } from '@marko/testing-library'
229+
// automatically unmount and cleanup DOM after the test is finished.
230+
afterEach(cleanup)
231+
```
232+
233+
To save some typing, you could also import a file with the above.
234+
235+
```javascript
236+
import '@marko/testing-library/cleanup-after-each'
237+
```
238+
239+
If you are using Jest, you can simply include the following to your Jest config
240+
to avoid doing this in each file.
241+
242+
```javascript
243+
module.exports = {
244+
...,
245+
setupFilesAfterEnv: ['@marko/testing-library/cleanup-after-each']
246+
}
247+
```

docs/marko-testing-library/intro.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
id: intro
3+
title: Marko Testing Library
4+
sidebar_label: Introduction
5+
---
6+
7+
The [`Marko Testing Library`](gh) is a very lightweight solution for testing
8+
Marko components. It provides light utility functions on top of
9+
[`@testing-library/dom`](https://github.com/testing-library/dom-testing-library)
10+
in a way that encourages better testing practices.
11+
12+
```
13+
npm install --save-dev @marko/testing-library
14+
```
15+
16+
- [Marko Testing Library on GitHub][gh]
17+
18+
[gh]: https://github.com/marko-js/testing-library
19+
20+
## The problem
21+
22+
You want to write maintainable tests for your Marko components. As a part of
23+
this goal, you want your tests to avoid including implementation details of your
24+
components and rather focus on making your tests give you the confidence for
25+
which they are intended. As part of this, you want your testbase to be
26+
maintainable in the long run so refactors of your components (changes to
27+
implementation but not functionality) don't break your tests and slow you and
28+
your team down.
29+
30+
## This solution
31+
32+
The `@marko/testing-library` is a very lightweight solution for testing Marko
33+
components. It provides light utility functions on top of
34+
[`@testing-library/dom`](https://github.com/testing-library/dom-testing-library)
35+
in a way that encourages better testing practices. Its primary guiding principle
36+
is:
37+
38+
> [The more your tests resemble the way your software is used, the more confidence they can give you.](#guiding-principles)
39+
40+
So rather than dealing with instances of rendered Marko components, your tests
41+
will work with actual DOM nodes. The utilities this library provides facilitate
42+
querying the DOM in the same way the user would. Finding for elements by their
43+
label text (just like a user would), finding links and buttons from their text
44+
(like a user would). It contains a small targeted API and can get out of your
45+
way if absolutely needed with some built in escape hatches.
46+
47+
This library encourages your applications to be more accessible and allows you
48+
to get your tests closer to using your components the way a user will, which
49+
allows your tests to give you more confidence that your application will work
50+
when a real user uses it.
51+
52+
**What this library is not**:
53+
54+
1. A test runner or framework
55+
2. Specific to a testing framework, you can [use it with Jest](./setup#jest),
56+
[mocha](./setup#mocha), or other test runners.
57+
58+
> NOTE: This library is built on top of
59+
> [`DOM Testing Library`](dom-testing-library/intro.md) which is where most of
60+
> the logic behind the queries is.

0 commit comments

Comments
 (0)