@orieljs/framework 0.1.0-alpha.5 → 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.
- package/NOTICE +7 -0
- package/README.md +10 -1
- package/TRADEMARKS.md +33 -0
- package/dist/{chunk-WNJP46B7.js → chunk-4MWG3B7G.js} +1785 -626
- package/dist/chunk-4MWG3B7G.js.map +1 -0
- package/dist/chunk-7ZQZYDOC.js +3253 -0
- package/dist/chunk-7ZQZYDOC.js.map +1 -0
- package/dist/{chunk-BSQKOLSK.js → chunk-GVNLZZTN.js} +1 -1
- package/dist/chunk-GVNLZZTN.js.map +1 -0
- package/dist/{chunk-A5XRLEWM.js → chunk-VOYW3R5J.js} +4 -4
- package/dist/chunk-VOYW3R5J.js.map +1 -0
- package/dist/contracts.d.ts +535 -17
- package/dist/contracts.js +1 -1
- package/dist/index.d-WQpfdIbG.d.ts +630 -0
- package/dist/index.d.ts +2202 -5952
- package/dist/index.js +4900 -944
- package/dist/index.js.map +1 -1
- package/dist/{jsx-runtime-WKTl3EEw.d.ts → jsx-runtime-6um4Mwfb.d.ts} +97 -51
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +3 -3
- package/dist/{testing-BErengYY.d.ts → testing-CKwfWJGn.d.ts} +313 -994
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +19 -5
- package/docs/authentication.md +487 -0
- package/docs/authorization.md +435 -0
- package/docs/cache.md +647 -0
- package/docs/collections.md +2270 -0
- package/docs/configuration.md +334 -0
- package/docs/console.md +651 -0
- package/docs/container.md +430 -0
- package/docs/controllers.md +406 -0
- package/docs/csrf.md +126 -0
- package/docs/database-testing.md +144 -0
- package/docs/database.md +236 -0
- package/docs/deployment.md +276 -0
- package/docs/eloquent-collections.md +218 -0
- package/docs/eloquent-factories.md +255 -0
- package/docs/eloquent-mutators.md +254 -0
- package/docs/eloquent-observers.md +145 -0
- package/docs/eloquent-relationships.md +557 -0
- package/docs/eloquent.md +494 -0
- package/docs/encryption.md +104 -0
- package/docs/errors.md +318 -0
- package/docs/events.md +336 -0
- package/docs/facades.md +278 -0
- package/docs/hashing.md +118 -0
- package/docs/helpers.md +1281 -0
- package/docs/http-tests.md +386 -0
- package/docs/inertia.md +485 -0
- package/docs/installation.md +209 -0
- package/docs/lifecycle.md +234 -0
- package/docs/live-tests.md +226 -0
- package/docs/live.md +910 -0
- package/docs/logging.md +255 -0
- package/docs/mail.md +619 -0
- package/docs/middleware.md +384 -0
- package/docs/migrations.md +416 -0
- package/docs/mocking.md +264 -0
- package/docs/navigation.json +273 -0
- package/docs/notifications.md +225 -0
- package/docs/pagination.md +247 -0
- package/docs/passwords.md +207 -0
- package/docs/providers.md +187 -0
- package/docs/queries.md +600 -0
- package/docs/queues.md +330 -0
- package/docs/requests.md +533 -0
- package/docs/responses.md +444 -0
- package/docs/routing.md +738 -0
- package/docs/scheduling.md +277 -0
- package/docs/seeding.md +122 -0
- package/docs/session.md +409 -0
- package/docs/starter-kits.md +396 -0
- package/docs/storage.md +192 -0
- package/docs/structure.md +264 -0
- package/docs/testing.md +165 -0
- package/docs/time-travel.md +138 -0
- package/docs/validation.md +1792 -0
- package/docs/verification.md +152 -0
- package/docs/views.md +566 -0
- package/docs/vite.md +322 -0
- package/package.json +34 -32
- package/dist/chunk-A5XRLEWM.js.map +0 -1
- package/dist/chunk-BSQKOLSK.js.map +0 -1
- package/dist/chunk-PM5YTG57.js +0 -880
- package/dist/chunk-PM5YTG57.js.map +0 -1
- package/dist/chunk-WNJP46B7.js.map +0 -1
- package/dist/index.d-DOlTeG-8.d.ts +0 -99
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# HTTP tests
|
|
2
|
+
|
|
3
|
+
Your tests send requests to your app and check the responses. The requests run inside the test, through your routes
|
|
4
|
+
and middleware, with no server and no network.
|
|
5
|
+
|
|
6
|
+
## Introduction
|
|
7
|
+
|
|
8
|
+
`t.get()` sends a request and returns a `TestResponse`, whose assertions check the status, the headers, the content,
|
|
9
|
+
the session and more:
|
|
10
|
+
|
|
11
|
+
```ts title="tests/Feature/Example.test.ts"
|
|
12
|
+
import { test } from 'vitest'
|
|
13
|
+
import { TestCase } from '../TestCase'
|
|
14
|
+
|
|
15
|
+
const t = TestCase.boot()
|
|
16
|
+
|
|
17
|
+
test('the application returns a successful response', async () => {
|
|
18
|
+
const response = await t.get('/')
|
|
19
|
+
|
|
20
|
+
response.assertOk()
|
|
21
|
+
})
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Await the request; the assertions that follow are synchronous, and each returns the response, so you can chain them.
|
|
25
|
+
|
|
26
|
+
## Making requests
|
|
27
|
+
|
|
28
|
+
`t.get()`, `t.post()`, `t.put()`, `t.patch()`, `t.delete()`, `t.options()` and `t.head()` send a request with that
|
|
29
|
+
method. The ones with a body take the data as their second argument, sent as a form:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { TestCase } from './tests/TestCase'
|
|
33
|
+
const t = TestCase.boot()
|
|
34
|
+
// ---cut---
|
|
35
|
+
const response = await t.post('/posts', { title: 'Hello', body: 'My first post' })
|
|
36
|
+
|
|
37
|
+
response.assertRedirect('/posts')
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`call(method, uri, data)` sends any method. Within one test, the requests share their cookies and session, as one
|
|
41
|
+
browser's would: a user who signs in on one request is signed in on the next.
|
|
42
|
+
|
|
43
|
+
### Customizing request headers
|
|
44
|
+
|
|
45
|
+
`withHeaders()` adds headers to every request that follows in the test, and `withHeader()` one:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { TestCase } from './tests/TestCase'
|
|
49
|
+
const t = TestCase.boot()
|
|
50
|
+
// ---cut---
|
|
51
|
+
const response = await t.withHeaders({ 'X-Header': 'Value' }).post('/user', { name: 'Sally' })
|
|
52
|
+
|
|
53
|
+
response.assertCreated()
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`withToken('secret')` sends `Authorization: Bearer secret`, `withBasicAuth(username, password)` a basic one, and
|
|
57
|
+
`withoutToken()` takes it out again.
|
|
58
|
+
|
|
59
|
+
### Cookies
|
|
60
|
+
|
|
61
|
+
`withCookie()` and `withCookies()` send cookies, encrypted as your app's own cookies are; `withUnencryptedCookie()`
|
|
62
|
+
sends one as it is:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { TestCase } from './tests/TestCase'
|
|
66
|
+
const t = TestCase.boot()
|
|
67
|
+
// ---cut---
|
|
68
|
+
const response = await t.withCookie('color', 'blue').get('/')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Session and authentication
|
|
72
|
+
|
|
73
|
+
`withSession()` puts data in the session before a request, and `flushSession()` empties it. `actingAs()` signs a user
|
|
74
|
+
in for every request that follows:
|
|
75
|
+
|
|
76
|
+
```ts title="tests/Feature/Dashboard.test.ts"
|
|
77
|
+
import { RefreshDatabase } from '@orieljs/framework/testing'
|
|
78
|
+
import { test } from 'vitest'
|
|
79
|
+
import { User } from '../../app/Models/User'
|
|
80
|
+
import { TestCase } from '../TestCase'
|
|
81
|
+
|
|
82
|
+
const t = TestCase.boot({ uses: [RefreshDatabase] })
|
|
83
|
+
|
|
84
|
+
test('signed-in users can see the dashboard', async () => {
|
|
85
|
+
const user = await User.factory().create()
|
|
86
|
+
|
|
87
|
+
const response = await t.actingAs(user).withSession({ banned: false }).get('/dashboard')
|
|
88
|
+
|
|
89
|
+
response.assertOk()
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`actingAs(user, 'token')` signs the user in on another guard, here the `token` guard, which becomes the default for the
|
|
94
|
+
requests.
|
|
95
|
+
|
|
96
|
+
### Redirects and the previous page
|
|
97
|
+
|
|
98
|
+
After `followingRedirects()`, each request of the test follows its redirects, with `GET`, and returns the last response.
|
|
99
|
+
`from(url)` sets the page the request comes from, so a `back()` redirect goes there:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { TestCase } from './tests/TestCase'
|
|
103
|
+
const t = TestCase.boot()
|
|
104
|
+
// ---cut---
|
|
105
|
+
const response = await t.from('/posts/create').post('/posts', {})
|
|
106
|
+
|
|
107
|
+
response.assertRedirect('/posts/create')
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Debugging responses
|
|
111
|
+
|
|
112
|
+
`dump()` prints the response's content, or with a path, a value of its JSON. `response.content` is the body,
|
|
113
|
+
`response.json()` the decoded JSON, and `response.status` and `response.headers` what they say.
|
|
114
|
+
|
|
115
|
+
### Exception handling
|
|
116
|
+
|
|
117
|
+
An error your code throws is rendered by your app's exception handler, as it would be for a visitor, so a test can
|
|
118
|
+
check the 500 or 404 it gets. `response.exception` is the error. `withoutExceptionHandling()` lets errors escape from
|
|
119
|
+
the request instead, so the test fails with the error and its stack:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { TestCase } from './tests/TestCase'
|
|
123
|
+
const t = TestCase.boot()
|
|
124
|
+
// ---cut---
|
|
125
|
+
const response = await t.withoutExceptionHandling().get('/')
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Testing JSON APIs
|
|
129
|
+
|
|
130
|
+
`getJson()`, `postJson()`, `putJson()`, `patchJson()`, `deleteJson()` and `json(method, uri, data)` ask for JSON back,
|
|
131
|
+
and the ones with a body send it as JSON:
|
|
132
|
+
|
|
133
|
+
```ts title="tests/Feature/Api.test.ts"
|
|
134
|
+
import { test } from 'vitest'
|
|
135
|
+
import { TestCase } from '../TestCase'
|
|
136
|
+
|
|
137
|
+
const t = TestCase.boot()
|
|
138
|
+
|
|
139
|
+
test('making an API request', async () => {
|
|
140
|
+
const response = await t.postJson('/api/user', { name: 'Sally' })
|
|
141
|
+
|
|
142
|
+
response.assertCreated().assertJson({ created: true })
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`assertJson()` passes when the response's JSON contains the data, whatever else it has. `assertExactJson()` wants
|
|
147
|
+
the JSON to be exactly the data, and `assertJsonPath()` checks one value:
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
import type { TestResponse } from '@orieljs/framework/testing'
|
|
151
|
+
declare const response: TestResponse
|
|
152
|
+
// ---cut---
|
|
153
|
+
response.assertJsonPath('team.owner.name', 'Darian')
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`response.json('team.owner.name')` reads a value, to check it with Vitest's `expect()`.
|
|
157
|
+
|
|
158
|
+
### Fluent JSON testing
|
|
159
|
+
|
|
160
|
+
`assertJson()` also takes a function, which checks the JSON one key at a time:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import type { TestResponse } from '@orieljs/framework/testing'
|
|
164
|
+
declare const response: TestResponse
|
|
165
|
+
// ---cut---
|
|
166
|
+
response.assertJson((json) =>
|
|
167
|
+
json
|
|
168
|
+
.where('id', 1)
|
|
169
|
+
.where('name', 'Victoria Faith')
|
|
170
|
+
.where('email', (email: string) => email.endsWith('@example.com'))
|
|
171
|
+
.whereNot('status', 'pending')
|
|
172
|
+
.missing('password')
|
|
173
|
+
.etc(),
|
|
174
|
+
)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Each key of the JSON must be checked, or the assertion fails with "Unexpected properties were found on the root
|
|
178
|
+
level.". `etc()` allows the keys you don't check.
|
|
179
|
+
|
|
180
|
+
| Method | Checks |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| `has(key)`, `has(key, count)` | The key is there, with that many items |
|
|
183
|
+
| `hasAll(keys)`, `hasAny(keys)` | Every key is there, or one of them is |
|
|
184
|
+
| `missing(key)`, `missingAll(keys)` | The key isn't there |
|
|
185
|
+
| `count(key, n)`, `countBetween(key, min, max)` | The number of items |
|
|
186
|
+
| `where(key, value)`, `whereNot(key, value)` | The value is that value, or isn't; with a function, it returns `true`, or `false` |
|
|
187
|
+
| `whereNull(key)`, `whereNotNull(key)` | The value is `null`, or isn't |
|
|
188
|
+
| `whereAll(values)` | Several values at once |
|
|
189
|
+
| `whereType(key, type)`, `whereAllType(types)` | The type: `'string'`, `'integer'`, `'number'`, `'boolean'`, `'array'` (a list or an object, as in Laravel), `'object'` or `'null'`, or a list of them |
|
|
190
|
+
| `whereContains(key, value)` | A list or string contains the value |
|
|
191
|
+
|
|
192
|
+
A key can be a dotted path: `where('user.name', 'Sally')`.
|
|
193
|
+
|
|
194
|
+
#### Asserting against JSON collections
|
|
195
|
+
|
|
196
|
+
`has(3)` checks the root's size, and `first()` scopes the checks to its first item:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import type { TestResponse } from '@orieljs/framework/testing'
|
|
200
|
+
declare const response: TestResponse
|
|
201
|
+
// ---cut---
|
|
202
|
+
response.assertJson((json) =>
|
|
203
|
+
json.has(3).first((user) => user.where('id', 1).where('name', 'Victoria Faith').etc()),
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
`has(3)` checks the root is a list of 3. `has('users', 3, (user) => ...)` checks the `users` list and its first item,
|
|
208
|
+
`first()` the first item, and `each()` every item.
|
|
209
|
+
|
|
210
|
+
#### Scoping JSON assertions
|
|
211
|
+
|
|
212
|
+
`has()` with a function scopes the checks to one key:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import type { TestResponse } from '@orieljs/framework/testing'
|
|
216
|
+
declare const response: TestResponse
|
|
217
|
+
// ---cut---
|
|
218
|
+
response.assertJson((json) =>
|
|
219
|
+
json.has('meta').has('users', 3, (user) => user.where('id', 1).missing('password').etc()),
|
|
220
|
+
)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
A scope must check each of its keys too, unless it calls `etc()`.
|
|
224
|
+
|
|
225
|
+
## Testing Inertia pages
|
|
226
|
+
|
|
227
|
+
`assertInertia()` checks that a response is an Inertia page, and checks its component and props with the same fluent
|
|
228
|
+
methods:
|
|
229
|
+
|
|
230
|
+
```ts title="tests/Feature/Posts.test.ts"
|
|
231
|
+
import { RefreshDatabase } from '@orieljs/framework/testing'
|
|
232
|
+
import { test } from 'vitest'
|
|
233
|
+
import { User } from '../../app/Models/User'
|
|
234
|
+
import { TestCase } from '../TestCase'
|
|
235
|
+
|
|
236
|
+
const t = TestCase.boot({ uses: [RefreshDatabase] })
|
|
237
|
+
|
|
238
|
+
test('the posts page lists the posts', async () => {
|
|
239
|
+
const user = await User.factory().create()
|
|
240
|
+
|
|
241
|
+
const response = await t.actingAs(user).get('/posts')
|
|
242
|
+
|
|
243
|
+
response.assertOk().assertInertia((page) => page.component('Posts/Index').has('posts', 0).where('auth.user.id', user.id))
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
It works on a first visit, whose page is in the HTML, and on an Inertia visit, whose page is JSON. `component()` also
|
|
248
|
+
checks that the page's file exists in `resources/js/pages`; `inertia.testing.ensure_pages_exist` set to `false` turns
|
|
249
|
+
that off.
|
|
250
|
+
|
|
251
|
+
| Method | Checks |
|
|
252
|
+
| --- | --- |
|
|
253
|
+
| `component(name)` | The page component |
|
|
254
|
+
| `url(url)`, `version(version)` | The page's URL and asset version |
|
|
255
|
+
| `hasFlash(key, value?)`, `missingFlash(key)` | A flashed value, in `page.flash` |
|
|
256
|
+
| `reload(callback)`, `reloadOnly(props, callback)`, `reloadExcept(props, callback)` | Requests the page again, as a partial reload, and checks it |
|
|
257
|
+
| `loadDeferredProps(callback)`, `loadDeferredProps(groups, callback)` | Loads the deferred props, and checks them |
|
|
258
|
+
|
|
259
|
+
The reloads are requests, so the callback that uses them is async, and `assertInertia()` returns a promise to await:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
import type { TestResponse } from '@orieljs/framework/testing'
|
|
263
|
+
declare const response: TestResponse
|
|
264
|
+
// ---cut---
|
|
265
|
+
await response.assertInertia((page) => page.component('Posts/Index').loadDeferredProps((reloaded) => reloaded.has('stats')))
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`response.assertInertiaFlash('status', 'Saved.')` checks a flashed value without a callback.
|
|
269
|
+
|
|
270
|
+
## Testing file uploads
|
|
271
|
+
|
|
272
|
+
`FakeFile` makes files to upload. Give one in the request's data, and the request is sent as `multipart/form-data`:
|
|
273
|
+
|
|
274
|
+
```ts title="tests/Feature/Avatar.test.ts"
|
|
275
|
+
import { Storage } from '@orieljs/framework'
|
|
276
|
+
import { FakeFile } from '@orieljs/framework/testing'
|
|
277
|
+
import { test } from 'vitest'
|
|
278
|
+
import { TestCase } from '../TestCase'
|
|
279
|
+
|
|
280
|
+
const t = TestCase.boot()
|
|
281
|
+
|
|
282
|
+
test('avatars can be uploaded', async () => {
|
|
283
|
+
const disk = Storage.fake('avatars')
|
|
284
|
+
|
|
285
|
+
const file = FakeFile.image('avatar.jpg')
|
|
286
|
+
|
|
287
|
+
const response = await t.post('/avatar', { avatar: file })
|
|
288
|
+
|
|
289
|
+
response.assertRedirect()
|
|
290
|
+
|
|
291
|
+
await disk.assertExists('avatar.jpg')
|
|
292
|
+
})
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The route here stores the file under its own name. `FakeFile.image(name, width, height)` makes a real image, in the
|
|
296
|
+
format its name's extension asks for: PNG, JPEG, GIF, WebP or BMP, so the `image`, `mimes` and `dimensions` rules pass.
|
|
297
|
+
`FakeFile.create(name, kilobytes)` makes a file of that size, typed from its name, or from a MIME type you give as a
|
|
298
|
+
third argument:
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
import { FakeFile } from '@orieljs/framework/testing'
|
|
302
|
+
|
|
303
|
+
const document = FakeFile.create('document.pdf', 500)
|
|
304
|
+
|
|
305
|
+
const sheet = FakeFile.create('data.csv', 10, 'text/csv')
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Testing views
|
|
309
|
+
|
|
310
|
+
A response rendered from a view knows it: `assertViewIs()` checks its name, and `assertViewHas()` its data:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
import { TestCase } from './tests/TestCase'
|
|
314
|
+
const t = TestCase.boot()
|
|
315
|
+
// ---cut---
|
|
316
|
+
const response = await t.get('/')
|
|
317
|
+
|
|
318
|
+
response.assertViewIs('welcome').assertViewHas('name', 'Taylor')
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`assertViewHas()` takes a key, a key and value, a function that checks the value, or several keys and values.
|
|
322
|
+
`response.viewData('name')` reads the view's data, and `response.original` is the view.
|
|
323
|
+
|
|
324
|
+
> [!NOTE]
|
|
325
|
+
> Laravel's `$this->view()` and `$this->blade()` render a view without a request. In Oriel, render it with
|
|
326
|
+
> `await view('welcome', data).render()` and check the HTML, or send the request that shows it.
|
|
327
|
+
|
|
328
|
+
## Available assertions
|
|
329
|
+
|
|
330
|
+
### Response assertions
|
|
331
|
+
|
|
332
|
+
| Assertion | Passes when |
|
|
333
|
+
| --- | --- |
|
|
334
|
+
| `assertStatus(code)` | The response has that status |
|
|
335
|
+
| `assertOk()`, `assertCreated()`, `assertAccepted()`, `assertNoContent()` | 200, 201, 202, or 204 with no body |
|
|
336
|
+
| `assertMovedPermanently()`, `assertFound()`, `assertNotModified()` | 301, 302, 304 |
|
|
337
|
+
| `assertBadRequest()`, `assertUnauthorized()`, `assertPaymentRequired()`, `assertForbidden()`, `assertNotFound()` | 400, 401, 402, 403, 404 |
|
|
338
|
+
| `assertMethodNotAllowed()`, `assertNotAcceptable()`, `assertRequestTimeout()`, `assertConflict()`, `assertGone()` | 405, 406, 408, 409, 410 |
|
|
339
|
+
| `assertUnsupportedMediaType()`, `assertUnprocessable()`, `assertTooManyRequests()` | 415, 422, 429 |
|
|
340
|
+
| `assertInternalServerError()`, `assertServiceUnavailable()` | 500, 503 |
|
|
341
|
+
| `assertSuccessful()`, `assertClientError()`, `assertServerError()` | A 2xx, 4xx or 5xx status |
|
|
342
|
+
| `assertRedirect(uri?)`, `assertRedirectContains(text)`, `assertRedirectToRoute(name, params?)` | A redirect, to that URI or route |
|
|
343
|
+
| `assertLocation(uri)` | The `Location` header is that URI |
|
|
344
|
+
| `assertHeader(name, value?)`, `assertHeaderMissing(name)` | The header is there, with that value, or isn't |
|
|
345
|
+
| `assertDownload(filename?)` | The response is a file download |
|
|
346
|
+
| `assertContent(text)` | The body is exactly the text |
|
|
347
|
+
| `assertSee(text)`, `assertDontSee(text)`, `assertSeeInOrder(texts)` | The body has the text, escaped unless you pass `false` |
|
|
348
|
+
| `assertSeeText(text)`, `assertDontSeeText(text)`, `assertSeeTextInOrder(texts)` | The same, with the HTML tags taken out |
|
|
349
|
+
| `assertJson(data)`, `assertExactJson(data)`, `assertJsonPath(path, value)`, `assertJsonMissingPath(path)` | The JSON |
|
|
350
|
+
| `assertJsonFragment(data)`, `assertJsonMissing(data)` | The data appears somewhere in the JSON, or doesn't |
|
|
351
|
+
| `assertJsonStructure(structure)`, `assertJsonCount(count, key?)` | The JSON's keys, or the number of items |
|
|
352
|
+
| `assertCookie(name, value?)`, `assertPlainCookie(name, value?)` | The response sets the cookie, encrypted or not |
|
|
353
|
+
| `assertCookieExpired(name)`, `assertCookieNotExpired(name)`, `assertCookieMissing(name)` | The cookie |
|
|
354
|
+
| `assertViewIs(name)`, `assertViewHas(key, value?)`, `assertViewHasAll(data)`, `assertViewMissing(key)` | The view |
|
|
355
|
+
|
|
356
|
+
The [Session](session.md#testing) page lists the session assertions: `assertSessionHas()`, `assertSessionMissing()`,
|
|
357
|
+
`assertSessionHasInput()` and the others.
|
|
358
|
+
|
|
359
|
+
### Authentication assertions
|
|
360
|
+
|
|
361
|
+
These are on the test case, and check the user after the requests so far. They are async, so `await` them:
|
|
362
|
+
|
|
363
|
+
| Assertion | Passes when |
|
|
364
|
+
| --- | --- |
|
|
365
|
+
| `await t.assertAuthenticated(guard?)` | A user is signed in |
|
|
366
|
+
| `await t.assertGuest(guard?)` | No user is signed in |
|
|
367
|
+
| `await t.assertAuthenticatedAs(user, guard?)` | That user is signed in |
|
|
368
|
+
|
|
369
|
+
### Validation assertions
|
|
370
|
+
|
|
371
|
+
| Assertion | Passes when |
|
|
372
|
+
| --- | --- |
|
|
373
|
+
| `assertValid(keys?)` | The response has no validation errors, for those keys or at all |
|
|
374
|
+
| `assertInvalid(errors)` | It has errors for the keys, or with the messages: `{ name: 'The name field is required.' }` |
|
|
375
|
+
| `assertSessionHasErrors(keys)`, `assertSessionHasNoErrors()` | The session has the errors a form's redirect carries |
|
|
376
|
+
| `assertJsonValidationErrors(keys)`, `assertJsonMissingValidationErrors(keys?)` | A JSON response has the errors |
|
|
377
|
+
|
|
378
|
+
`assertValid()` and `assertInvalid()` read the errors from the session, or from a JSON response's `errors`, so they work
|
|
379
|
+
for both kinds of request.
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
import type { TestResponse } from '@orieljs/framework/testing'
|
|
383
|
+
declare const response: TestResponse
|
|
384
|
+
// ---cut---
|
|
385
|
+
response.assertInvalid({ email: 'The email field must be a valid email address.' })
|
|
386
|
+
```
|