@use-everywhere/test-utils 0.1.3 → 0.1.5
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/llms.txt +97 -0
- package/package.json +19 -5
package/llms.txt
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# @use-everywhere/test-utils
|
|
2
|
+
|
|
3
|
+
> Test seams for `use-everywhere`: run several simulated tabs in one process,
|
|
4
|
+
> with no browser, no globals and no Playwright. `createScenario` gives you tabs
|
|
5
|
+
> that talk over an in-memory hub instead of a real BroadcastChannel, so
|
|
6
|
+
> cross-tab behaviour becomes an ordinary unit test — including the two cases
|
|
7
|
+
> that are otherwise painful to reach, a tab closing cleanly and a tab crashing.
|
|
8
|
+
> Also ships fakes for the Web Locks API and for cross-origin windows.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
npm install -D @use-everywhere/test-utils
|
|
13
|
+
|
|
14
|
+
Peer: `@use-everywhere/core`. Test-only — never ship it in app code.
|
|
15
|
+
|
|
16
|
+
## Minimal working example
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { createScenario } from '@use-everywhere/test-utils';
|
|
20
|
+
|
|
21
|
+
const scenario = createScenario();
|
|
22
|
+
const a = scenario.tab();
|
|
23
|
+
const b = scenario.tab();
|
|
24
|
+
|
|
25
|
+
// Primitives are created THROUGH a tab, which is what puts them in it.
|
|
26
|
+
const storeA = a.store('settings', { theme: 'light' });
|
|
27
|
+
const storeB = b.store('settings', { theme: 'light' });
|
|
28
|
+
|
|
29
|
+
storeA.set({ theme: 'dark' });
|
|
30
|
+
await scenario.settle();
|
|
31
|
+
|
|
32
|
+
expect(storeB.get()).toEqual({ theme: 'dark' }); // the write crossed the hub
|
|
33
|
+
|
|
34
|
+
scenario.dispose();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
| Export | Kind | What it does |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `createScenario` | factory | Several simulated tabs sharing one in-memory hub |
|
|
42
|
+
| `MemoryHub` | class | The hub itself, if you need it directly |
|
|
43
|
+
| `MemoryTransport` | class | One tab's end of it; pass as a `transport` option |
|
|
44
|
+
| `FakeLockManager` | class | A stand-in for `navigator.locks`, for leader election |
|
|
45
|
+
| `FakeWindow` | class | A stand-in for a `Window`, for the cross-origin channel |
|
|
46
|
+
| `fakeWindowPair` | function | An opener/opened pair already wired together |
|
|
47
|
+
| `tick` | function | Drain pending microtasks — the line between "wrote" and "seen" |
|
|
48
|
+
| `snapshotWindow` | function | Wait out the late-joiner snapshot delay (default 80ms) |
|
|
49
|
+
|
|
50
|
+
`MemoryHub` and `MemoryTransport` are re-exported from
|
|
51
|
+
`@use-everywhere/core/testing` so a test needs one import rather than two. They
|
|
52
|
+
are the same classes, not copies.
|
|
53
|
+
|
|
54
|
+
### Scenario
|
|
55
|
+
|
|
56
|
+
`hub`, `locks` and `tabs` are readonly properties. `tab(options?)` opens another
|
|
57
|
+
tab, `settle(ms?)` lets the wire catch up, and `dispose()` closes every tab still
|
|
58
|
+
open and is safe to call twice.
|
|
59
|
+
|
|
60
|
+
`createScenario({ election })` takes `'web-locks'` (default, matching every
|
|
61
|
+
browser that has the API) or `'heartbeat'` (what plain-http origins get). Test
|
|
62
|
+
both if your app ships to one.
|
|
63
|
+
|
|
64
|
+
### Tab
|
|
65
|
+
|
|
66
|
+
Primitives are created through the tab, not imported and called inside it:
|
|
67
|
+
`store(name, initial, options?)`, `reducer(name, reducer, initial, options?)`,
|
|
68
|
+
`channel(name, options?)`, `presence(name, options?)` and `leader(name,
|
|
69
|
+
options?)`. Each gets its own connection to the hub, exactly as a real tab does
|
|
70
|
+
when it uses several bus names.
|
|
71
|
+
|
|
72
|
+
`close()` ends the tab the way a user closes one — every primitive says goodbye,
|
|
73
|
+
peers drop it at once, locks are released. `crash()` cuts the wire mid-sentence
|
|
74
|
+
with no goodbye, so peers have to *notice*. That difference is the whole reason
|
|
75
|
+
multi-tab code is hard, and testing only `close()` is testing the easy half.
|
|
76
|
+
|
|
77
|
+
## Gotchas
|
|
78
|
+
|
|
79
|
+
- **Delivery is asynchronous, as it is in a real browser.** Assert after
|
|
80
|
+
`await scenario.settle()`, never on the line after the write — a test that
|
|
81
|
+
passes synchronously is testing the wrong thing.
|
|
82
|
+
- **Hydration is not one microtask away.** A late joiner is answered after a
|
|
83
|
+
jittered pause, so a newly-opened tab needs `await scenario.settle(80)` (or
|
|
84
|
+
`snapshotWindow()`), not a bare `tick()`. This is on purpose.
|
|
85
|
+
- **One primitive per name per tab.** A presence and a store created on the same
|
|
86
|
+
name in one tab announce as two clients, matching a browser exactly.
|
|
87
|
+
- **This does not test the transport, it replaces it.** Whether
|
|
88
|
+
`BroadcastChannel` itself behaves belongs in the e2e suite; your logic on top
|
|
89
|
+
of it belongs here.
|
|
90
|
+
|
|
91
|
+
## Docs
|
|
92
|
+
|
|
93
|
+
- Full documentation: https://rxova.org/packages/use-everywhere/
|
|
94
|
+
- Agent-facing index: https://rxova.org/packages/use-everywhere/llms.txt
|
|
95
|
+
- Every page as raw markdown: add `.md` to any docs URL
|
|
96
|
+
- Testing guide: https://rxova.org/packages/use-everywhere/guides/testing.md
|
|
97
|
+
- Source: https://github.com/rxova/use-everywhere
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@use-everywhere/test-utils",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Simulate several tabs in one process: a scenario DSL, an in-memory bus, fake windows and fake Web Locks",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Jonatan Kruszewski <jonakrusze@gmail.com>",
|
|
@@ -9,14 +9,27 @@
|
|
|
9
9
|
"url": "git+https://github.com/rxova/use-everywhere.git",
|
|
10
10
|
"directory": "packages/test-utils"
|
|
11
11
|
},
|
|
12
|
-
"homepage": "https://
|
|
12
|
+
"homepage": "https://rxova.org/packages/use-everywhere/guides/testing/",
|
|
13
13
|
"bugs": "https://github.com/rxova/use-everywhere/issues",
|
|
14
14
|
"keywords": [
|
|
15
15
|
"testing",
|
|
16
|
+
"test-utils",
|
|
16
17
|
"cross-tab",
|
|
18
|
+
"multi-tab",
|
|
19
|
+
"tabs",
|
|
17
20
|
"broadcastchannel",
|
|
18
21
|
"shared-state",
|
|
19
|
-
"use-everywhere"
|
|
22
|
+
"use-everywhere",
|
|
23
|
+
"mock",
|
|
24
|
+
"fake-timers",
|
|
25
|
+
"cross-tab-state",
|
|
26
|
+
"tab-synchronization",
|
|
27
|
+
"multi-tab-testing",
|
|
28
|
+
"vitest",
|
|
29
|
+
"jest",
|
|
30
|
+
"web-locks",
|
|
31
|
+
"presence",
|
|
32
|
+
"leader-election"
|
|
20
33
|
],
|
|
21
34
|
"publishConfig": {
|
|
22
35
|
"access": "public"
|
|
@@ -39,10 +52,11 @@
|
|
|
39
52
|
}
|
|
40
53
|
},
|
|
41
54
|
"files": [
|
|
42
|
-
"dist"
|
|
55
|
+
"dist",
|
|
56
|
+
"llms.txt"
|
|
43
57
|
],
|
|
44
58
|
"dependencies": {
|
|
45
|
-
"@use-everywhere/core": "0.11.
|
|
59
|
+
"@use-everywhere/core": "0.11.2"
|
|
46
60
|
},
|
|
47
61
|
"devDependencies": {
|
|
48
62
|
"@arethetypeswrong/cli": "^0.18.5",
|