@orieljs/framework 0.1.0-alpha.6 → 0.1.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/NOTICE +4 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-ZOEPKBVF.js → chunk-4MWG3B7G.js} +1347 -428
  5. package/dist/chunk-4MWG3B7G.js.map +1 -0
  6. package/dist/{chunk-POEP4B2M.js → chunk-7ZQZYDOC.js} +745 -105
  7. package/dist/chunk-7ZQZYDOC.js.map +1 -0
  8. package/dist/{chunk-I63ZFMX6.js → chunk-GVNLZZTN.js} +1 -1
  9. package/dist/chunk-GVNLZZTN.js.map +1 -0
  10. package/dist/{chunk-MFTZQMZM.js → chunk-VOYW3R5J.js} +4 -4
  11. package/dist/chunk-VOYW3R5J.js.map +1 -0
  12. package/dist/contracts.d.ts +515 -17
  13. package/dist/contracts.js +1 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +964 -152
  16. package/dist/index.js +4279 -866
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-CBZQYYDS.d.ts → jsx-runtime-6um4Mwfb.d.ts} +96 -5
  19. package/dist/jsx-runtime.d.ts +2 -2
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/{testing-BM481Hdz.d.ts → testing-CKwfWJGn.d.ts} +180 -15
  22. package/dist/testing.d.ts +2 -2
  23. package/dist/testing.js +3 -3
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +651 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +276 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/facades.md +278 -0
  46. package/docs/hashing.md +118 -0
  47. package/docs/helpers.md +1281 -0
  48. package/docs/http-tests.md +386 -0
  49. package/docs/inertia.md +485 -0
  50. package/docs/installation.md +209 -0
  51. package/docs/lifecycle.md +234 -0
  52. package/docs/live-tests.md +226 -0
  53. package/docs/live.md +910 -0
  54. package/docs/logging.md +255 -0
  55. package/docs/mail.md +619 -0
  56. package/docs/middleware.md +384 -0
  57. package/docs/migrations.md +416 -0
  58. package/docs/mocking.md +264 -0
  59. package/docs/navigation.json +273 -0
  60. package/docs/notifications.md +225 -0
  61. package/docs/pagination.md +247 -0
  62. package/docs/passwords.md +207 -0
  63. package/docs/providers.md +187 -0
  64. package/docs/queries.md +600 -0
  65. package/docs/queues.md +330 -0
  66. package/docs/requests.md +533 -0
  67. package/docs/responses.md +444 -0
  68. package/docs/routing.md +738 -0
  69. package/docs/scheduling.md +277 -0
  70. package/docs/seeding.md +122 -0
  71. package/docs/session.md +409 -0
  72. package/docs/starter-kits.md +396 -0
  73. package/docs/storage.md +192 -0
  74. package/docs/structure.md +264 -0
  75. package/docs/testing.md +165 -0
  76. package/docs/time-travel.md +138 -0
  77. package/docs/validation.md +1792 -0
  78. package/docs/verification.md +152 -0
  79. package/docs/views.md +566 -0
  80. package/docs/vite.md +322 -0
  81. package/package.json +34 -32
  82. package/dist/chunk-I63ZFMX6.js.map +0 -1
  83. package/dist/chunk-MFTZQMZM.js.map +0 -1
  84. package/dist/chunk-POEP4B2M.js.map +0 -1
  85. package/dist/chunk-ZOEPKBVF.js.map +0 -1
  86. package/dist/index.d-BJyJx6U1.d.ts +0 -319
@@ -0,0 +1,234 @@
1
+ # Request lifecycle
2
+
3
+ Oriel builds your app once, when its process starts, and answers every request from it, until the process ends and
4
+ the app shuts down.
5
+
6
+ ## Introduction
7
+
8
+ You can build an app without knowing how Oriel works inside. Knowing the order in which it does things tells you
9
+ where your own code goes: what a service provider can rely on, when your routes exist, and what lasts longer than one
10
+ request.
11
+
12
+ ## Lifecycle overview
13
+
14
+ ### First steps
15
+
16
+ Your app has two entry points. `public/index.ts` starts the web server, as `node --import tsx public/index.ts` or
17
+ `bun public/index.ts`, and `node oriel serve` starts the same server while you work. The `oriel` file runs a command,
18
+ as `node oriel migrate`. Both get the application from `createApplication()` in `bootstrap/app.ts`:
19
+
20
+ ```ts title="public/index.ts"
21
+ // @filename: bootstrap/app.ts
22
+ export declare function createApplication(): Promise<import('@orieljs/framework').Application>
23
+ // ---cut---
24
+ import { serve } from '@orieljs/framework'
25
+ import { createApplication } from '../bootstrap/app'
26
+
27
+ // Production entry: `node --import tsx public/index.ts`, or `bun public/index.ts`.
28
+ await serve(await createApplication())
29
+ ```
30
+
31
+ `Application.configure()` creates the application, which is also the [service container](container.md). The methods
32
+ after it record what you give them, and `create()` puts the app together:
33
+
34
+ ```ts title="bootstrap/app.ts"
35
+ import { dirname } from 'node:path'
36
+ import { Application } from '@orieljs/framework'
37
+ import providers from './providers'
38
+
39
+ /** Builds the application. Each call returns a fresh app (tests need one per test). */
40
+ export function createApplication(): Promise<Application> {
41
+ return Application.configure({ basePath: dirname(import.meta.dirname) })
42
+ .withRouting({ web: 'routes/web.ts', console: 'routes/console.ts', health: '/up' })
43
+ .withProviders(providers)
44
+ .create()
45
+ }
46
+ ```
47
+
48
+ `create()` loads your `.env` file and every file in `config/` ([Configuration](configuration.md) covers both), then
49
+ registers the service providers. Each call makes a new application, so your tests can have a fresh one for every test.
50
+
51
+ ### HTTP / console kernels
52
+
53
+ A request goes to the HTTP kernel, and a command to the console kernel.
54
+
55
+ `serve()` answers a `GET` or `HEAD` request for a file in `public/` itself, and hands every other request to
56
+ `app.handle()`, which passes it to the HTTP kernel and returns the response. The kernel takes a web-standard `Request`
57
+ and returns a `Response`: think of it as a black box that stands for your whole app. It wraps the request in Oriel's
58
+ request object, applies the trusted proxies and the body limits of `config/http.ts` (see
59
+ [Requests](requests.md#body-size-limits)), reads the body, and sends the request through the global middleware to the
60
+ router.
61
+
62
+ `node oriel` gives its arguments to the console kernel. The command runs, the app terminates as it does after a
63
+ request, then it shuts down, and the process exits with the command's exit code.
64
+
65
+ ### Service providers
66
+
67
+ Service providers set up every part of the framework: the database, the queue, validation, routing and the rest.
68
+ `create()` calls each provider's `register()`, in order. The framework has 22, registered first, in this order:
69
+ Pipeline, Encryption, Cookie, Session, Hashing, Log, Event, Cache, Filesystem, Database, Queue, Bus, Mail,
70
+ Notifications, Validation, Auth, View, Inertia, Live, Routing, Http and Console (each one's class is its name followed
71
+ by `ServiceProvider`). Each may rely on the ones before it. Yours come next, in the order `bootstrap/providers.ts`
72
+ lists them.
73
+
74
+ `create()` registers the providers but doesn't boot them. The app boots once, the first time something needs it:
75
+ `serve()` before it starts listening, `node oriel` before it runs its command, your tests before each test, or the
76
+ first request the app handles. Booting runs, in this order:
77
+
78
+ 1. the `booting()` callbacks;
79
+ 2. each provider's `boot()`, in the order they were registered. A provider registered while the app boots is booted in
80
+ the same pass, and one registered after it has booted is booted at once;
81
+ 3. the `booted()` callbacks, in the order they were added: those you give the builder first, then the framework's,
82
+ which add the health route, load the route files of `withRouting()` (the `web` files in the `web` group, then the
83
+ `api` files in the `api` group), apply your `withMiddleware()` callback and, in the console only, find your
84
+ commands; last, those your providers add in `boot()`.
85
+
86
+ Your route files run once every provider has booted, so a route pattern, a binding or a gate that a provider sets up
87
+ in `boot()` is ready for them. The builder takes callbacks of its own:
88
+
89
+ ```ts
90
+ Application.configure({ basePath: '.' })
91
+ .booting((app) => {
92
+ // Before the first provider boots...
93
+ })
94
+ .booted((app) => {
95
+ // Once every provider has booted, before your routes load...
96
+ })
97
+ ```
98
+
99
+ > [!NOTE]
100
+ > Laravel bootstraps the application again for every request: a PHP process handles one request, then forgets it.
101
+ > An Oriel app runs in a long-lived Node or Bun process, so it registers and boots its providers once, and serves
102
+ > every request from the same application. A singleton lasts as long as the process; what belongs to one request lives
103
+ > in that request's [scope](container.md#request-scopes).
104
+
105
+ ### Routing
106
+
107
+ `app.handle()` opens a request scope, and the kernel binds the request in it: everything resolved for this request,
108
+ such as the request, its session and its cookie jar, lives in the scope and is dropped with it. The global middleware
109
+ run first: `TrimStrings`, `ConvertEmptyStringsToNull`, and those you add with
110
+ [`withMiddleware()`](middleware.md#global-middleware).
111
+
112
+ The router then finds the route. A path no route matches is a 404, and a path whose routes don't take the request's
113
+ method is a 405 with an `Allow` header. The router records the route as the current one, then runs the route's
114
+ middleware: those of its groups, outer group first, then its own. The routes in `routes/web.ts` are in the `web` group,
115
+ which gives them their cookies, their session and CSRF protection:
116
+
117
+ | The `web` group | What it does |
118
+ | --- | --- |
119
+ | `EncryptCookies` | Decrypts the request's cookies and encrypts the response's |
120
+ | `AddQueuedCookiesToResponse` | Adds the cookies queued on the cookie jar to the response |
121
+ | `StartSession` | Loads the session, and saves it once the response is ready |
122
+ | `VerifyCsrfToken` | Refuses a form without the CSRF token |
123
+
124
+ The `web` and `api` groups' middleware, `auth` and `guest` run before the route's other middleware, then route model
125
+ binding replaces the parameters that name a model, then the rest run ([Sorting
126
+ middleware](middleware.md#sorting-middleware) has the rule). Last, the action runs: a closure is called with the
127
+ route's parameters, in URI order, and a controller is built by the container for this request. Whatever the action
128
+ returns becomes the response, as [Responses](responses.md) describes.
129
+
130
+ ### Finishing up
131
+
132
+ The response goes back out through the route's middleware, then the global middleware, each of which can examine or
133
+ change it. An error thrown at any stage becomes a response at that stage, rendered by the exception handler (see
134
+ [Error handling](errors.md)), so a middleware further out always receives a response. The response to a `HEAD`
135
+ request loses its body here.
136
+
137
+ The server writes the response, and then the app terminates the request, still inside its scope:
138
+
139
+ 1. `terminate()` runs on each [terminable middleware](middleware.md#terminable-middleware), the global ones first,
140
+ then the route's;
141
+ 2. the `terminating()` callbacks run, in the order they were added. One of them is the framework's: it runs the jobs
142
+ you dispatched with [`dispatchAfterResponse()`](queues.md#running-a-job-now).
143
+
144
+ On Node, this starts once the response is written; on Bun, right after the response is handed to the server.
145
+
146
+ Register a terminating callback in a service provider. It receives no arguments:
147
+
148
+ ```ts title="app/Providers/AppServiceProvider.ts"
149
+ // @filename: app/Services/Metrics.ts
150
+ export declare class Metrics {
151
+ flush(): Promise<void>
152
+ }
153
+ // ---cut---
154
+ import { ServiceProvider } from '@orieljs/framework'
155
+ import { Metrics } from '../Services/Metrics'
156
+
157
+ export class AppServiceProvider extends ServiceProvider {
158
+ override register(): void {
159
+ this.app.singleton(Metrics)
160
+ }
161
+
162
+ override boot(): void {
163
+ this.app.terminating(async () => {
164
+ await this.app.make(Metrics).flush()
165
+ })
166
+ }
167
+ }
168
+ ```
169
+
170
+ > [!WARNING]
171
+ > The terminating callbacks also run after each `node oriel` command, and after the server sends a file from
172
+ > `public/`, which never reaches the app. They then run outside any request, where `request()` throws. A terminating
173
+ > callback that throws stops the ones after it, and its error is printed to the console rather than reported; after a
174
+ > command, `node oriel` then exits with code 1.
175
+
176
+ ## Shutting down
177
+
178
+ A Node or Bun process keeps its database pools, its Redis clients and its log files open from one request to the next.
179
+ When the process ends, `app.shutdown()` closes them: it runs the callbacks registered with `app.onShutdown()`, once.
180
+
181
+ - The server calls it when it receives `SIGINT` or `SIGTERM`. It stops taking new connections, waits for the requests
182
+ in flight (closing the connections that are still open after 10 seconds), shuts the app down, and exits with 0.
183
+ - `node oriel` calls it after its command, also when the command throws.
184
+ - A script that makes the app itself must call it before it exits, or the last log entries are lost (see [Flushing
185
+ the log](logging.md#flushing-the-log)).
186
+
187
+ The callbacks run newest first. Yours were registered after the framework's, so they run before the framework closes
188
+ what they might use. Then the framework closes the queue's Redis connections, the database pools, the cache's Redis
189
+ clients, the log files (writing out what is waiting) and the session's Redis connection, each only if your app opened
190
+ it:
191
+
192
+ ```ts title="app/Providers/AppServiceProvider.ts"
193
+ // @filename: app/Services/Metrics.ts
194
+ export declare class Metrics {
195
+ close(): Promise<void>
196
+ }
197
+ // ---cut---
198
+ import { ServiceProvider } from '@orieljs/framework'
199
+ import { Metrics } from '../Services/Metrics'
200
+
201
+ export class AppServiceProvider extends ServiceProvider {
202
+ override register(): void {
203
+ this.app.singleton(Metrics)
204
+
205
+ this.app.onShutdown(async () => {
206
+ if (this.app.resolved(Metrics)) await this.app.make(Metrics).close()
207
+ })
208
+ }
209
+ }
210
+ ```
211
+
212
+ A callback that throws is printed with `console.error`, and the others still run. If the callbacks haven't finished
213
+ after 10 seconds, the rest are given up on, with "The shutdown callbacks did not finish within 10 seconds; carrying on
214
+ with the exit.", so the process can still exit. `shutdown()` never rejects, and calling it again returns the same
215
+ promise.
216
+
217
+ | | `terminating()` | `onShutdown()` |
218
+ | --- | --- | --- |
219
+ | Runs | After each response, and after each `node oriel` command | Once, when the process ends |
220
+ | Inside | The request's scope, after a request | No request |
221
+ | Order | The order they were added | Newest first |
222
+ | For | Work that waits until the response is sent | Closing pools, clients and files |
223
+
224
+ > [!NOTE]
225
+ > Laravel has no `onShutdown()`: PHP frees what a request opened when the request ends. An Oriel process serves many
226
+ > requests, so the handles it opens stay open until the process ends, and `shutdown()` closes them before it exits.
227
+
228
+ ## Focus on service providers
229
+
230
+ Service providers are the key to how an Oriel app starts: the application is created, the providers are registered and
231
+ booted, and the requests are handed to the booted app. Your own providers live in `app/Providers`. A new app's
232
+ `AppServiceProvider` is empty: it is the place for your app's bindings and its setup at boot. A larger app can split
233
+ them over several providers, each listed in `bootstrap/providers.ts`. [Service providers](providers.md) explains how
234
+ to write them.
@@ -0,0 +1,226 @@
1
+ # Live tests
2
+
3
+ `Live.test()` mounts a Live component in a test, calls its actions and checks its state and HTML. It goes through the
4
+ same steps as the browser's updates, without a browser.
5
+
6
+ ## Creating your first test
7
+
8
+ `make:live --test` writes a test with the component:
9
+
10
+ ```sh
11
+ node oriel make:live Counter --test
12
+ ```
13
+
14
+ The test, in `tests/Feature/Live`, mounts the component and checks its HTML:
15
+
16
+ ```ts title="tests/Feature/Live/Counter.test.ts"
17
+ // @filename: app/Live/Counter.tsx
18
+ import { Component } from '@orieljs/framework'
19
+ export default class Counter extends Component {
20
+ count = 0
21
+ increment() {
22
+ this.count++
23
+ }
24
+ render() {
25
+ return <div>{this.count}</div>
26
+ }
27
+ }
28
+ // ---cut---
29
+ import { Live } from '@orieljs/framework'
30
+ import { expect, test } from 'vitest'
31
+ import Counter from '../../../app/Live/Counter'
32
+ import { TestCase } from '../../TestCase'
33
+
34
+ TestCase.boot()
35
+
36
+ test('counter renders', async () => {
37
+ const component = await Live.test(Counter)
38
+ expect(component.html()).toContain('<div')
39
+ })
40
+ ```
41
+
42
+ `Live.test()` needs the test's application, so boot the test case in the file.
43
+
44
+ ## Testing actions and properties
45
+
46
+ `set()` sets a property, as an `o:model` input does, and `call()` calls an action, as `o:click` does. Both send an
47
+ update, so `await` each one; the assertions that follow are synchronous and chain:
48
+
49
+ ```ts title="tests/Feature/Live/Counter.test.ts"
50
+ // @filename: app/Live/Counter.tsx
51
+ import { Component } from '@orieljs/framework'
52
+ export default class Counter extends Component {
53
+ count = 0
54
+ increment() {
55
+ this.count++
56
+ }
57
+ render() {
58
+ return <div>{this.count}</div>
59
+ }
60
+ }
61
+ // ---cut---
62
+ import { Live } from '@orieljs/framework'
63
+ import { test } from 'vitest'
64
+ import Counter from '../../../app/Live/Counter'
65
+ import { TestCase } from '../../TestCase'
66
+
67
+ TestCase.boot()
68
+
69
+ test('the counter goes up', async () => {
70
+ const component = await Live.test(Counter)
71
+
72
+ await component.set('count', 5)
73
+ await component.call('increment')
74
+
75
+ component.assertSet('count', 6).assertSee('6')
76
+ })
77
+ ```
78
+
79
+ `set()` also takes several properties at once, `set({ title: 'Hello', body: 'World' })`, and `call()` the action's
80
+ arguments after its name: `call('delete', 5)`. `toggle('open')` flips a boolean, `refresh()` renders the component
81
+ again, and `dispatch('post-created', { id: 1 })` sends it an event, which its `@On` methods receive.
82
+
83
+ The second argument of `Live.test()` is the mount parameters, as `<Live>` props would be:
84
+
85
+ ```ts
86
+ // @filename: app/Live/Counter.tsx
87
+ import { Component } from '@orieljs/framework'
88
+ export default class Counter extends Component {
89
+ count = 0
90
+ render() {
91
+ return <div>{this.count}</div>
92
+ }
93
+ }
94
+ // ---cut---
95
+ import Counter from './app/Live/Counter'
96
+
97
+ const component = await Live.test(Counter, { count: 4 })
98
+ ```
99
+
100
+ `get('count')` reads a property, `html()` the last HTML, `instance()` the component itself, and `effects()` what the
101
+ last update asks of the browser, such as a redirect.
102
+
103
+ ### Authenticating
104
+
105
+ `Live.test()` runs in the test's requests, so a user you gave `actingAs()` is signed in for the component:
106
+
107
+ ```ts title="tests/Feature/Live/Password.test.ts"
108
+ // @filename: app/Live/Settings/Password.tsx
109
+ import { Component } from '@orieljs/framework'
110
+ export default class Password extends Component {
111
+ current_password = ''
112
+ password = ''
113
+ password_confirmation = ''
114
+ updatePassword() {}
115
+ render() {
116
+ return <form />
117
+ }
118
+ }
119
+ // ---cut---
120
+ import { Hash, Live } from '@orieljs/framework'
121
+ import { RefreshDatabase } from '@orieljs/framework/testing'
122
+ import { expect, test } from 'vitest'
123
+ import Password from '../../../app/Live/Settings/Password'
124
+ import { User } from '../../../app/Models/User'
125
+ import { TestCase } from '../../TestCase'
126
+
127
+ const t = TestCase.boot({ uses: [RefreshDatabase] })
128
+
129
+ test('password can be updated', async () => {
130
+ const user = await User.factory().create()
131
+
132
+ t.actingAs(user)
133
+ const component = await Live.test(Password)
134
+ await component.set({ current_password: 'password', password: 'new-password', password_confirmation: 'new-password' })
135
+ await component.call('updatePassword')
136
+
137
+ component.assertHasNoErrors().assertSet('password', '').assertDispatched('password-updated')
138
+
139
+ expect(await Hash.check('new-password', (await user.fresh())!.password)).toBe(true)
140
+ })
141
+ ```
142
+
143
+ What the component changes in the session, such as a sign-in, carries on to the test's next request, and the
144
+ other way round.
145
+
146
+ ## Testing validation
147
+
148
+ A failed validation leaves its errors on the component, which `assertHasErrors()` checks:
149
+
150
+ ```ts
151
+ import type { Testable } from '@orieljs/framework/testing'
152
+ declare const component: Testable
153
+ // ---cut---
154
+ await component.set('title', '')
155
+ await component.call('save')
156
+
157
+ component.assertHasErrors(['title'])
158
+ ```
159
+
160
+ With no keys, `assertHasErrors()` passes when there is any error, and `assertHasNoErrors()` when there is none.
161
+ `instance().errors.first('title')` reads a message.
162
+
163
+ ## Testing authorization
164
+
165
+ An action that calls `abort(403)`, or whose policy check fails, throws in the test as it would end the request:
166
+
167
+ ```ts
168
+ import type { Testable } from '@orieljs/framework/testing'
169
+ import { expect } from 'vitest'
170
+ declare const component: Testable
171
+ // ---cut---
172
+ await expect(component.call('delete', 5)).rejects.toMatchObject({ status: 403 })
173
+ ```
174
+
175
+ A page route's `can` middleware runs on the page's own request, so check it with an [HTTP test](http-tests.md) of the
176
+ page.
177
+
178
+ ## Testing redirects and events
179
+
180
+ ```ts
181
+ import type { Testable } from '@orieljs/framework/testing'
182
+ declare const component: Testable
183
+ // ---cut---
184
+ await component.call('save')
185
+
186
+ component.assertRedirect('/posts').assertDispatched('post-created', { title: 'Hello' })
187
+ ```
188
+
189
+ `assertRedirect()` with no URL passes on any redirect. `effects().redirectUsingNavigate` is `true` when the action
190
+ asked for `{ navigate: true }`.
191
+
192
+ ## Testing file uploads
193
+
194
+ In a `WithFileUploads` component, `set()` with a `FakeFile` uploads it as the browser would: through the upload rules,
195
+ to the temporary disk, and into the property as a `TemporaryUploadedFile`:
196
+
197
+ ```ts
198
+ import { FakeFile, type Testable } from '@orieljs/framework/testing'
199
+ declare const component: Testable
200
+ // ---cut---
201
+ await component.set('photo', FakeFile.image('photo.jpg'))
202
+ await component.call('save')
203
+
204
+ component.assertHasNoErrors()
205
+ ```
206
+
207
+ An array of files sets a `multiple` input's property. A file that fails `live.temporary_file_upload.rules` leaves the
208
+ upload's error on the property. Use `Storage.fake()` to check where `store()` puts the file.
209
+
210
+ ## Available assertions
211
+
212
+ | Assertion | Passes when |
213
+ | --- | --- |
214
+ | `assertSet(name, value)`, `assertNotSet(name, value)` | The property has the value, or hasn't |
215
+ | `assertSee(...texts)`, `assertDontSee(...texts)` | The HTML has each text, escaped as the HTML writes it |
216
+ | `assertSeeHtml(...html)` | The HTML has each piece of HTML, as it is |
217
+ | `assertSeeText(...texts)`, `assertDontSeeText(...texts)` | The text, with the tags taken out, has each text |
218
+ | `assertHasErrors(keys?)`, `assertHasNoErrors(keys?)` | The component has errors for the keys, or has none |
219
+ | `assertDispatched(name, params?)`, `assertNotDispatched(name)` | The last update dispatched the event, with those parameters |
220
+ | `assertRedirect(url?)`, `assertNoRedirect()` | The last update redirects, to that URL |
221
+ | `assertReturned(value)` | The last action returned the value |
222
+
223
+ > [!NOTE]
224
+ > Livewire's `assertSeeLivewire()`, `assertViewHas()`, `assertCount()` and `withQueryParams()` aren't in Oriel. To
225
+ > check that a page has a component, check the page's HTML with an HTTP test; `get()` and Vitest's `expect()` check
226
+ > the rest.