Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

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:

Terminal
node oriel make:live Counter --test

The test, in tests/Feature/Live, mounts the component and checks its HTML:

tests/Feature/Live/Counter.test.ts
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:

tests/Feature/Live/Counter.test.ts
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:

tests/Feature/Live/Password.test.ts
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#

AssertionPasses 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

Privacy policy ·

esc