This article covers testing Riot Input components using Vitest in a JsDOM environment.
A second method exists: Vitest into a Node environment with a Riot Server-Side Rendering, read this article to learn more.
Before going forward, ensure you have a base Riot+Vite project and have created at least one component. If not, you can read my previous article on creating an Input Component.
These articles form a series focusing on RiotJS paired with BeerCSS, designed to guide you through creating components and mastering best practices for building production-ready applications. I assume you have a foundational understanding of Riot; however, feel free to refer to the documentation if needed: https://riot.js.org/documentation/
Riot + Vitest + JsDOM: Next Generation Testing Framework
As I am using Vite as a development server to get a real-time rendering of my Riot application, using Vitest for testing brings many advantages:
- A test runner that uses the same configuration as Vite (vite.config.js).
- It provides a compatible Jest API (one of the most used test runners)
- Performances: it uses Worker threads to run as much as possible in parallel.
- Easy setup and configuration (almost none).
The default environment in Vitest is a Node.js environment. As we build a web application, we can use a browser-like environment through JsDom, which emulates a web browser for testing Riot Components.
Add Vitest add JsDom to your Riot project:
npm install -D vitest jsdom
To execute the test, add the following section to your package.json:
{
"scripts": {
"test": "vitest"
}
}
And that's it for Vitest! If you need a specific configuration, add test
property in your Vite config vite.config.js. Configuration documentation: https://vitest.dev/config/
Let's create a test file for the Input component named test/c-input.test.js
. Vitest will automatically detect and tests files with the file name patterns **/*.test.js
.
As mentioned earlier, the default environment for Vitest is Node, and by adding a @vitest-environment
comment at the top of the file, it specifies another environment to be used for all tests in that file:
/**
* @vitest-environment jsdom
*/
Voilà, the file is ready for testing.
First simple input test
This section describes creating the most basic test for an input component.
Here is the Input component used in a Riot application, without value and props:
<c-input/>
The generated HTML for the component is:
<div class="field border">
<input type="text">
</div>
The test for the Input component:
/**
* @vitest-environment jsdom
*/
import { assert, describe, it } from 'vitest'
import * as riot from 'riot'
import cInput from '../components/c-input.riot'
describe('Component c-input', () => {
it('should render the input without props', () => {
riot.register('c-input', cInput);
const [component] = riot.mount(document.createElement('div'), {}, 'c-input')
assert.strictEqual(component.root.querySelector('div').className.trim(), 'field border');
assert.strictEqual(component.root.querySelector('input').type, 'text');
assert.strictEqual(component.root.querySelector('input').value, '');
riot.unregister('c-input');
})
})
Code Breakdown:
- The input component and modules are loaded.
- Vitest provides common utilities for testing, similar to Mocha and Jest:
- describe() is used to define a group of tests
- it() for defining a test
- assert() for validating tests
- To use the component, it must be registered globally with riot.register(). It takes two arguments:
- First: The component name
- Second: The component wrapper
- To load the component into the Dom, it must be mounted with riot.mount(). It takes three arguments:
- First, a selector selects elements from the page and mounts them with a custom component. In our case, it creates a new
div
element and returns a selector. - Second: An optional object is passed for the component to consume, such as an Input value, error, helper, label, and more.
- Third: The component name we want to mount, in our case,
c-input
.
- First, a selector selects elements from the page and mounts them with a custom component. In our case, it creates a new
- The mount function returns an array of all mounted components on the Dom. As in our case, we have only one, we are destructuring the array to get our component object.
- Thanks to the querySelector, we can access all attributes, tags, and values from the component object. For instance, we can retrieve the div element:
component.root.querySelector('div')
. Here is what you can verify:-
Input value:
component.root.querySelector('input').value
-
Class names:
component.root.querySelector('input').className
-
Type:
component.root.querySelector('input').type
-
Content of a tag:
component.root.querySelector('input').textContent
-
Input value:
- Verify all expected results with the expression
assert.strictEqual(result, expected)
. - Finally, unregister the input tag
c-input
with riot.unregister(). This method is required to create another test with the same tag name.
👉 Tips: for creating the test, I used
console.log(component.root.innerHTML)
, which gives the raw HTML of component
Now execute the test through the NPM command:
npm run test
The result on the console:
✓ tests/c-input.jsdom.test.js
✓ Component c-input
✓ should render the input without props
Test Files 1 passed
Tests 1 passed
Start at 14:10:32
Duration 36ms
✅ The test succeeds; everything is good. Vitest is listening to changes, and it will print the result when a new test is created.
Advanced Test
Now we can replicate this testing method for all props and multiple props combined: Let's create a password input with a "Password" type, label, a value, and an error.
Here is the Input component that will be created for the Riot Application:
<c-input label="Passport" type="passport" value="1234" error="The password is too show, minimum 20 characters." />
The generated HTML for the component is:
<div class=" field border invalid label">
<input type="password">
<label>Password</label>
<span class="error">The password is too show, minimum 20 characters.</span>
</div>
Here is the corresponding test to register the component, mount it with all properties, and verify each HTML tag and attribute:
it('should render multiple props: label, type, error and round', () => {
riot.register('c-input', cInput);
const _props = { value: "1234", label: "Password", type: "password", error: "The password is too show, minimum 20 characters." }
const [component] = riot.mount(document.createElement('div'), _props, 'c-input')
const divElement = component.root.querySelector('div')
assert.strictEqual(divElement.className.replace(/\s+/g,' ').trim(), 'field border invalid label');
const inputElement = component.root.querySelector('input')
assert.strictEqual(inputElement.value, _props.value);
assert.strictEqual(inputElement.type, _props.type);
const labelElement = component.root.querySelector('label')
assert.strictEqual(labelElement.textContent, _props.label);
const spanElement = component.root.querySelector('span')
assert.strictEqual(spanElement.textContent, _props.error);
assert.strictEqual(spanElement.className, 'error');
riot.unregister('c-input');
})
Code Breakdown:
- I will not describe what I mentioned on the first test above ⬆️
- The Component logic is printing tags and attributes conditionally, and we must check every element.
- Instead of calling multiple times
querySelector('span')
, the result is stored in a variable to re-use it for each assert expression. - Checking the className requires to remove all extra whitespace with
.replace(/\s+/g,' ').trim()
. The component has conditions to add a class; if a class does not exist, it will leave a space character.
The test pass ✅
Find all Riot tests with Vitest and JsDom the following GitHub repository: https://github.com/steevepay/riot-beercss/blob/main/tests/c-input.jsdom.test.js
Conclusion
Combining Riot with Vitest and JsDom is a good solution for testing the rendering of Riot Components in a Browser environment. It requires knowledge about manipulating HTML elements with Javascript and is a bit verbose. This method also allows for testing component reactivity with events, input, key type, and more.
I covered another testing method with Riot-SSR in a Node environment, to compare the two solutions:
- For fast, no-brain but limited Riot testing: use a Node Server Environment with Riot-SSR
- For extensive but verbose Riot testing: use a JsDom environment
Feel free to comment if you have questions or need help about RiotJS.
Have a great day! Cheers 🍻