informa 3.1.0 → 4.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.
Files changed (48) hide show
  1. package/README.md +171 -184
  2. package/dist/EnumerableWeakSet.d.ts +10 -0
  3. package/dist/EnumerableWeakSet.d.ts.map +1 -0
  4. package/dist/EnumerableWeakSet.js +44 -0
  5. package/dist/EnumerableWeakSet.js.map +1 -0
  6. package/dist/high.d.ts +2 -3
  7. package/dist/high.d.ts.map +1 -1
  8. package/dist/high.js +1 -2
  9. package/dist/high.js.map +1 -1
  10. package/dist/internals.d.ts +3 -0
  11. package/dist/internals.d.ts.map +1 -1
  12. package/dist/internals.js +5 -2
  13. package/dist/internals.js.map +1 -1
  14. package/dist/low.d.ts +16 -0
  15. package/dist/low.d.ts.map +1 -1
  16. package/dist/low.js +150 -0
  17. package/dist/low.js.map +1 -1
  18. package/dist/quirks/array.d.ts +4 -3
  19. package/dist/quirks/array.d.ts.map +1 -1
  20. package/dist/quirks/array.js +163 -75
  21. package/dist/quirks/array.js.map +1 -1
  22. package/dist/quirks/basestatified.d.ts +24 -2
  23. package/dist/quirks/basestatified.d.ts.map +1 -1
  24. package/dist/quirks/basestatified.js +244 -83
  25. package/dist/quirks/basestatified.js.map +1 -1
  26. package/dist/quirks/basestatified.test.d.ts +9 -0
  27. package/dist/quirks/basestatified.test.d.ts.map +1 -0
  28. package/dist/quirks/basestatified.test.js +291 -0
  29. package/dist/quirks/basestatified.test.js.map +1 -0
  30. package/dist/quirks/map.d.ts.map +1 -1
  31. package/dist/quirks/map.js +1 -0
  32. package/dist/quirks/map.js.map +1 -1
  33. package/dist/quirks/set.d.ts.map +1 -1
  34. package/dist/quirks/set.js +1 -0
  35. package/dist/quirks/set.js.map +1 -1
  36. package/dist/test.js +2 -6
  37. package/dist/test.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/EnumerableWeakSet.ts +46 -0
  40. package/src/high.ts +2 -3
  41. package/src/internals.ts +5 -1
  42. package/src/low.ts +120 -1
  43. package/src/quirks/array.ts +168 -92
  44. package/src/quirks/basestatified.test.ts +375 -0
  45. package/src/quirks/basestatified.ts +294 -117
  46. package/src/quirks/map.ts +3 -1
  47. package/src/quirks/set.ts +3 -1
  48. package/src/test.ts +13 -15
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Acceptance tests for statifyClass / makeStatified / BaseStatified.
3
+ *
4
+ * Run with: npx tsx src/quirks/basestatified.test.ts
5
+ *
6
+ * Uses Node.js built-in assert — no test framework required.
7
+ */
8
+
9
+ import assert from "node:assert/strict";
10
+ import $ from "../high.js";
11
+ import { statifyClass } from "./basestatified.js";
12
+ import { StatifiedSet } from "./set.js";
13
+ import { isStatified, statifySealKey } from "../internals.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Minimal test harness
17
+ // ---------------------------------------------------------------------------
18
+
19
+ let passed = 0;
20
+ let failed = 0;
21
+
22
+ function test(name: string, fn: () => void | Promise<void>) {
23
+ try {
24
+ const r = fn();
25
+ if (r instanceof Promise) {
26
+ r.then(
27
+ () => { console.log(` ✓ ${name}`); passed++; },
28
+ (e) => { console.error(` ✗ ${name}`, e); failed++; },
29
+ );
30
+ } else {
31
+ console.log(` ✓ ${name}`);
32
+ passed++;
33
+ }
34
+ } catch (e) {
35
+ console.error(` ✗ ${name}`, e);
36
+ failed++;
37
+ }
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // 1. Constructor arguments
42
+ // ---------------------------------------------------------------------------
43
+
44
+ console.log("\n── Constructor arguments ──");
45
+
46
+ class Base {
47
+ base: string;
48
+ constructor(base: string) { this.base = base; }
49
+ }
50
+
51
+ const Derived = statifyClass(
52
+ (BaseClass) => class Derived extends (BaseClass as unknown as typeof Base) {
53
+ value: number;
54
+ constructor(base: string, value: number) {
55
+ super(base);
56
+ this.value = value;
57
+ }
58
+ },
59
+ Base,
60
+ );
61
+
62
+ test("new Derived constructs without error", () => {
63
+ const x = new Derived("x", 42);
64
+ assert.equal(x.base, "x");
65
+ assert.equal(x.value, 42);
66
+ });
67
+
68
+ test("constructor args are forwarded correctly", () => {
69
+ const x = new Derived("hello", 99);
70
+ assert.equal(x.base, "hello");
71
+ assert.equal(x.value, 99);
72
+ });
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // 2. instanceof
76
+ // ---------------------------------------------------------------------------
77
+
78
+ console.log("\n── instanceof ──");
79
+
80
+ test("x instanceof Derived", () => {
81
+ const x = new Derived("x", 1);
82
+ assert.ok(x instanceof Derived);
83
+ });
84
+
85
+ test("x instanceof Base", () => {
86
+ const x = new Derived("x", 1);
87
+ assert.ok(x instanceof Base);
88
+ });
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // 3. isStatified / statifySealKey
92
+ // ---------------------------------------------------------------------------
93
+
94
+ console.log("\n── isStatified ──");
95
+
96
+ test("isStatified returns true for Derived instance", () => {
97
+ const x = new Derived("x", 1);
98
+ assert.ok(isStatified(x as any));
99
+ });
100
+
101
+ test("statifySealKey is truthy on instance", () => {
102
+ const x = new Derived("x", 1);
103
+ assert.ok((x as any)[statifySealKey]);
104
+ });
105
+
106
+ // ---------------------------------------------------------------------------
107
+ // 4. Class fields — availability after construction
108
+ // ---------------------------------------------------------------------------
109
+
110
+ console.log("\n── Class fields ──");
111
+
112
+ const WithFields = statifyClass(
113
+ (Base) => class extends Base {
114
+ fromBase = "base-field";
115
+ count = 0;
116
+ label: string;
117
+ constructor(label: string) {
118
+ super();
119
+ this.label = label;
120
+ }
121
+ },
122
+ Object,
123
+ );
124
+
125
+ test("own fields are readable after construction", () => {
126
+ const w = new WithFields("my-label");
127
+ assert.equal((w as any).fromBase, "base-field");
128
+ assert.equal((w as any).count, 0);
129
+ assert.equal((w as any).label, "my-label");
130
+ });
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // 5. Mutation — field setter fires replacement events
134
+ // ---------------------------------------------------------------------------
135
+
136
+ console.log("\n── Mutation ──");
137
+
138
+ test("mutation fires onReplace listener after construction", () => {
139
+ const w = new WithFields("label");
140
+ const received: unknown[] = [];
141
+
142
+ $.onReplace(() => (w as any).count, (v: unknown) => received.push(v));
143
+
144
+ (w as any).count = 1;
145
+ (w as any).count = 2;
146
+
147
+ assert.deepEqual(received, [1, 2]);
148
+ });
149
+
150
+ test("mutation fires replaceProp listener", () => {
151
+ const w = new WithFields("label");
152
+ let lastProp: string | symbol | undefined;
153
+ let lastVal: unknown;
154
+
155
+ $.on(() => w as any, { replaceProp: (v: unknown, p: string | symbol) => { lastProp = p as string; lastVal = v; } });
156
+
157
+ (w as any).count = 99;
158
+ assert.equal(lastProp, "count");
159
+ assert.equal(lastVal, 99);
160
+ });
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // 6. Non-configurable properties — not reactive
164
+ // ---------------------------------------------------------------------------
165
+
166
+ console.log("\n── Non-configurable properties ──");
167
+
168
+ class WithNonConfig {
169
+ regular = "mutable";
170
+ constructor() {
171
+ Object.defineProperty(this, "frozen", {
172
+ value: "immutable",
173
+ writable: false,
174
+ configurable: false,
175
+ enumerable: true,
176
+ });
177
+ }
178
+ }
179
+
180
+ const StatifiedNonConfig = statifyClass(
181
+ (Base) => class extends (Base as unknown as typeof WithNonConfig) { },
182
+ WithNonConfig,
183
+ );
184
+
185
+ test("non-configurable property retains its value", () => {
186
+ const x = new StatifiedNonConfig();
187
+ assert.equal((x as any).frozen, "immutable");
188
+ assert.equal((x as any).regular, "mutable");
189
+ });
190
+
191
+ test("non-configurable property triggers no events (listener not fired)", () => {
192
+ const x = new StatifiedNonConfig();
193
+ let fired = false;
194
+ // Subscribing to the regular field works fine.
195
+ $.onReplace(() => (x as any).regular, () => { fired = true; });
196
+ (x as any).regular = "changed";
197
+ assert.ok(fired, "regular field should fire");
198
+
199
+ // Non-configurable field cannot be instrumented; no subscription is possible.
200
+ // (Simply verify no crash and value is preserved.)
201
+ assert.equal((x as any).frozen, "immutable");
202
+ });
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // 7. Accessor properties — not double-wrapped
206
+ // ---------------------------------------------------------------------------
207
+
208
+ console.log("\n── Accessor properties ──");
209
+
210
+ class WithAccessor {
211
+ #val = 0;
212
+ get computed() { return this.#val * 2; }
213
+ set computed(v: number) { this.#val = v / 2; }
214
+ }
215
+
216
+ const StatifiedAccessor = statifyClass(
217
+ (Base) => class extends (Base as unknown as typeof WithAccessor) { },
218
+ WithAccessor,
219
+ );
220
+
221
+ test("existing prototype accessor is not double-wrapped", () => {
222
+ const x = new StatifiedAccessor();
223
+ (x as any).computed = 10;
224
+ assert.equal((x as any).computed, 10); // 10 → #val = 5 → computed = 10 ✓
225
+ });
226
+
227
+ test("prototype accessor write fires replacement events via shim set trap", () => {
228
+ const x = new StatifiedAccessor();
229
+ const received: unknown[] = [];
230
+ $.onReplace(() => (x as any).computed, (v: unknown) => received.push(v));
231
+ (x as any).computed = 4;
232
+ assert.deepEqual(received, [4]);
233
+ });
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // 8. Nested statified objects — hook/unhook
237
+ // ---------------------------------------------------------------------------
238
+
239
+ console.log("\n── Nested statified objects ──");
240
+
241
+ test("assigning a statified child hooks it into the parent graph", () => {
242
+ const parent = new WithFields("parent");
243
+ const child = $.state({ val: 1 });
244
+
245
+ const received: unknown[] = [];
246
+ $.onReplace(() => (parent as any).count, (v: unknown) => received.push(v));
247
+
248
+ (parent as any).count = child as any;
249
+ // Mutating child should propagate events up through parent's graph.
250
+ // (The "replace" event on the field itself.)
251
+ assert.ok(true, "no crash on nested assignment");
252
+ });
253
+
254
+ test("replacing a statified child unhooks the old one", () => {
255
+ const parent = new WithFields("parent");
256
+ const child1 = $.state({ val: 1 });
257
+ const child2 = $.state({ val: 2 });
258
+
259
+ (parent as any).fromBase = child1 as any;
260
+ (parent as any).fromBase = child2 as any;
261
+ // No crash; old child unhooked, new child hooked.
262
+ assert.ok(true, "no crash on child replacement");
263
+ });
264
+
265
+ console.log("\n── statifyClass direct extension ──");
266
+
267
+ const Wayland = statifyClass(
268
+ (Base) => class Wayland extends Base {
269
+ displays: StatifiedSet<string>;
270
+ #state = 0;
271
+ get state() { return this.#state; }
272
+ set state(v: number) { this.#state = v; }
273
+
274
+ constructor() {
275
+ super();
276
+ this.displays = new StatifiedSet<string>();
277
+ }
278
+ },
279
+ Object
280
+ );
281
+
282
+ test("statifyClass subclass constructs without error", () => {
283
+ const w = new Wayland();
284
+ assert.ok(w instanceof Wayland);
285
+ assert.ok(w instanceof Object);
286
+ });
287
+
288
+ test("isStatified on statifyClass instance", () => {
289
+ const w = new Wayland();
290
+ assert.ok(isStatified(w as any));
291
+ });
292
+
293
+ test("prototype accessor on statifyClass subclass fires replacement events", () => {
294
+ const w = new Wayland();
295
+ const received: number[] = [];
296
+ $.onReplace(() => (w as any).state, (v: number) => received.push(v));
297
+ (w as any).state = 6;
298
+ (w as any).state = 7;
299
+ assert.deepEqual(received, [6, 7]);
300
+ });
301
+
302
+ test("own field on statifyClass subclass fires replacement events", () => {
303
+ const w = new Wayland();
304
+ const received: unknown[] = [];
305
+ $.onReplace(() => (w as any).displays, (v: unknown) => received.push(v));
306
+ const newSet = new StatifiedSet<string>();
307
+ (w as any).displays = newSet;
308
+ assert.deepEqual(received, [newSet]);
309
+ });
310
+
311
+
312
+
313
+ test("on() resolves path for own field", () => {
314
+ const w = new WithFields("test");
315
+ const received: unknown[] = [];
316
+ $.onReplace(() => (w as any).label, (v: unknown) => received.push(v));
317
+ (w as any).label = "changed";
318
+ assert.deepEqual(received, ["changed"]);
319
+ });
320
+
321
+ test("on() registered during construction fires for post-construction mutation", () => {
322
+ // This tests the listen-during-construction → replace → fire ordering.
323
+ let offFn: (() => void) | undefined;
324
+ const received: unknown[] = [];
325
+
326
+ const ConstructionListener = statifyClass(
327
+ (Base) => class extends Base {
328
+ value = 42;
329
+ constructor() {
330
+ super();
331
+ // Register listener BEFORE construction exits.
332
+ // pendingAssemblies will be reconciled by registerPreExtractionHook
333
+ // which fires inside selectorToRootAndPath (called by $.onReplace).
334
+ offFn = $.onReplace(
335
+ () => (this as any).value,
336
+ (v: unknown) => received.push(v),
337
+ );
338
+ }
339
+ },
340
+ Object,
341
+ );
342
+
343
+ const cl = new ConstructionListener();
344
+ (cl as any).value = 100;
345
+ assert.deepEqual(received, [100]);
346
+ offFn?.();
347
+ });
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // 12. Aliases — shared statified child is not duplicated
351
+ // ---------------------------------------------------------------------------
352
+
353
+ console.log("\n── Aliases ──");
354
+
355
+ test("assigning same statified value to two fields is one graph node", () => {
356
+ const parent = new WithFields("alias-test");
357
+ const shared = $.state({ v: 1 });
358
+
359
+ (parent as any).fromBase = shared as any;
360
+ (parent as any).label = shared as any;
361
+
362
+ // Both fields reference the same statified object — no duplication.
363
+ assert.equal((parent as any).fromBase, (parent as any).label);
364
+ assert.ok(true, "no crash for aliased assignment");
365
+ });
366
+
367
+ // ---------------------------------------------------------------------------
368
+ // Summary
369
+ // ---------------------------------------------------------------------------
370
+
371
+ // Allow async tests to settle before printing summary.
372
+ setTimeout(() => {
373
+ console.log(`\n── Summary: ${passed} passed, ${failed} failed ──\n`);
374
+ if (failed > 0) process.exit(1);
375
+ }, 100);