@use-everywhere/test-utils 0.1.3 → 0.1.4

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 (2) hide show
  1. package/llms.txt +97 -0
  2. package/package.json +11 -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",
3
+ "version": "0.1.4",
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,19 @@
9
9
  "url": "git+https://github.com/rxova/use-everywhere.git",
10
10
  "directory": "packages/test-utils"
11
11
  },
12
- "homepage": "https://github.com/rxova/use-everywhere#readme",
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"
20
25
  ],
21
26
  "publishConfig": {
22
27
  "access": "public"
@@ -39,10 +44,11 @@
39
44
  }
40
45
  },
41
46
  "files": [
42
- "dist"
47
+ "dist",
48
+ "llms.txt"
43
49
  ],
44
50
  "dependencies": {
45
- "@use-everywhere/core": "0.11.0"
51
+ "@use-everywhere/core": "0.11.1"
46
52
  },
47
53
  "devDependencies": {
48
54
  "@arethetypeswrong/cli": "^0.18.5",