vhs-tape
v3.3.1
Published
A test harness for browser elements and components
Downloads
37
Readme
vhs-tape
A tape extension for testing frontend components.
Usage
Writing tests
const vhs = require('vhs-tape')
const MorphComponent = require('hui/morph')
const html = require('hui/html')
class Example extends MorphComponent {
constructor (loadMsg) {
super()
this._loadMsg = loadMsg
this._msg = 'Hello, not mounted yet'
this._count = 0
this.onclick = this.onclick.bind(this)
}
createElement () {
return html`
<div>
${this._msg}
<button onclick=${this.onclick}>Click me</button>
<div class="counter">Counter: ${this._count}</div>
</div>
`
}
onload () {
this._msg = this._loadMsg
this.update()
}
onclick () {
this._count++
this.update()
}
}
vhs('A simple mounting of some html async/await', async t => {
const exampleComponent = new Example('This should be loaded')
t.element.appendChild(exampleComponent.element)
await t.onload(exampleComponent.element)
// t.click takes a query selector rooted from the test element
await t.click('button')
t.equal(exampleComponent.element.querySelector('.counter').innerText, 'Counter: 1')
// t.click also takes an element
await t.click(t.element.querySelector('button'))
t.equal(exampleComponent.element.querySelector('.counter').innerText, 'Counter: 2')
// you can also directly interact with elements but you may need to await t.raf()
// to wait for updates
t.element.querySelector('button').click()
await t.raf()
t.equal(exampleComponent.element.querySelector('.counter').innerText, 'Counter: 3')
})
vhs('A simple mounting of some html', t => {
const exampleComponent = new Example('This should be loaded')
t.element.appendChild(exampleComponent.element)
setTimeout(() => {
exampleComponent.element.querySelector('button').click()
t.end()
}, 500)
})
See example.js for more helper functions.
Running tests
You can run your tests headless witht the CLI:
vhs-tape test.js
# or
vhs-tape '**/*.some.glob.js'
Run your code
Note : You have to install one of those dependencies before running the command line.
With budo
budo --live --open example.js
With nanotron
nanotron example.js
With tape-run (and browserify)
Tape-run documentation invite us to use browserify
browserify example.js | tape-run
API
WIP See https://github.com/hyperdivision/vhs-tape/blob/master/index.js#L53-L91
Tests are written exactly like tape tests except your test body can be an async function and t
has the following helpers.
vhs = require('vhs-tape')
Import the vhs test function. Works almost identically to tape
, except your test function can be async. Async test bodies do not need to call t.done()
, simply return from the async test body, or throw.
vhs(description, async testFn)
Describe your test with a description
string, and pass an async testFn
which receives the t
assertion variable. This assertion variable includes all of the tape
helpers, with a few extras that are helpful for testing dom elements and components.
vhs.delay(ms)(description, async testFn)
Delay all vhs-test helpers by ms
, unless otherwise noted in the test helper description.
vhs.slow(description, async testFn)
Shorthand for vhs.delay(500)
.
vhs.skip(description, async testFn)
Same as tape
t.skip
.
vhs.only(description, async testFn)
Same as tape
t.only
.
t.element
The HTMLElement element where your test should work inside.
await t.appendChild([parentElOrQuery], el, [msg])
Takes an element el
, append it and then waits for onload. You can also pass a different parent element or query selector parentElOrQuery
to append to. Asserts when complete with a msg
.
const newDiv = document.createElement('div')
newDiv.innerText = 'New div to append'
await t.appendChild(newDiv, 'Appended newDiv')
await t.removeChild(elementOrQuerySelector, [msg])
Takes a loaded element el
or query selector and removes it from its parent element and then waits for onunload. Asserts when complete with a msg
.
await t.sleep(ms, [msg])
Async sleep for ms
and asserts when complete with msg
.
await t.onload(element, [msg])
Wait for the element to be fully mounted and rendered into the page.
const myElement = document.createElement('div')
t.element.appendChild(myElement)
await t.onload(myElement)
await t.onunload(element, [msg])
Same as t.onload
except it lets you wait for an element to be fully unloaded from the document.
await t.raf([msg])
Lets you wait for an animation frame to fire. This gives an opportunity for the page to repaint and reflow after making modifications to the DOM. Always waits for a RequestAnimationFrame and ignores any delay parameters. Only asserts when passed a msg
. Does not insert additional delays.
await t.delay([msg])
Similar to await t.raf()
, except this will sleep when a test delay is set, so you can watch your test in slow motion. When no delay is set, these will revert to just a t.raf()
. Only asserts when passed a msg
.
await t.click(elementOrQuerySelector, [msg])
Accepts a query selector string that resolves to an element or an element. Calls element.click()
followed by a t.delay()
.
await t.focus(elementOrQuerySelector, [msg])
Accepts a query selector string that resolves to an element or an element. Calls element.focus()
followed by a t.delay()
.
await t.blur(elementOrQuerySelector, [msg])
Accepts a query selector string that resolves to an element or an element. Calls element.blur()
followed by a t.delay()
.
await t.type(string, [event], [msg])
Dispatches new window.KeyboardEvent
defaulting to the keydown
event, for each character in string
. Helpful for typing into the currently focused element on screen. This helper is a WIP, and doesn't work everywhere. Includes a t.delay()
call so updates are rendered every keypress.
await t.typeValue(elementOrQuerySelector, string, [msg])
Sumulate typing to an elementOrQuerySelector
by repeatedly setting the value and waiting for a delay.
await once(emitter, name, [msg])
Shortcut to use 'events.once'
, which is useful for catching events as promises.
CLI
VSH-Tape ships with a headless test runner that utilizes browserify and tape-run.
Pass a glob string, or series of glob strings as arguments to locate test files. Browserify flags are passed at the end after the --
and tape-run opts are passed as a subarg
under the --tape-run
flag. Note: tape-run opts are not aliased. Refer to the tape-run README to see the available options.
If no file glob is passed, the default '**/*.vhs.js'
is used. Ensure that you quote your file globs so that your CLI doesn't try to perform a depth limited globbing search instead of the built in globber.
Usage:
vhs-tape '**/*.vhs.js' [opts] --tape-run [tape-run opts] -- [browserify opts]
Options:
--help, -h show help message
--version show version
--tape-run tape-run subargs
--ignore file globs to ignore default: node_modules/** .git/**
-- [browserify options] raw flags to pass to browserify
WIP: Interactive test runner
FAQ
How do I run vhs-tests?
vhs-tests
are geared towards a Node.js style common.js environment, so you will need a bundler like browserify or webpack to bundle them into the browser or an electron app.
How do I load global styles or assumed side effects?
If your components or tests require global styles or sprite sheets to work, write a module that mounts these assets into the page as a side effect of require
ing or import
ing that file.
In each test, require the global style module, and your module loading system will de-duplicate the calls to the global side-effects, and each of your tests will still work.
// global-styles.css
const css = require('sheetify')
css('./app.css') // Mounts global styles when global-styles.css is imported once
// Be sure that your mounting logic can accomidate your production app and the test document
require('./lib/mount-sprites')(document.querySelector('#sprite-container') || document.body)
In each test that needs these assets you would then do the following:
const vhs = require('vhs-tape')
require('../../global-styles')
// vhs('The rest of your tests...
Additionally, you can always load a test bundle into a page with styles and spritesheets already mounted, or utilize features in your bundler to hande that insertion for you.
Contributors
- @tony-go - logo and features