Skip to content

Commit

Permalink
docs(vitest): add Vitest option to testing section (#1407)
Browse files Browse the repository at this point in the history
* docs(vitest): add Vitest option to testing section

* Update docs/testing/03-vitest.md

Co-authored-by: Alice Pote <alice.writes.wrongs@gmail.com>

* properly annotate file names

* PR feedback

---------

Co-authored-by: Alice Pote <alice.writes.wrongs@gmail.com>
  • Loading branch information
christian-bromann and alicewriteswrongs authored May 9, 2024
1 parent 5ba1966 commit 4338513
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 3 deletions.
180 changes: 180 additions & 0 deletions docs/testing/03-vitest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
title: Vitest
position: 5
---

# Overview

[Vitest](https://vitest.dev/) is a popular and modern test framework for unit testing. You can use Vitest to test Stencil components in the browser using its [browser mode feature](https://vitest.dev/guide/browser.html).

:::caution
Vitest browser mode is an experimental feature and in early development. As such, it may not yet be fully optimized, and there may be some bugs or issues that have not yet been ironed out.
:::

## Set Up

To get started with Vitest, all you need to install it via:

```bash npm2yarn
npm install vitest @vitest/browser unplugin-stencil webdriverio
```

This command installs:

- `vitest`: the core test framework
- `@vitest/browser`: enables testing in browser environments
- `unplugin-stencil`: integrates Stencil's compiler with Vitest for seamless testing
- `webdriverio`: facilitates browser management for tests

Next, we create a Vitest configuration as following:

```ts title="vitest.config.ts"
import stencil from 'unplugin-stencil/vite'
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
browser: {
enabled: true,
headless: true,
name: 'chrome'
},
},
plugins: [stencil()]
})
```

This configuration enables tests to run in a headless Chrome browser.

## Writing Tests

Once you've setup Vitest you can start write your first test. In order to render a Stencil component into the browser, all you need to do is import the component and initiate an instance of the component on the page:

```ts title="src/components/my-component/my-component.test.ts"
import { expect, test } from 'vitest'

import '../src/components/my-component/my-component.js'

test('should render component correctly', async () => {
const cmp = document.createElement('my-component')
cmp.setAttribute('first', 'Stencil')
cmp.setAttribute('last', `'Don't call me a framework' JS`)
document.body.appendChild(cmp)

await new Promise((resolve) => requestIdleCallback(resolve))
expect(cmp.shadowRoot?.querySelector('div')?.innerText)
.toBe(`Hello, World! I'm Stencil 'Don't call me a framework' JS`)
})
```

Lastly, let's add a Vitest script to our `package.json`:

```json
{
"scripts": {
"test": "vitest --run"
},
}
```

Execute the tests using:

```bash npm2yarn
npm test
```

Expected output:

```
❯ npm test
> vitest@1.0.0 test
> vitest --run
The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.
RUN v1.5.0 /private/tmp/vitest
Browser runner started at http://localhost:5173/
[19:39.9] build, vitest, prod mode, started ...
[19:39.9] transpile started ...
[19:40.0] transpile finished in 72 ms
[19:40.0] generate custom elements + source maps started ...
[19:40.1] generate custom elements + source maps finished in 137 ms
[19:40.1] build finished in 227 ms
✓ src/components/my-component/my-component.test.ts (1)
✓ should render component correctly
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 14:19:36
Duration 3.19s (transform 0ms, setup 0ms, collect 721ms, tests 22ms, environment 0ms, prepare 0ms)
```

### Use JSX

The example above creates the Stencil instance using basic DOM primitives. If you prefer to use JSX also for rendering Stencil components in your test, just create a `jsx.ts` utility file with the following content:

```ts title="src/utils/jsx.ts"
export const createElement = (tag, props, ...children) => {
if (typeof tag === 'function') {
return tag(props, ...children)
}
const element = document.createElement(tag)

Object.entries(props || {}).forEach(([name, value]) => {
if (name.startsWith('on') && name.toLowerCase() in window) {
element.addEventListener(name.toLowerCase().substr(2), value)
} else {
element.setAttribute(name, value.toString())
}
})

children.forEach((child) => {
appendChild(element, child)
})

return element
}

export const appendChild = (parent, child) => {
if (Array.isArray(child)) {
child.forEach((nestedChild) => appendChild(parent, nestedChild))
} else {
parent.appendChild(child.nodeType ? child : document.createTextNode(child))
}
}

export const createFragment = (_, ...children) => {
return children
}
```

Now update your test, import the `createElement` method and tell the JSX engine to use that method for rendering the JSX snippet. Our test should look as follows:

```tsx title="src/components/my-component/my-component.test.tsx"
/** @jsx createElement */
import { expect, test } from 'vitest'

import { createElement } from '../../utils/jsx'
import './my-component.js'

test('should render the component with jsx', async () => {
const cmp = <my-component first="Stencil" last="'Don't call me a framework' JS"></my-component>
document.body.appendChild(cmp)
await new Promise((resolve) => requestIdleCallback(resolve))
expect(cmp.shadowRoot?.querySelector('div')?.innerText)
.toBe(`Hello, World! I'm Stencil 'Don't call me a framework' JS`)
})
```

__Note:__ the `/** @jsx createElement */` at the top of the file tells JSX which rendering function it should use to parse the JSX snippet.

## Limitations

Be aware of the following limitations, when using Vitest as test framework for testing Stencil components:

- __Mocking not yet supported__: you can't mock any files or dependencies when running with the Stencil browser feature
- __No auto-waits__: in order to ensure that the component is rendered, you have to manually wait via, e.g. calling `await new Promise((resolve) => requestIdleCallback(resolve))`
3 changes: 2 additions & 1 deletion docs/testing/playwright/_category_.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"label": "Playwright"
"label": "Playwright",
"position": 2
}
2 changes: 1 addition & 1 deletion docs/testing/stencil-testrunner/_category_.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"label": "Stencil Test Runner",
"position": 9
"position": 1
}
3 changes: 2 additions & 1 deletion docs/testing/webdriverio/_category_.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"label": "WebdriverIO"
"label": "WebdriverIO",
"position": 2
}

0 comments on commit 4338513

Please sign in to comment.