sounding 0.0.0 → 0.0.1
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/README.md +31 -4
- package/RESEARCH.md +743 -231
- package/index.js +58 -3
- package/lib/create-app-manager.js +329 -0
- package/lib/create-auth-helpers.js +159 -0
- package/lib/create-browser-manager.js +132 -0
- package/lib/create-expect.js +155 -0
- package/lib/create-helper-runner.js +55 -0
- package/lib/create-mail-capture.js +391 -0
- package/lib/create-mailbox.js +28 -0
- package/lib/create-request-client.js +549 -0
- package/lib/create-runtime.js +170 -0
- package/lib/create-test-api.js +228 -0
- package/lib/create-visit-client.js +114 -0
- package/lib/create-world-engine.js +300 -0
- package/lib/create-world-loader.js +128 -0
- package/lib/default-config.js +60 -0
- package/lib/define-world.js +37 -0
- package/lib/merge-config.js +25 -0
- package/lib/normalize-config.js +54 -0
- package/lib/resolve-datastore.js +97 -0
- package/package.json +17 -1
package/RESEARCH.md
CHANGED
|
@@ -6,16 +6,16 @@ Sounding is a testing framework for Sails applications and The Boring JavaScript
|
|
|
6
6
|
|
|
7
7
|
It should make it feel natural to test:
|
|
8
8
|
- helpers and business logic
|
|
9
|
-
- actions and
|
|
9
|
+
- actions, endpoints, and JSON APIs
|
|
10
10
|
- Inertia responses
|
|
11
11
|
- authentication flows
|
|
12
12
|
- email flows
|
|
13
13
|
- browser journeys
|
|
14
|
-
- sockets, jobs, payments, and webhooks over time
|
|
14
|
+
- sockets, jobs, payments, uploads, and webhooks over time
|
|
15
15
|
|
|
16
16
|
The key idea is simple:
|
|
17
17
|
|
|
18
|
-
**Use the native Node.js test runner, use Playwright for browser work, and wrap both in a Sails-
|
|
18
|
+
**Use the native Node.js test runner, use Playwright for browser work, and wrap both in a Sails-native runtime that makes realistic tests easy to write and easy to trust.**
|
|
19
19
|
|
|
20
20
|
This should feel less like "yet another framework" and more like the missing test home for everything TBJS already does.
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ It is how mariners probe unknown waters, verify what is safe, and learn what lie
|
|
|
27
27
|
|
|
28
28
|
That maps naturally to testing.
|
|
29
29
|
|
|
30
|
-
Sounding
|
|
30
|
+
Sounding suggests:
|
|
31
31
|
- probing the system
|
|
32
32
|
- measuring the unknown
|
|
33
33
|
- learning before committing
|
|
@@ -37,7 +37,7 @@ It is also:
|
|
|
37
37
|
- one word
|
|
38
38
|
- maritime without being too cute
|
|
39
39
|
- broad enough for unit, integration, endpoint, and browser testing
|
|
40
|
-
-
|
|
40
|
+
- broad enough to grow into the full Sails-native testing story
|
|
41
41
|
|
|
42
42
|
## The problem we are actually solving
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ The TBJS testing story is close, but still fragmented.
|
|
|
46
46
|
Today we have pieces:
|
|
47
47
|
- `node:test` for unit-style tests
|
|
48
48
|
- Playwright for browser flows
|
|
49
|
-
- `inertia-sails/test` for response assertions
|
|
49
|
+
- `inertia-sails/test` for response assertions today
|
|
50
50
|
- `getSails()` patterns for loading the app in tests
|
|
51
51
|
- ad hoc seeding and fixture code per application
|
|
52
52
|
|
|
@@ -57,370 +57,882 @@ Current pain points:
|
|
|
57
57
|
- data setup is repetitive and not expressive enough
|
|
58
58
|
- E2E and app boot orchestration can get ugly fast
|
|
59
59
|
- `sails-disk` and multi-process test setups collide in painful ways
|
|
60
|
-
- realistic auth
|
|
60
|
+
- realistic auth, email, and payment flows are still too manual to test cleanly
|
|
61
61
|
- people are tempted to add app-code test hooks just to make tests possible
|
|
62
62
|
|
|
63
63
|
The missing thing is not just a test runner.
|
|
64
64
|
|
|
65
65
|
The missing thing is an **elegant testing story**.
|
|
66
66
|
|
|
67
|
+
## Product goals
|
|
68
|
+
|
|
69
|
+
Sounding should be:
|
|
70
|
+
- unmistakably Sails-native
|
|
71
|
+
- expressive enough for product-behavior tests
|
|
72
|
+
- boring to maintain
|
|
73
|
+
- delightful to write
|
|
74
|
+
- credible for API-only apps, Inertia apps, and browser-heavy apps
|
|
75
|
+
|
|
67
76
|
## Design principles
|
|
68
77
|
|
|
69
78
|
### 1. Native first
|
|
70
79
|
Sounding should build on the native Node.js test runner, not compete with it.
|
|
71
80
|
|
|
72
|
-
### 2.
|
|
73
|
-
|
|
81
|
+
### 2. Hook first
|
|
82
|
+
Sounding should be a Sails hook first and a CLI second.
|
|
83
|
+
|
|
84
|
+
### 3. Sails-aware, not Sails-entangled
|
|
85
|
+
It should understand helpers, actions, policies, sessions, Waterline, Inertia, mail, sockets, uploads, and jobs.
|
|
74
86
|
But it should not force awkward test-only app code.
|
|
75
87
|
|
|
76
|
-
###
|
|
88
|
+
### 4. Tests own test data
|
|
77
89
|
Factories, traits, scenarios, and fixtures should live under `tests/`, not in the app runtime.
|
|
78
90
|
|
|
79
|
-
###
|
|
80
|
-
For E2E, there should be one real app instance and one isolated test
|
|
91
|
+
### 5. One live runtime per browser flow
|
|
92
|
+
For E2E, there should be one real app instance and one isolated test datastore. No shadow app instances fighting the same datastore.
|
|
93
|
+
|
|
94
|
+
### 6. Good datastore defaults
|
|
95
|
+
Sounding should respect normal Sails test configuration first.
|
|
81
96
|
|
|
82
|
-
|
|
83
|
-
|
|
97
|
+
By default, Sounding should manage a temporary `sails-sqlite` datastore under `.tmp/db`.
|
|
98
|
+
When teams want stronger isolation with less ceremony, Sounding should be able to manage a temporary `sails-sqlite` datastore per run or per worker.
|
|
84
99
|
|
|
85
|
-
|
|
100
|
+
The helper surface should mirror Sails itself: `sails.helpers.user.signupWithTeam(inputs)` should be the happy path.
|
|
101
|
+
|
|
102
|
+
### 7. Realistic over synthetic
|
|
86
103
|
The goal is not mocking everything. The goal is real flows with as little fake plumbing as possible.
|
|
87
104
|
|
|
88
|
-
###
|
|
105
|
+
### 8. Minimal magic
|
|
89
106
|
The best APIs should feel obvious. The framework should save time, not hide too much.
|
|
90
107
|
|
|
91
|
-
###
|
|
108
|
+
### 9. Great failure output
|
|
92
109
|
When a test fails, the developer should know:
|
|
93
110
|
- what world was created
|
|
94
111
|
- what request or browser step failed
|
|
112
|
+
- what actor was involved
|
|
95
113
|
- what the relevant app state was
|
|
96
114
|
|
|
97
|
-
##
|
|
115
|
+
## Hook-first architecture
|
|
98
116
|
|
|
99
|
-
|
|
100
|
-
- helpers
|
|
101
|
-
- pure business logic
|
|
102
|
-
- model-adjacent logic
|
|
103
|
-
- policies when run in isolation
|
|
117
|
+
Sounding should be a **Sails hook first** and a **CLI second**.
|
|
104
118
|
|
|
105
|
-
###
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
- action inputs/exits
|
|
110
|
-
- policy interaction
|
|
119
|
+
### Canonical runtime surfaces
|
|
120
|
+
- `sails.hooks.sounding` - internal hook runtime
|
|
121
|
+
- `sails.sounding` - ergonomic public alias for app and test usage
|
|
122
|
+
- `config/sounding.js` - the primary configuration surface
|
|
111
123
|
|
|
112
|
-
###
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
- partial reload behavior
|
|
116
|
-
- validation and redirect behavior
|
|
124
|
+
### What we should not do
|
|
125
|
+
- we should **not** split the mental model between `sails.test` and `sails.sounding`
|
|
126
|
+
- we should **not** use `config/test.js` as the main Sounding config namespace
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
- checkout and subscription handoff
|
|
124
|
-
- mobile navigation
|
|
128
|
+
`config/sounding.js` is the Sails-native answer because it behaves like every other serious subsystem in the ecosystem:
|
|
129
|
+
- `config/mail.js`
|
|
130
|
+
- `config/quest.js`
|
|
131
|
+
- `config/clearance.js`
|
|
132
|
+
- `config/shipwright.js`
|
|
125
133
|
|
|
126
|
-
###
|
|
127
|
-
- magic link emails
|
|
128
|
-
- password reset emails
|
|
129
|
-
- invite emails
|
|
130
|
-
- webhook-triggered notifications
|
|
134
|
+
### Config shape
|
|
131
135
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
```js
|
|
137
|
+
// config/sounding.js
|
|
138
|
+
module.exports.sounding = {
|
|
139
|
+
world: {
|
|
140
|
+
factories: 'tests/factories',
|
|
141
|
+
scenarios: 'tests/scenarios',
|
|
142
|
+
seed: 1337,
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
datastore: {
|
|
146
|
+
mode: 'managed',
|
|
147
|
+
identity: 'default',
|
|
148
|
+
|
|
149
|
+
managed: {
|
|
150
|
+
adapter: 'sails-sqlite',
|
|
151
|
+
isolation: 'worker',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
browser: {
|
|
156
|
+
enabled: true,
|
|
157
|
+
baseUrl: 'http://127.0.0.1:3333',
|
|
158
|
+
projects: ['desktop'],
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
mail: {
|
|
162
|
+
capture: true,
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
request: {
|
|
166
|
+
transport: 'virtual',
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
auth: {
|
|
170
|
+
defaultActor: 'guest',
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Default behavior
|
|
176
|
+
|
|
177
|
+
Sounding should ship with calm, predictable defaults:
|
|
178
|
+
- `datastore.mode = 'managed'`
|
|
179
|
+
- `datastore.identity = 'default'`
|
|
180
|
+
- `datastore.adapter = 'sails-sqlite'`
|
|
181
|
+
- `datastore.isolation = 'worker'`
|
|
182
|
+
- `world.factories = 'tests/factories'`
|
|
183
|
+
- `world.scenarios = 'tests/scenarios'`
|
|
184
|
+
- `mail.capture = true`
|
|
185
|
+
- `request.transport = 'virtual'`
|
|
186
|
+
- `browser.projects = ['desktop']`
|
|
187
|
+
|
|
188
|
+
Environment-specific overrides should still live in `config/env/test.js`, but `config/sounding.js` should be the home of the Sounding subsystem itself.
|
|
138
189
|
|
|
139
190
|
## The core mental model
|
|
140
191
|
|
|
141
192
|
Sounding should feel like this:
|
|
142
193
|
|
|
143
194
|
- **App**: a booted Sails application under test
|
|
144
|
-
- **World**: a realistic set of data created for a test
|
|
195
|
+
- **World**: a realistic, named set of data created for a test
|
|
145
196
|
- **Actor**: a user role in that world
|
|
146
197
|
- **Trial**: the test itself
|
|
147
198
|
- **Mailbox**: captured outbound mail for assertions
|
|
148
|
-
- **Browser**: Playwright page
|
|
199
|
+
- **Browser**: Playwright page and context helpers
|
|
149
200
|
|
|
150
201
|
The tests should read like behavior, not setup plumbing.
|
|
151
202
|
|
|
152
|
-
## What Captain Vane should become
|
|
153
203
|
|
|
154
|
-
|
|
204
|
+
## What a trial means
|
|
205
|
+
|
|
206
|
+
A **trial** is the smallest meaningful behavior Sounding asks your app to prove.
|
|
207
|
+
|
|
208
|
+
It is one named claim about how the product should behave in a real Sails runtime.
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
|
|
212
|
+
- a guest is redirected from the dashboard
|
|
213
|
+
- a subscriber can read a members-only issue
|
|
214
|
+
- a publisher can save a draft
|
|
215
|
+
- requesting a magic link sends a usable email
|
|
216
|
+
|
|
217
|
+
This stays close to the mental model developers already know from Jest, Pest, and the native Node test runner: one file groups related checks, and each named check proves one thing.
|
|
218
|
+
|
|
219
|
+
Sounding keeps the familiar `test()` API, but uses **trial** as the conceptual word because the framework is designed around product behaviors, worlds, actors, and realistic runtime conditions.
|
|
220
|
+
|
|
221
|
+
A good trial should be:
|
|
222
|
+
|
|
223
|
+
- named after behavior, not implementation
|
|
224
|
+
- small enough to understand quickly
|
|
225
|
+
- real enough to trust
|
|
226
|
+
- written at the right layer for what it is proving
|
|
227
|
+
|
|
228
|
+
## What a trial context means
|
|
229
|
+
|
|
230
|
+
A **trial context** is the single object passed into `test()`.
|
|
231
|
+
|
|
232
|
+
It should always have one clear center of gravity: `sails`.
|
|
233
|
+
|
|
234
|
+
That means:
|
|
235
|
+
- `sails` is the primary runtime object
|
|
236
|
+
- app-native surfaces stay where Sails developers expect them
|
|
237
|
+
- Sounding capabilities live under `sails.sounding`
|
|
238
|
+
- a few top-level aliases like `get()` and `post()` can exist for convenience
|
|
239
|
+
- `expect` is always present
|
|
240
|
+
|
|
241
|
+
This is important because Sounding should not invent a second pretend app model.
|
|
242
|
+
The trial context should feel like a real Sails app that has been furnished for testing.
|
|
243
|
+
|
|
244
|
+
## Design patterns for Sounding
|
|
245
|
+
|
|
246
|
+
These are the patterns that should keep Sounding elegant as it grows.
|
|
247
|
+
|
|
248
|
+
### 1. Runtime-rooted context
|
|
249
|
+
|
|
250
|
+
Every trial should start from the real app runtime:
|
|
251
|
+
|
|
252
|
+
- `sails` is the primary object
|
|
253
|
+
- `sails.helpers`, `sails.models`, `sails.config`, and `sails.hooks` stay canonical
|
|
254
|
+
- Sounding-specific capabilities hang off `sails.sounding`
|
|
255
|
+
|
|
256
|
+
This keeps Sounding from inventing a second fake app model.
|
|
257
|
+
|
|
258
|
+
### 2. Capability aliases, not parallel abstractions
|
|
259
|
+
|
|
260
|
+
Top-level helpers like `get()`, `post()`, and `visit()` should exist as ergonomic shortcuts.
|
|
155
261
|
|
|
156
|
-
|
|
262
|
+
But they should always map back to a canonical home like:
|
|
157
263
|
|
|
158
|
-
|
|
264
|
+
- `sails.sounding.request.get()`
|
|
265
|
+
- `sails.sounding.request.post()`
|
|
266
|
+
- `sails.sounding.visit()`
|
|
267
|
+
|
|
268
|
+
That gives us convenience without splitting the mental model.
|
|
269
|
+
|
|
270
|
+
### 3. Worlds as business situations
|
|
271
|
+
|
|
272
|
+
Worlds should describe business state, not just rows in a datastore.
|
|
273
|
+
|
|
274
|
+
That means:
|
|
275
|
+
|
|
276
|
+
- scenarios should read like product situations
|
|
277
|
+
- actors should be role-based
|
|
278
|
+
- tests should load a world instead of assembling ten unrelated records
|
|
279
|
+
|
|
280
|
+
### 4. Calm defaults, explicit escalation
|
|
281
|
+
|
|
282
|
+
Sounding should respect the app before it tries to be clever:
|
|
283
|
+
|
|
284
|
+
- manage a temporary `sails-sqlite` datastore by default
|
|
285
|
+
- use `config/env/test.js` as the app's truth
|
|
286
|
+
- let teams opt into `inherit` or `external` only when they truly need them
|
|
287
|
+
|
|
288
|
+
This keeps the first experience simple and the advanced experience deliberate.
|
|
289
|
+
|
|
290
|
+
### 5. Progressive disclosure
|
|
291
|
+
|
|
292
|
+
The beginner path should be tiny:
|
|
293
|
+
|
|
294
|
+
- `test()`
|
|
295
|
+
- `sails`
|
|
296
|
+
- `expect`
|
|
297
|
+
|
|
298
|
+
Then as the need grows, the trial can reach for:
|
|
299
|
+
|
|
300
|
+
- `get()` / `post()`
|
|
301
|
+
- `sails.sounding.world`
|
|
302
|
+
- `sails.sounding.mailbox`
|
|
303
|
+
- `page`
|
|
304
|
+
|
|
305
|
+
We should not force complexity on the first test.
|
|
306
|
+
|
|
307
|
+
### 6. Lazy heavyweight surfaces
|
|
308
|
+
|
|
309
|
+
The heaviest tools should only boot when a trial really needs them.
|
|
310
|
+
|
|
311
|
+
That includes:
|
|
312
|
+
|
|
313
|
+
- Playwright browser state
|
|
314
|
+
- mailbox capture integrations
|
|
315
|
+
- richer Inertia helpers
|
|
316
|
+
|
|
317
|
+
This keeps helper and endpoint trials fast without creating a separate API universe.
|
|
318
|
+
|
|
319
|
+
### 7. One assertion style
|
|
320
|
+
|
|
321
|
+
`expect` should be the primary assertion API everywhere.
|
|
322
|
+
|
|
323
|
+
That means the same mental style for:
|
|
324
|
+
|
|
325
|
+
- helpers
|
|
326
|
+
- JSON APIs
|
|
327
|
+
- Inertia responses
|
|
328
|
+
- mail
|
|
329
|
+
- browser assertions
|
|
330
|
+
|
|
331
|
+
## What is a world?
|
|
332
|
+
|
|
333
|
+
A world should be documented and taught as one of Sounding's signature ideas, not a side feature.
|
|
334
|
+
|
|
335
|
+
The docs need to make these distinctions obvious:
|
|
336
|
+
|
|
337
|
+
- a **factory** builds one kind of record
|
|
338
|
+
- a **scenario** composes factories into a named business situation
|
|
339
|
+
- an **actor** is the role a trial operates through inside that situation
|
|
340
|
+
- the resulting **world** is the readable state the trial uses
|
|
341
|
+
|
|
342
|
+
The best worlds should feel like product language, not seed-script language.
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
A **world** is the named, deterministic business state that a trial lives inside.
|
|
346
|
+
|
|
347
|
+
A world includes:
|
|
348
|
+
- actors such as guests, publishers, subscribers, or admins
|
|
349
|
+
- records like issues, subscriptions, teams, unlocks, invoices, or comments
|
|
350
|
+
- the relationships between those records
|
|
351
|
+
- the current business situation the trial cares about
|
|
352
|
+
|
|
353
|
+
A world is not just a fixture.
|
|
354
|
+
|
|
355
|
+
It is a reusable description of a product situation.
|
|
356
|
+
|
|
357
|
+
That lets tests say:
|
|
358
|
+
- "load the subscriber who has access to a gated issue"
|
|
359
|
+
- "load the publisher with a draft issue"
|
|
360
|
+
- "load the guest who requested a magic link"
|
|
361
|
+
|
|
362
|
+
instead of rewriting ten lines of setup every time.
|
|
363
|
+
|
|
364
|
+
## The built-in world engine
|
|
365
|
+
|
|
366
|
+
Sounding should own its own world engine.
|
|
367
|
+
|
|
368
|
+
That means factories, traits, states, scenarios, seeds, and world composition should live inside Sounding itself.
|
|
369
|
+
|
|
370
|
+
This keeps the testing story elegant:
|
|
371
|
+
- one package
|
|
372
|
+
- one mental model
|
|
373
|
+
- one configuration surface
|
|
374
|
+
- one documentation story
|
|
375
|
+
- one runtime that understands app boot, data setup, mail capture, and browser execution together
|
|
376
|
+
|
|
377
|
+
### The world engine should own
|
|
159
378
|
- factories
|
|
160
|
-
- traits
|
|
379
|
+
- traits and states
|
|
161
380
|
- sequences
|
|
162
381
|
- deterministic seeds
|
|
163
382
|
- build vs create APIs
|
|
164
383
|
- relationship graphs
|
|
165
384
|
- scenarios that return readable world objects
|
|
166
385
|
|
|
167
|
-
###
|
|
386
|
+
### The world engine should support
|
|
168
387
|
- `tests/factories`
|
|
169
388
|
- `tests/scenarios`
|
|
389
|
+
- `defineFactory()`
|
|
390
|
+
- `defineScenario()`
|
|
170
391
|
- `build()`
|
|
171
392
|
- `buildMany()`
|
|
172
393
|
- `create()`
|
|
173
394
|
- `createMany()`
|
|
174
|
-
- `
|
|
395
|
+
- `trait()` / `state()`
|
|
175
396
|
- `seed()`
|
|
176
397
|
- `afterBuild()` / `afterCreate()`
|
|
398
|
+
- `world.use()`
|
|
177
399
|
|
|
178
|
-
###
|
|
400
|
+
### The world engine should not own
|
|
179
401
|
- Sails app boot lifecycle
|
|
180
402
|
- request clients
|
|
181
403
|
- Playwright lifecycle
|
|
182
404
|
- mail capture runtime
|
|
183
|
-
- worker
|
|
405
|
+
- worker and datastore orchestration
|
|
184
406
|
|
|
185
|
-
|
|
407
|
+
Those remain the job of the Sounding runtime around it.
|
|
186
408
|
|
|
187
|
-
## What Sounding should
|
|
409
|
+
## What Sounding should cover
|
|
188
410
|
|
|
189
|
-
###
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
411
|
+
### Helper trials
|
|
412
|
+
- helpers
|
|
413
|
+
- pure business logic
|
|
414
|
+
- policy-like checks in isolation
|
|
415
|
+
- model-adjacent rules
|
|
193
416
|
|
|
194
|
-
###
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
417
|
+
### Endpoint and action trials
|
|
418
|
+
- guest vs authenticated access
|
|
419
|
+
- redirects
|
|
420
|
+
- JSON and HTML responses
|
|
421
|
+
- status codes and headers
|
|
422
|
+
- action inputs and exits
|
|
423
|
+
- policy interaction
|
|
424
|
+
- webhooks and provider callbacks
|
|
199
425
|
|
|
200
|
-
###
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
- job helpers later
|
|
426
|
+
### Inertia trials
|
|
427
|
+
- component name assertions
|
|
428
|
+
- prop assertions
|
|
429
|
+
- nested prop paths
|
|
430
|
+
- shared props
|
|
431
|
+
- validation and redirect behavior
|
|
432
|
+
- partial reload behavior
|
|
208
433
|
|
|
209
|
-
###
|
|
210
|
-
|
|
434
|
+
### Browser trials
|
|
435
|
+
- sign in flows
|
|
436
|
+
- onboarding
|
|
437
|
+
- editor flows
|
|
438
|
+
- gated-content flows
|
|
439
|
+
- checkout and subscription handoff
|
|
440
|
+
- mobile navigation
|
|
441
|
+
|
|
442
|
+
### Mail trials
|
|
443
|
+
- magic link emails
|
|
444
|
+
- password reset emails
|
|
445
|
+
- invite emails
|
|
446
|
+
- billing and transactional notifications
|
|
447
|
+
|
|
448
|
+
### Future layers
|
|
449
|
+
- sockets
|
|
450
|
+
- quest jobs
|
|
451
|
+
- uploads
|
|
452
|
+
- passkey/WebAuthn flows
|
|
453
|
+
- payment simulation
|
|
454
|
+
|
|
455
|
+
## The API surface we want
|
|
456
|
+
|
|
457
|
+
### Core
|
|
458
|
+
- `defineConfig()`
|
|
459
|
+
- `test()`
|
|
460
|
+
- `describe()`
|
|
461
|
+
- `beforeEach()`
|
|
462
|
+
- `afterEach()`
|
|
463
|
+
- `beforeAll()`
|
|
464
|
+
- `afterAll()`
|
|
465
|
+
- `dataset()`
|
|
466
|
+
- `expect()`
|
|
467
|
+
|
|
468
|
+
### One trial API
|
|
469
|
+
- `test()` is the primary public entrypoint
|
|
470
|
+
- the callback receives a single context object
|
|
471
|
+
- `sails` is the canonical runtime surface
|
|
472
|
+
- transport aliases like `get()`, `post()`, and later `visit()` are convenience helpers
|
|
473
|
+
- browser-capable trials can additionally destructure `page` when needed
|
|
474
|
+
|
|
475
|
+
### Runtime surfaces
|
|
476
|
+
- `sails.sounding.boot()`
|
|
477
|
+
- `sails.sounding.lower()`
|
|
478
|
+
- `sails.sounding.world.use()`
|
|
479
|
+
- `sails.helpers.user.signupWithTeam()`
|
|
480
|
+
- `sails.sounding.mailbox.latest()`
|
|
481
|
+
- `sails.sounding.mailbox.clear()`
|
|
482
|
+
|
|
483
|
+
## Assertion style
|
|
484
|
+
|
|
485
|
+
Sounding should prefer `expect` as the primary assertion API.
|
|
486
|
+
|
|
487
|
+
That choice matters because it gives the framework one clear, readable style across helper, endpoint, Inertia, mail, and browser trials.
|
|
488
|
+
|
|
489
|
+
`assert` from Node can remain available as an escape hatch, but it should not be the main story.
|
|
490
|
+
|
|
491
|
+
### Core matchers
|
|
492
|
+
- `toBe()`
|
|
493
|
+
- `toEqual()`
|
|
494
|
+
- `toContain()`
|
|
495
|
+
- `toMatch()`
|
|
496
|
+
- `toBeTruthy()`
|
|
497
|
+
- `toBeFalsy()`
|
|
498
|
+
- `toBeDefined()`
|
|
499
|
+
|
|
500
|
+
### Sails-native matchers
|
|
501
|
+
- `toHaveStatus()`
|
|
502
|
+
- `toRedirectTo()`
|
|
503
|
+
- `toHaveJsonPath()`
|
|
504
|
+
- `toHaveHeader()`
|
|
505
|
+
- `toExit()`
|
|
506
|
+
- `toBeInertiaPage()`
|
|
507
|
+
- `toHaveProp()`
|
|
508
|
+
- `toHaveValidationError()`
|
|
509
|
+
- `toHaveSentMail()`
|
|
510
|
+
|
|
511
|
+
## The API-only testing story
|
|
512
|
+
|
|
513
|
+
Sounding has to be excellent for JSON and endpoint-heavy apps.
|
|
211
514
|
|
|
212
|
-
|
|
515
|
+
This is not a side quest.
|
|
516
|
+
It is part of the core product.
|
|
517
|
+
|
|
518
|
+
### One API, multiple transports
|
|
519
|
+
|
|
520
|
+
Sounding should keep one public request story while supporting more than one transport underneath.
|
|
521
|
+
|
|
522
|
+
That means `get()`, `post()`, `visit()`, and `sails.sounding.request` should feel stable even if the underlying transport changes.
|
|
523
|
+
|
|
524
|
+
The two important transports are:
|
|
525
|
+
|
|
526
|
+
- **virtual** requests, powered by `sails.request()`
|
|
527
|
+
- **HTTP** requests, powered by a real listening app over the network
|
|
528
|
+
|
|
529
|
+
### Why `sails.request()` matters
|
|
530
|
+
|
|
531
|
+
`sails.request()` is one of the most interesting native building blocks Sounding can lean on.
|
|
532
|
+
|
|
533
|
+
It already gives Sails a virtual request interpreter, and its documented sweet spot is faster-running unit and integration tests.
|
|
534
|
+
|
|
535
|
+
That makes it a strong fit for:
|
|
536
|
+
|
|
537
|
+
- fast endpoint trials
|
|
538
|
+
- action-like request flows
|
|
539
|
+
- Inertia response assertions that care about server-side contracts
|
|
540
|
+
- situations where lifting a full HTTP server is unnecessary
|
|
541
|
+
|
|
542
|
+
### Where virtual requests should not be the whole story
|
|
543
|
+
|
|
544
|
+
The Sails docs are also clear that virtual requests are not identical to true HTTP requests.
|
|
545
|
+
|
|
546
|
+
That matters because:
|
|
547
|
+
|
|
548
|
+
- body parsing is simpler
|
|
549
|
+
- Express HTTP middleware is not fully in play
|
|
550
|
+
- static assets are not involved
|
|
551
|
+
- some middleware-sensitive behaviors need real HTTP parity
|
|
552
|
+
|
|
553
|
+
So Sounding should not pretend `sails.request()` is the answer to everything.
|
|
554
|
+
|
|
555
|
+
### The right transport strategy
|
|
556
|
+
|
|
557
|
+
The elegant answer is:
|
|
558
|
+
|
|
559
|
+
- keep one request API
|
|
560
|
+
- choose the transport underneath based on the kind of trial
|
|
561
|
+
- let the app or the trial opt into stricter parity when needed
|
|
562
|
+
- make the override order obvious and boring
|
|
563
|
+
|
|
564
|
+
A good switching story looks like:
|
|
565
|
+
|
|
566
|
+
1. per-call override, such as `get('/health', { transport: 'http' })`
|
|
567
|
+
2. per-trial override, such as `test('...', { transport: 'http' }, ...)`
|
|
568
|
+
3. the default from `config/sounding.js`
|
|
569
|
+
4. a scoped client when a trial wants to stay explicit: `sails.sounding.request.using('http')`
|
|
570
|
+
|
|
571
|
+
So a good default would be:
|
|
572
|
+
|
|
573
|
+
- use **virtual transport** for fast app-aware endpoint and Inertia-style trials when that is sufficient
|
|
574
|
+
- use **HTTP transport** for browser flows and for endpoint trials that need true HTTP behavior
|
|
575
|
+
|
|
576
|
+
That gives Sounding speed without lying about what is actually being exercised.
|
|
577
|
+
|
|
578
|
+
### `test()` for endpoint behavior
|
|
579
|
+
Use `test()` for endpoint behavior:
|
|
580
|
+
- status codes
|
|
581
|
+
- headers
|
|
582
|
+
- redirects
|
|
583
|
+
- JSON bodies
|
|
584
|
+
- policy interaction
|
|
585
|
+
- guest vs authenticated access
|
|
213
586
|
|
|
214
|
-
### Helper test
|
|
215
587
|
```js
|
|
216
|
-
import { test } from '
|
|
588
|
+
import { test } from 'sounding'
|
|
217
589
|
|
|
218
|
-
test
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
590
|
+
test('guest gets 401 on a private JSON endpoint', async ({
|
|
591
|
+
get,
|
|
592
|
+
expect,
|
|
593
|
+
}) => {
|
|
594
|
+
const response = await get('/api/issues')
|
|
224
595
|
|
|
225
|
-
expect(
|
|
596
|
+
expect(response).toHaveStatus(401)
|
|
226
597
|
})
|
|
227
598
|
```
|
|
228
599
|
|
|
229
|
-
###
|
|
600
|
+
### `test()` for action contracts
|
|
601
|
+
Use `test()` when the action contract matters more than raw HTTP.
|
|
602
|
+
|
|
230
603
|
```js
|
|
231
|
-
import { test } from '
|
|
604
|
+
import { test } from 'sounding'
|
|
605
|
+
|
|
606
|
+
test('issues/publish rejects incomplete drafts', async ({
|
|
607
|
+
action,
|
|
608
|
+
expect,
|
|
609
|
+
}) => {
|
|
610
|
+
const result = await action('issues/publish', { id: 12 })
|
|
232
611
|
|
|
233
|
-
|
|
234
|
-
const response = await request.get('/dashboard')
|
|
235
|
-
expect(response).toRedirectTo('/login')
|
|
612
|
+
expect(result).toExit('invalid')
|
|
236
613
|
})
|
|
237
614
|
```
|
|
238
615
|
|
|
239
|
-
|
|
616
|
+
## The Inertia testing story
|
|
617
|
+
|
|
618
|
+
Inertia responses deserve their own first-class lane.
|
|
619
|
+
|
|
620
|
+
They are not just JSON and not just browser pages.
|
|
621
|
+
|
|
622
|
+
### `test()` for Inertia responses
|
|
623
|
+
Use `test()` with `visit()` and Inertia-aware matchers for:
|
|
624
|
+
- component assertions
|
|
625
|
+
- prop assertions
|
|
626
|
+
- nested prop paths
|
|
627
|
+
- shared props
|
|
628
|
+
- validation and redirect behavior
|
|
629
|
+
- partial reloads
|
|
630
|
+
|
|
240
631
|
```js
|
|
241
|
-
import { test } from '
|
|
632
|
+
import { test } from 'sounding'
|
|
242
633
|
|
|
243
|
-
test
|
|
634
|
+
test('pricing page returns the correct component and props', async ({
|
|
635
|
+
visit,
|
|
636
|
+
expect,
|
|
637
|
+
}) => {
|
|
244
638
|
const page = await visit('/pricing')
|
|
639
|
+
|
|
245
640
|
expect(page).toBeInertiaPage('billing/pricing')
|
|
641
|
+
expect(page).toHaveProp('plans')
|
|
642
|
+
expect(page).toHaveProp('auth.user', null)
|
|
246
643
|
})
|
|
247
644
|
```
|
|
248
645
|
|
|
249
|
-
|
|
250
|
-
```js
|
|
251
|
-
import { test } from 'drydock'
|
|
252
|
-
|
|
253
|
-
test.browser('subscriber can read a members-only issue', async ({ page, world, login, expect }) => {
|
|
254
|
-
await world.use('issue-access')
|
|
255
|
-
await login.as('subscriber', page)
|
|
646
|
+
And for partial reloads:
|
|
256
647
|
|
|
257
|
-
|
|
648
|
+
```js
|
|
649
|
+
test('dashboard can reload only notifications', async ({ visit, expect }) => {
|
|
650
|
+
const page = await visit('/dashboard', {
|
|
651
|
+
component: 'dashboard/index',
|
|
652
|
+
only: ['notifications'],
|
|
653
|
+
reset: ['sidebar'],
|
|
654
|
+
})
|
|
258
655
|
|
|
259
|
-
|
|
656
|
+
expect(page).toBeInertiaPage('dashboard/index')
|
|
657
|
+
expect(page).toHaveProp('notifications')
|
|
260
658
|
})
|
|
261
659
|
```
|
|
262
660
|
|
|
263
|
-
|
|
661
|
+
## The mail testing story
|
|
662
|
+
|
|
663
|
+
Sounding should integrate cleanly with the Sails mail story and make mailbox capture feel native.
|
|
664
|
+
|
|
665
|
+
For `0.0.1`, the right implementation is simple and honest:
|
|
666
|
+
- wrap `sails.helpers.mail.send` when a trial boots
|
|
667
|
+
- capture the real inputs that flow through `sails-hook-mail`
|
|
668
|
+
- render the template preview when a template is used
|
|
669
|
+
- store normalized messages in `sails.sounding.mailbox`
|
|
670
|
+
- restore the original helper when the trial ends
|
|
671
|
+
|
|
672
|
+
That keeps the story Sails-native without inventing a fake mail subsystem.
|
|
673
|
+
|
|
674
|
+
A mail trial should let a developer say:
|
|
675
|
+
|
|
264
676
|
```js
|
|
265
|
-
import { test } from '
|
|
677
|
+
import { test } from 'sounding'
|
|
266
678
|
|
|
267
|
-
test
|
|
268
|
-
|
|
679
|
+
test('magic link sends a usable email', async ({
|
|
680
|
+
sails,
|
|
681
|
+
auth,
|
|
682
|
+
expect,
|
|
683
|
+
}) => {
|
|
684
|
+
await auth.requestMagicLink('reader@example.com')
|
|
685
|
+
|
|
686
|
+
const email = await sails.sounding.mailbox.latest()
|
|
687
|
+
|
|
688
|
+
expect(email.to).toContain('reader@example.com')
|
|
269
689
|
expect(email.subject).toContain('Sign in')
|
|
270
|
-
expect(email.
|
|
690
|
+
expect(email.html).toContain('/magic-link/')
|
|
271
691
|
})
|
|
272
692
|
```
|
|
273
693
|
|
|
274
|
-
|
|
694
|
+
That is the level of clarity we want.
|
|
695
|
+
|
|
696
|
+
And the captured message should be rich enough to assert on:
|
|
697
|
+
- `to`, `cc`, and `bcc`
|
|
698
|
+
- `subject`, `from`, and `replyTo`
|
|
699
|
+
- rendered `html` and `text`
|
|
700
|
+
- `template` and `templateData`
|
|
701
|
+
- extracted links like `ctaUrl`
|
|
702
|
+
- `status` for sent vs failed deliveries
|
|
703
|
+
- `error` details when delivery fails
|
|
704
|
+
|
|
705
|
+
## The browser testing story
|
|
706
|
+
|
|
707
|
+
Sounding should use Playwright for browser work, but it should make browser trials feel like the natural top layer of the same testing story.
|
|
708
|
+
|
|
709
|
+
A browser trial should have access to:
|
|
710
|
+
- Playwright `page`
|
|
711
|
+
- app-aware auth helpers like `login.as()`
|
|
712
|
+
- worlds and actors
|
|
713
|
+
- mobile projects as first-class citizens
|
|
714
|
+
|
|
715
|
+
## What we borrow from Pest
|
|
716
|
+
|
|
717
|
+
The best thing to borrow from Pest is not PHP syntax.
|
|
718
|
+
It is the product feel.
|
|
719
|
+
|
|
720
|
+
We should borrow:
|
|
721
|
+
- one beautiful home for testing
|
|
722
|
+
- low ceremony
|
|
723
|
+
- coherent configuration
|
|
724
|
+
- first-class datasets and hooks
|
|
725
|
+
- browser testing as part of the same product, not a bolt-on
|
|
726
|
+
- reporting that feels like a feature
|
|
727
|
+
|
|
728
|
+
We should not borrow:
|
|
729
|
+
- too much syntax sugar
|
|
730
|
+
- magic that fights the host language
|
|
731
|
+
- abstractions that hide Node so much that debugging gets harder
|
|
732
|
+
|
|
733
|
+
Sounding should feel like:
|
|
734
|
+
- Pest philosophy
|
|
735
|
+
- Node honesty
|
|
736
|
+
- Sails-native ergonomics
|
|
737
|
+
|
|
738
|
+
## The first credible release
|
|
739
|
+
|
|
740
|
+
`0.0.1` should prove the story is real, elegant, and useful.
|
|
741
|
+
|
|
742
|
+
It should include:
|
|
743
|
+
- hook loading and `sails.sounding`
|
|
744
|
+
- `config/sounding.js`
|
|
745
|
+
- datastore inheritance and managed `sails-sqlite` orchestration
|
|
746
|
+
- `test()` for helpers, endpoints, JSON, Inertia, and mail
|
|
747
|
+
- `test()` with `page` when the browser matters
|
|
748
|
+
- a built-in world engine
|
|
749
|
+
- mailbox capture
|
|
750
|
+
- a small example app and docs
|
|
751
|
+
|
|
752
|
+
It does not need to do everything at once.
|
|
753
|
+
It does need to make developers believe the rest of the vision is inevitable.
|
|
275
754
|
|
|
276
|
-
|
|
755
|
+
## Migration bar: replace the African Engineer test suite
|
|
277
756
|
|
|
278
|
-
|
|
279
|
-
- `sails-disk` for serious endpoint/E2E tests
|
|
280
|
-
- multi-process setups that touch the same datastore files
|
|
281
|
-
- test-only HTTP routes just for seeding state
|
|
757
|
+
Sounding should be able to replace the current test story in `/Users/koo/Gringotts/687/africanengineer.com` without asking the app to invent more test plumbing.
|
|
282
758
|
|
|
283
|
-
###
|
|
284
|
-
For `0.0.1`, Sounding should default to:
|
|
285
|
-
- **temporary SQLite**
|
|
286
|
-
- one database file per run or per worker
|
|
287
|
-
- stored under something like `/tmp/drydock/<run-id>/<worker-id>.sqlite`
|
|
759
|
+
### What the current suite looks like
|
|
288
760
|
|
|
289
|
-
|
|
290
|
-
- TBJS already leans on SQLite as a sensible default
|
|
291
|
-
- no external services required
|
|
292
|
-
- much more realistic than `sails-disk`
|
|
293
|
-
- transactions and relational behavior are available
|
|
294
|
-
- dramatically better for E2E orchestration
|
|
761
|
+
The current African Engineer test setup includes:
|
|
295
762
|
|
|
296
|
-
|
|
763
|
+
- unit helper tests using `node:test` + `getSails()`
|
|
764
|
+
- Playwright page smoke tests for public pages
|
|
765
|
+
- guest protection tests for login redirects
|
|
766
|
+
- magic-link browser tests that manually issue tokens through a test helper module
|
|
767
|
+
- issue-access browser tests that seed users, subscriptions, unlocks, and bookmarks through a custom support file
|
|
768
|
+
- publisher editor tests that seed a draft issue and then drive the browser editor
|
|
297
769
|
|
|
298
|
-
|
|
770
|
+
Today, that setup relies on:
|
|
299
771
|
|
|
300
|
-
|
|
772
|
+
- `tests/util/get-sails.js`
|
|
773
|
+
- `tests/e2e/support/test-db.cjs`
|
|
774
|
+
- Playwright web-server orchestration in `playwright.config.js`
|
|
775
|
+
- custom fixture builders and explicit database cleanup
|
|
301
776
|
|
|
302
|
-
|
|
777
|
+
Sounding should absorb that burden.
|
|
303
778
|
|
|
304
|
-
|
|
779
|
+
### What Sounding must provide to replace it cleanly
|
|
305
780
|
|
|
306
|
-
|
|
307
|
-
tests/
|
|
308
|
-
factories/
|
|
309
|
-
user.js
|
|
310
|
-
issue.js
|
|
311
|
-
subscription.js
|
|
312
|
-
scenarios/
|
|
313
|
-
issue-access.js
|
|
314
|
-
publisher-editor.js
|
|
315
|
-
reader-dashboard.js
|
|
316
|
-
e2e/
|
|
317
|
-
integration/
|
|
318
|
-
unit/
|
|
319
|
-
```
|
|
781
|
+
#### 1. One primary `test()` API
|
|
320
782
|
|
|
321
|
-
|
|
783
|
+
The public entrypoint should stay:
|
|
322
784
|
|
|
323
|
-
|
|
785
|
+
- `test()`
|
|
324
786
|
|
|
325
|
-
|
|
787
|
+
And the trial context should be able to power:
|
|
326
788
|
|
|
327
|
-
|
|
328
|
-
-
|
|
329
|
-
-
|
|
330
|
-
-
|
|
331
|
-
-
|
|
332
|
-
- `test.helper()`
|
|
333
|
-
- `test.endpoint()`
|
|
334
|
-
- `test.browser()`
|
|
335
|
-
- basic auth helpers for common roles
|
|
336
|
-
- mailbox capture for log mailers / test mailers
|
|
337
|
-
- Captain Vane adapter for factories/scenarios
|
|
338
|
-
- one reference TBJS example app
|
|
789
|
+
- helper tests
|
|
790
|
+
- endpoint tests
|
|
791
|
+
- Inertia tests
|
|
792
|
+
- mail assertions
|
|
793
|
+
- browser flows when `page` is needed
|
|
339
794
|
|
|
340
|
-
|
|
341
|
-
- custom assertion engine from scratch
|
|
342
|
-
- sockets and jobs on day one
|
|
343
|
-
- full payment/webhook simulation on day one
|
|
344
|
-
- WebAuthn passkey coverage on day one
|
|
345
|
-
- every possible Sails hook abstraction immediately
|
|
795
|
+
#### 2. A Sails-centered trial context
|
|
346
796
|
|
|
347
|
-
|
|
797
|
+
Every trial should be able to reach for:
|
|
348
798
|
|
|
349
|
-
|
|
799
|
+
- `sails`
|
|
800
|
+
- `expect`
|
|
801
|
+
- `get()`, `post()`, `put()`, `patch()`, `del()`
|
|
802
|
+
- `visit()`
|
|
803
|
+
- browser-capable trials should additionally be able to destructure `page`
|
|
350
804
|
|
|
351
|
-
|
|
352
|
-
- install Sounding
|
|
353
|
-
- point it at a Sails app
|
|
354
|
-
- define factories and scenarios under `tests/`
|
|
355
|
-
- run helper tests, endpoint tests, and browser tests with one coherent mental model
|
|
356
|
-
- avoid touching product code to make tests possible
|
|
805
|
+
The canonical app surfaces should remain:
|
|
357
806
|
|
|
358
|
-
|
|
807
|
+
- `sails.helpers`
|
|
808
|
+
- `sails.models`
|
|
809
|
+
- `sails.config`
|
|
810
|
+
- `sails.hooks`
|
|
359
811
|
|
|
360
|
-
|
|
812
|
+
And Sounding-specific surfaces should remain under:
|
|
361
813
|
|
|
362
|
-
|
|
363
|
-
-
|
|
364
|
-
- `
|
|
365
|
-
- storage-state auth helpers
|
|
366
|
-
- mobile/browser project presets
|
|
367
|
-
- better response and redirect assertions
|
|
814
|
+
- `sails.sounding.world`
|
|
815
|
+
- `sails.sounding.request`
|
|
816
|
+
- `sails.sounding.mailbox`
|
|
368
817
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
|
|
818
|
+
#### 3. Virtual request transport by default
|
|
819
|
+
|
|
820
|
+
For non-browser trials, Sounding should default to a request transport powered by `sails.request()`.
|
|
821
|
+
|
|
822
|
+
That gives us a Sails-native replacement for most current endpoint-style and Inertia-style needs without bringing in `supertest`.
|
|
823
|
+
|
|
824
|
+
It must normalize responses well enough for:
|
|
375
825
|
|
|
376
|
-
|
|
377
|
-
-
|
|
378
|
-
-
|
|
379
|
-
-
|
|
380
|
-
-
|
|
826
|
+
- `toHaveStatus()`
|
|
827
|
+
- `toRedirectTo()`
|
|
828
|
+
- `toHaveHeader()`
|
|
829
|
+
- `toHaveJsonPath()`
|
|
830
|
+
- `toBeInertiaPage()`
|
|
831
|
+
- `toHaveProp()`
|
|
832
|
+
|
|
833
|
+
#### 4. True HTTP/browser capability when parity matters
|
|
834
|
+
|
|
835
|
+
Sounding still needs real browser support for the flows that genuinely require it:
|
|
836
|
+
|
|
837
|
+
- login journeys
|
|
838
|
+
- gated issue reading in the browser
|
|
839
|
+
- editor interactions
|
|
840
|
+
- mobile navigation
|
|
841
|
+
|
|
842
|
+
That means browser-capable trials need:
|
|
843
|
+
|
|
844
|
+
- `page`
|
|
845
|
+
- web-server orchestration
|
|
846
|
+
- a clear way to combine browser behavior with worlds and actors
|
|
847
|
+
|
|
848
|
+
#### 5. A built-in world engine strong enough to replace `tests/e2e/support/test-db.cjs`
|
|
849
|
+
|
|
850
|
+
Sounding should support factories and scenarios under `tests/` that can express the current African Engineer fixtures, including:
|
|
851
|
+
|
|
852
|
+
- users with roles like publisher, subscriber, unlocked reader, and guest
|
|
853
|
+
- teams and memberships
|
|
854
|
+
- subscriptions
|
|
855
|
+
- issue unlocks
|
|
856
|
+
- bookmarks
|
|
857
|
+
- published and draft issues
|
|
858
|
+
|
|
859
|
+
Concrete scenarios Sounding should be able to express early:
|
|
860
|
+
|
|
861
|
+
- `issue-access`
|
|
862
|
+
- `publisher-editor`
|
|
863
|
+
- `guest-protection`
|
|
864
|
+
- `magic-link-auth`
|
|
865
|
+
|
|
866
|
+
#### 6. Mailbox capture that removes the need for manual magic-link token helpers
|
|
867
|
+
|
|
868
|
+
African Engineer currently issues magic-link tokens by reaching into app internals from `tests/e2e/support/test-db.cjs`.
|
|
869
|
+
|
|
870
|
+
Sounding should replace that with a better story:
|
|
871
|
+
|
|
872
|
+
- request the magic link through the real app behavior
|
|
873
|
+
- capture the resulting mail through `sails.sounding.mailbox`
|
|
874
|
+
- extract the sign-in URL from the captured message
|
|
875
|
+
- continue the browser flow from there
|
|
876
|
+
|
|
877
|
+
That is more realistic and removes custom token-plumbing from app tests.
|
|
878
|
+
|
|
879
|
+
#### 7. Simple auth helpers for browser and endpoint trials
|
|
880
|
+
|
|
881
|
+
To replace the current repeated login setup, Sounding should offer lightweight auth helpers built around real app behavior.
|
|
882
|
+
|
|
883
|
+
At minimum, it should support:
|
|
884
|
+
|
|
885
|
+
- login via captured magic link
|
|
886
|
+
- acting as a known seeded actor in request-driven trials
|
|
887
|
+
|
|
888
|
+
#### 8. Enough ergonomics to replace the current unit helper tests too
|
|
889
|
+
|
|
890
|
+
The helper tests in African Engineer should become straightforward Sounding trials like:
|
|
891
|
+
|
|
892
|
+
```js
|
|
893
|
+
const { test } = require('sounding')
|
|
894
|
+
|
|
895
|
+
test('capitalize helper works', async ({ sails, expect }) => {
|
|
896
|
+
expect(sails.helpers.capitalize('hello')).toBe('Hello')
|
|
897
|
+
})
|
|
898
|
+
```
|
|
381
899
|
|
|
382
|
-
|
|
900
|
+
That means Sounding should feel just as good for tiny tests as for browser journeys.
|
|
383
901
|
|
|
384
|
-
###
|
|
385
|
-
If Sounding tries to hide too much, it will become hard to trust.
|
|
902
|
+
### The concrete migration target
|
|
386
903
|
|
|
387
|
-
|
|
388
|
-
Sails boot and teardown need to be extremely predictable.
|
|
904
|
+
Sounding is ready to replace the current African Engineer suite when we can remove or obsolete:
|
|
389
905
|
|
|
390
|
-
|
|
391
|
-
|
|
906
|
+
- `/Users/koo/Gringotts/687/africanengineer.com/tests/util/get-sails.js`
|
|
907
|
+
- `/Users/koo/Gringotts/687/africanengineer.com/tests/e2e/support/test-db.cjs`
|
|
908
|
+
- the app-specific seeding and magic-link plumbing around Playwright
|
|
909
|
+
- the idea that helper, endpoint, Inertia, mail, and browser tests need separate mental models
|
|
392
910
|
|
|
393
|
-
###
|
|
394
|
-
It should be opinionated for TBJS, but still flexible enough for real Sails apps.
|
|
911
|
+
### The first migration phases
|
|
395
912
|
|
|
396
|
-
|
|
397
|
-
If Sounding and Captain Vane overlap too much, both products get muddy.
|
|
913
|
+
#### Phase 1
|
|
398
914
|
|
|
399
|
-
|
|
915
|
+
Replace:
|
|
400
916
|
|
|
401
|
-
|
|
402
|
-
-
|
|
403
|
-
-
|
|
404
|
-
- browser tests can run against one isolated app + one isolated database cleanly
|
|
405
|
-
- data setup reads like business scenarios, not SQL dumps
|
|
406
|
-
- failures are easier to understand than today
|
|
917
|
+
- unit helper tests
|
|
918
|
+
- guest-protection endpoint-style tests
|
|
919
|
+
- public page smoke tests that do not need rich browser fixtures
|
|
407
920
|
|
|
408
|
-
|
|
921
|
+
#### Phase 2
|
|
409
922
|
|
|
410
|
-
|
|
411
|
-
Best balance of elegance, meaning, and distinctiveness.
|
|
923
|
+
Replace:
|
|
412
924
|
|
|
413
|
-
|
|
414
|
-
-
|
|
415
|
-
-
|
|
416
|
-
- **Harbor** — strong, but feels more like infra than testing
|
|
417
|
-
- **Trials** — clear, but less distinctive
|
|
925
|
+
- issue-access tests
|
|
926
|
+
- magic-link auth tests
|
|
927
|
+
- basic dashboard/member flows
|
|
418
928
|
|
|
419
|
-
|
|
929
|
+
#### Phase 3
|
|
420
930
|
|
|
421
|
-
|
|
931
|
+
Replace:
|
|
422
932
|
|
|
423
|
-
|
|
933
|
+
- publisher editor tests
|
|
934
|
+
- the remaining browser-heavy flows
|
|
935
|
+
- mobile navigation coverage
|
|
424
936
|
|
|
425
|
-
If
|
|
426
|
-
|
|
937
|
+
If Sounding can carry that migration, then it is not just a nice idea.
|
|
938
|
+
It is a credible testing framework for real Sails applications.
|