@ultimat3/testing 7.0.0 → 8.0.0

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/CLAUDE.md CHANGED
@@ -64,7 +64,8 @@ is its own entry point and not part of the barrel.
64
64
  | `querySelector` skips `this` | descendants only, as the DOM's does. Matching the element it is called on made a host `<div>` answer `find('div')` with the container the test built rather than the markup the island rendered |
65
65
  | A mount installs process globals | so `MountedIsland` is `Disposable` and a `mount` that THROWS restores before it rethrows. A fake `document` left installed reaches every later FILE in the run, and fails somewhere with no thread back |
66
66
  | `fire` answers whether a handler ran | a selector matching nothing and an island that attached no handler are the same silence otherwise — the second is a bug, the first a typo. It reads Solid's delegated `$$click` property or an `addEventListener` listener; a compiled island uses one or the other |
67
- | The chunk is imported from a temp FILE, `As of 2026-08-21` | `mkdtemp`ed once per process and named by the chunk's SHA-256, so an edited island is a different module rather than a cache hit on the same path and no test leaves a `.mjs` behind in the app it just built. It was a `data:` URL until 2026-08-21 and that read better: `bun test --coverage` panics with `range end index N out of range for slice of length 4096` on `import()` of any `data:` module past ~4 kB, and an island chunk is 12-55 kB — so every island test dumped core in the per-package CI job while the root gate stayed green. Measured on Bun 1.4.0; `fixture-island.test.ts` pins the file form as a source rule, because the failure is invisible to a `bun test` without `--coverage` |
67
+ | The chunk is imported from a temp FILE, `As of 2026-08-21` | `mkdtemp`ed on the FIRST mount and named by the chunk's SHA-256, so an edited island is a different module rather than a cache hit on the same path and no test leaves a `.mjs` behind in the app it just built. It was a `data:` URL until 2026-08-21 and that read better: `bun test --coverage` panics with `range end index N out of range for slice of length 4096` on `import()` of any `data:` module past ~4 kB, and an island chunk is 12-55 kB — so every island test dumped core in the per-package CI job while the root gate stayed green. Measured on Bun 1.4.0; `fixture-island.test.ts` pins the file form as a source rule, because the failure is invisible to a `bun test` without `--coverage` |
68
+ | The scratch directory is lazy and removed, `As of 2026-08-22` | `mkdtempSync` ran at MODULE scope and nothing removed it, so every process importing `@ultimat3/testing` at all — this module is on the `.` barrel, so `expect` alone did it — left one directory in `/tmp` forever. Created on the first `modulePathFor` and `rmSync`ed from `process.on('exit')`: the handler has to be synchronous, and it is per PROCESS while `MountedIsland`'s `Disposable` is per mount and is never reached by a mount that threw. `fixture-island-cleanup.test.ts` asserts both halves from a CHILD process, which is the only place either is observable |
68
69
  | Attaching a node MOVES it | `appendChild`, `insertBefore` and `replaceChild` detach the node from its old parent first, and `removeChild` clears `parentNode` — as the DOM does, and as `reconcileArrays` requires: a `<For>` re-order calls `parentNode.insertBefore(child, ref)` on a child ALREADY in that parent (`solid-js/web`'s `web.js:155`), so an attach that only pushed left the node in BOTH positions and a five-row list reconciled to ten. A removal that left `parentNode` set was the same defect read backwards — `indexOf` answers -1, so the orphan's `nextSibling` was its old parent's FIRST child instead of `null`. `textContent = ''` detaches too; it opens every island's `mount` |
69
70
  | Globals install all-or-nothing | `installGlobals` saves DESCRIPTORS, not values — a saved value cannot tell "no such global" from "a global holding `undefined`", and the teardown deleted both — and rolls the whole install back if one assignment throws. That rollback is the half with teeth: the install runs BEFORE `mountIsland`'s own `try`, so a getter-only own global among the caller's `globals` used to leave the fake `document` installed for the rest of the process |
70
71
  | Which command shards | `bun test` is one process on one database, and that is still what a scaffolded app's `test` script runs. `x verify` DOES shard its parallel test steps, over `ULTIMATE_TEST_WORKER` and one database per worker; `live` and `e2e` stay serial because a replication slot is cluster-scoped and `e2e` has one built `dist/`. Say which command a claim is about |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/testing",
3
- "version": "7.0.0",
3
+ "version": "8.0.0",
4
4
  "description": "Test harness: cloned template DBs per worker, frozen clock, sealed network, 6 test types",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,16 +33,16 @@
33
33
  "test": "bun test"
34
34
  },
35
35
  "dependencies": {
36
- "@ultimat3/cache": "7.0.0",
37
- "@ultimat3/core": "7.0.0",
38
- "@ultimat3/db": "7.0.0",
39
- "@ultimat3/entity": "7.0.0",
40
- "@ultimat3/i18n": "7.0.0",
41
- "@ultimat3/jobs": "7.0.0",
42
- "@ultimat3/mail": "7.0.0",
43
- "@ultimat3/policy": "7.0.0",
44
- "@ultimat3/query": "7.0.0",
45
- "@ultimat3/realtime": "7.0.0",
46
- "@ultimat3/time": "7.0.0"
36
+ "@ultimat3/cache": "8.0.0",
37
+ "@ultimat3/core": "8.0.0",
38
+ "@ultimat3/db": "8.0.0",
39
+ "@ultimat3/entity": "8.0.0",
40
+ "@ultimat3/i18n": "8.0.0",
41
+ "@ultimat3/jobs": "8.0.0",
42
+ "@ultimat3/mail": "8.0.0",
43
+ "@ultimat3/policy": "8.0.0",
44
+ "@ultimat3/query": "8.0.0",
45
+ "@ultimat3/realtime": "8.0.0",
46
+ "@ultimat3/time": "8.0.0"
47
47
  }
48
48
  }
@@ -3,7 +3,7 @@
3
3
  // packages are tier 5, so importing it here would be the reverse of the one declared `cli → testing`
4
4
  // edge. A structural seam keeps the direction honest and survives the bundler changing underneath.
5
5
 
6
- import { mkdtempSync } from 'node:fs';
6
+ import { mkdtempSync, rmSync } from 'node:fs';
7
7
  import { tmpdir } from 'node:os';
8
8
  import { join } from 'node:path';
9
9
  import { islandMountMissing, islandNotBuilt } from './errors';
@@ -76,8 +76,36 @@ function entryOf(module: unknown, file: string): IslandEntry {
76
76
  return { mount: mount as IslandEntry['mount'] };
77
77
  }
78
78
 
79
- /** One directory per process, outside the app under test — so no test leaves a `.mjs` behind. */
80
- const MODULE_DIR = mkdtempSync(join(tmpdir(), 'ultimate-island-'));
79
+ let moduleDir: string | undefined;
80
+
81
+ /**
82
+ * One directory per process, outside the app under test — so no test leaves a `.mjs` behind. Two
83
+ * properties the module-scope `mkdtempSync` it replaces had neither of:
84
+ *
85
+ * LAZY. This module is on the `.` barrel, so importing `@ultimat3/testing` for `expect` alone
86
+ * created a directory — in every test process in the repo, whether or not it ever mounts anything.
87
+ *
88
+ * REMOVED. `exit` and `rmSync`, because an exit handler runs synchronously and nothing else covers
89
+ * every path: `mountIsland` restores and rethrows on a failed mount, so that run never reaches the
90
+ * `Disposable`, and the directory is per PROCESS while the disposable is per mount.
91
+ */
92
+ function moduleDirPath(): string {
93
+ const existing = moduleDir;
94
+ if (existing !== undefined) return existing;
95
+ const created = mkdtempSync(join(tmpdir(), 'ultimate-island-'));
96
+ process.on('exit', () => {
97
+ rmSync(created, { recursive: true, force: true });
98
+ });
99
+ moduleDir = created;
100
+ return created;
101
+ }
102
+
103
+ /**
104
+ * The scratch root, or `undefined` when nothing has been mounted. Read by the leak test, which
105
+ * asserts both halves of the above from a CHILD process — the only place where "this process
106
+ * created no directory" and "the directory is gone afterwards" are both observable.
107
+ */
108
+ export const islandModuleDir = (): string | undefined => moduleDir;
81
109
 
82
110
  /**
83
111
  * A temp file named by the chunk's own hash, NOT a `data:` URL. The URL form read better and
@@ -90,7 +118,7 @@ const MODULE_DIR = mkdtempSync(join(tmpdir(), 'ultimate-island-'));
90
118
  * edited island is a different module rather than a cache hit on the same path.
91
119
  */
92
120
  const modulePathFor = (code: string): string =>
93
- join(MODULE_DIR, `${Bun.SHA256.hash(code, 'hex').slice(0, 16)}.mjs`);
121
+ join(moduleDirPath(), `${Bun.SHA256.hash(code, 'hex').slice(0, 16)}.mjs`);
94
122
 
95
123
  /**
96
124
  * DESCRIPTORS, not values, and all-or-nothing.
package/src/live-node.ts CHANGED
@@ -17,7 +17,7 @@ import type {
17
17
  UpgradeTarget,
18
18
  WsData,
19
19
  WsLike,
20
- } from '@ultimat3/realtime';
20
+ } from '@ultimat3/realtime/server';
21
21
  import { liveNodeUnavailable, upgradeRefused } from './errors';
22
22
 
23
23
  /** Every frame this end received, in order, already parsed. */
@@ -121,7 +121,7 @@ let sequence = 0;
121
121
  export async function createLiveNode(options: LiveNodeOptions = {}): Promise<LiveNodeHandle> {
122
122
  const core = await import('@ultimat3/core');
123
123
  const query = await import('@ultimat3/query');
124
- const realtime = await import('@ultimat3/realtime');
124
+ const realtime = await import('@ultimat3/realtime/server');
125
125
 
126
126
  const buildId = options.buildId ?? 'test-build';
127
127
  const transport = new realtime.InProcessTransport();
@@ -14,7 +14,8 @@
14
14
  // still decides what a real node reads, and this is never in that decision.
15
15
 
16
16
  import type { RowBulkChange, RowChange, RowObserver } from '@ultimat3/entity';
17
- import type { ChangeEvent, ChangeOp, LiveQueryRegistry, Row } from '@ultimat3/realtime';
17
+ import type { Row } from '@ultimat3/realtime';
18
+ import type { ChangeEvent, ChangeOp, LiveQueryRegistry } from '@ultimat3/realtime/server';
18
19
 
19
20
  /** What a caller does with a change nobody could deliver. */
20
21
  export interface LiveReplicatorOptions {