Testing
Live tests
Live.test() mounts a Live component in a test, calls its actions and checks its state and HTML. It goes through the
same steps as the browser's updates, without a browser.
Creating your first test#
make:live --test writes a test with the component:
node oriel make:live Counter --testThe test, in tests/, mounts the component and checks its HTML:
import { Live } from '@orieljs/framework'import { expect, test } from 'vitest'import Counter from '../../../app/Live/Counter'import { TestCase } from '../../TestCase'TestCase.boot()test('counter renders', async () => { const component = await Live.test(Counter) expect(component.html()).toContain('<div')})Live.test() needs the test's application, so boot the test case in the file.
Testing actions and properties#
set() sets a property, as an o:model input does, and call() calls an action, as o:click does. Both send an
update, so await each one; the assertions that follow are synchronous and chain:
import { Live } from '@orieljs/framework'import { test } from 'vitest'import Counter from '../../../app/Live/Counter'import { TestCase } from '../../TestCase'TestCase.boot()test('the counter goes up', async () => { const component = await Live.test(Counter) await component.set('count', 5) await component.call('increment') component.assertSet('count', 6).assertSee('6')})set() also takes several properties at once, set({ title: 'Hello', body: 'World' }), and call() the action's
arguments after its name: call('delete', 5). toggle('open') flips a boolean, refresh() renders the component
again, and dispatch('post-created', { id: 1 }) sends it an event, which its @On methods receive.
The second argument of Live.test() is the mount parameters, as <Live> props would be:
import Counter from './app/Live/Counter'const component = await Live.test(Counter, { count: 4 })get('count') reads a property, html() the last HTML, instance() the component itself, and effects() what the
last update asks of the browser, such as a redirect.
Authenticating#
Live.test() runs in the test's requests, so a user you gave actingAs() is signed in for the component:
import { Hash, Live } from '@orieljs/framework'import { RefreshDatabase } from '@orieljs/framework/testing'import { expect, test } from 'vitest'import Password from '../../../app/Live/Settings/Password'import { User } from '../../../app/Models/User'import { TestCase } from '../../TestCase'const t = TestCase.boot({ uses: [RefreshDatabase] })test('password can be updated', async () => { const user = await User.factory().create() t.actingAs(user) const component = await Live.test(Password) await component.set({ current_password: 'password', password: 'new-password', password_confirmation: 'new-password' }) await component.call('updatePassword') component.assertHasNoErrors().assertSet('password', '').assertDispatched('password-updated') expect(await Hash.check('new-password', (await user.fresh())!.password)).toBe(true)})What the component changes in the session, such as a sign-in, carries on to the test's next request, and the other way round.
Testing validation#
A failed validation leaves its errors on the component, which assertHasErrors() checks:
await component.set('title', '')await component.call('save')component.assertHasErrors(['title'])With no keys, assertHasErrors() passes when there is any error, and assertHasNoErrors() when there is none.
instance().errors.first('title') reads a message.
Testing authorization#
An action that calls abort(403), or whose policy check fails, throws in the test as it would end the request:
await expect(component.call('delete', 5)).rejects.toMatchObject({ status: 403 })A page route's can middleware runs on the page's own request, so check it with an HTTP test of the
page.
Testing redirects and events#
await component.call('save')component.assertRedirect('/posts').assertDispatched('post-created', { title: 'Hello' })assertRedirect() with no URL passes on any redirect. effects().redirectUsingNavigate is true when the action
asked for { navigate: true }.
Testing file uploads#
In a WithFileUploads component, set() with a FakeFile uploads it as the browser would: through the upload rules,
to the temporary disk, and into the property as a TemporaryUploadedFile:
await component.set('photo', FakeFile.image('photo.jpg'))await component.call('save')component.assertHasNoErrors()An array of files sets a multiple input's property. A file that fails live.temporary_file_upload.rules leaves the
upload's error on the property. Use Storage.fake() to check where store() puts the file.
Available assertions#
| Assertion | Passes when |
|---|---|
assertSet(name, value), assertNotSet(name, value) | The property has the value, or hasn't |
assertSee(...texts), assertDontSee(...texts) | The HTML has each text, escaped as the HTML writes it |
assertSeeHtml(...html) | The HTML has each piece of HTML, as it is |
assertSeeText(...texts), assertDontSeeText(...texts) | The text, with the tags taken out, has each text |
assertHasErrors(keys?), assertHasNoErrors(keys?) | The component has errors for the keys, or has none |
assertDispatched(name, params?), assertNotDispatched(name) | The last update dispatched the event, with those parameters |
assertRedirect(url?), assertNoRedirect() | The last update redirects, to that URL |
assertReturned(value) | The last action returned the value |