@spearwolf/shadow-objects 0.34.0 → 0.35.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 (174) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +11 -2
  3. package/bundle.js +4 -2
  4. package/package.json +9 -1
  5. package/src/WorkerTimeoutError.d.ts +4 -3
  6. package/src/WorkerTimeoutError.d.ts.map +1 -1
  7. package/src/WorkerTimeoutError.js.map +2 -2
  8. package/src/constants.d.ts +8 -4
  9. package/src/constants.d.ts.map +1 -1
  10. package/src/constants.js +6 -0
  11. package/src/constants.js.map +2 -2
  12. package/src/elements/ShaePropElement.d.ts +15 -6
  13. package/src/elements/ShaePropElement.d.ts.map +1 -1
  14. package/src/elements/ShaePropElement.js +31 -7
  15. package/src/elements/ShaePropElement.js.map +2 -2
  16. package/src/elements/ShaeWorkerElement.d.ts +10 -0
  17. package/src/elements/ShaeWorkerElement.d.ts.map +1 -1
  18. package/src/elements/ShaeWorkerElement.js +69 -2
  19. package/src/elements/ShaeWorkerElement.js.map +2 -2
  20. package/src/elements/constants.d.ts +3 -0
  21. package/src/elements/constants.d.ts.map +1 -1
  22. package/src/elements/constants.js +6 -0
  23. package/src/elements/constants.js.map +2 -2
  24. package/src/in-the-dark/Entity.d.ts +30 -0
  25. package/src/in-the-dark/Entity.d.ts.map +1 -1
  26. package/src/in-the-dark/Entity.js +38 -0
  27. package/src/in-the-dark/Entity.js.map +2 -2
  28. package/src/in-the-dark/Kernel.d.ts +24 -1
  29. package/src/in-the-dark/Kernel.d.ts.map +1 -1
  30. package/src/in-the-dark/Kernel.js +56 -1
  31. package/src/in-the-dark/Kernel.js.map +2 -2
  32. package/src/in-the-dark/Registry.d.ts +7 -1
  33. package/src/in-the-dark/Registry.d.ts.map +1 -1
  34. package/src/in-the-dark/Registry.js +29 -0
  35. package/src/in-the-dark/Registry.js.map +2 -2
  36. package/src/in-the-dark/ShadowObjectCreationScope.d.ts +8 -1
  37. package/src/in-the-dark/ShadowObjectCreationScope.d.ts.map +1 -1
  38. package/src/in-the-dark/ShadowObjectCreationScope.js +23 -16
  39. package/src/in-the-dark/ShadowObjectCreationScope.js.map +2 -2
  40. package/src/in-the-dark/SignalsPath.d.ts +2 -0
  41. package/src/in-the-dark/SignalsPath.d.ts.map +1 -1
  42. package/src/in-the-dark/SignalsPath.js +4 -0
  43. package/src/in-the-dark/SignalsPath.js.map +2 -2
  44. package/src/in-the-dark/displayName.d.ts +4 -0
  45. package/src/in-the-dark/displayName.d.ts.map +1 -0
  46. package/src/in-the-dark/displayName.js +5 -0
  47. package/src/in-the-dark/displayName.js.map +7 -0
  48. package/src/index.d.ts +2 -0
  49. package/src/index.d.ts.map +1 -1
  50. package/src/index.js +4 -0
  51. package/src/index.js.map +2 -2
  52. package/src/inspect/createKernelSnapshot.d.ts +17 -0
  53. package/src/inspect/createKernelSnapshot.d.ts.map +1 -0
  54. package/src/inspect/createKernelSnapshot.js +247 -0
  55. package/src/inspect/createKernelSnapshot.js.map +7 -0
  56. package/src/inspect/createViewSnapshot.d.ts +10 -0
  57. package/src/inspect/createViewSnapshot.d.ts.map +1 -0
  58. package/src/inspect/createViewSnapshot.js +143 -0
  59. package/src/inspect/createViewSnapshot.js.map +7 -0
  60. package/src/inspect/normalizeInspectRequest.d.ts +32 -0
  61. package/src/inspect/normalizeInspectRequest.d.ts.map +1 -0
  62. package/src/inspect/normalizeInspectRequest.js +55 -0
  63. package/src/inspect/normalizeInspectRequest.js.map +7 -0
  64. package/src/inspect/serializeValue.d.ts +13 -0
  65. package/src/inspect/serializeValue.d.ts.map +1 -0
  66. package/src/inspect/serializeValue.js +149 -0
  67. package/src/inspect/serializeValue.js.map +7 -0
  68. package/src/inspect/types.d.ts +260 -0
  69. package/src/inspect/types.d.ts.map +1 -0
  70. package/src/inspect/types.js +1 -0
  71. package/src/inspect/types.js.map +7 -0
  72. package/src/model-context/ModelContextLike.d.ts +59 -0
  73. package/src/model-context/ModelContextLike.d.ts.map +1 -0
  74. package/src/model-context/ModelContextLike.js +15 -0
  75. package/src/model-context/ModelContextLike.js.map +7 -0
  76. package/src/model-context/exposeShadowEnvsToModelContext.d.ts +48 -0
  77. package/src/model-context/exposeShadowEnvsToModelContext.d.ts.map +1 -0
  78. package/src/model-context/exposeShadowEnvsToModelContext.js +39 -0
  79. package/src/model-context/exposeShadowEnvsToModelContext.js.map +7 -0
  80. package/src/model-context/redactProps.d.ts +14 -0
  81. package/src/model-context/redactProps.d.ts.map +1 -0
  82. package/src/model-context/redactProps.js +22 -0
  83. package/src/model-context/redactProps.js.map +7 -0
  84. package/src/model-context/sharedExposure.d.ts +32 -0
  85. package/src/model-context/sharedExposure.d.ts.map +1 -0
  86. package/src/model-context/sharedExposure.js +108 -0
  87. package/src/model-context/sharedExposure.js.map +7 -0
  88. package/src/model-context/toolSupport.d.ts +72 -0
  89. package/src/model-context/toolSupport.d.ts.map +1 -0
  90. package/src/model-context/toolSupport.js +145 -0
  91. package/src/model-context/toolSupport.js.map +7 -0
  92. package/src/model-context/tools/findEntities.d.ts +15 -0
  93. package/src/model-context/tools/findEntities.d.ts.map +1 -0
  94. package/src/model-context/tools/findEntities.js +71 -0
  95. package/src/model-context/tools/findEntities.js.map +7 -0
  96. package/src/model-context/tools/getEntity.d.ts +16 -0
  97. package/src/model-context/tools/getEntity.d.ts.map +1 -0
  98. package/src/model-context/tools/getEntity.js +65 -0
  99. package/src/model-context/tools/getEntity.js.map +7 -0
  100. package/src/model-context/tools/getEntityTree.d.ts +4 -0
  101. package/src/model-context/tools/getEntityTree.d.ts.map +1 -0
  102. package/src/model-context/tools/getEntityTree.js +58 -0
  103. package/src/model-context/tools/getEntityTree.js.map +7 -0
  104. package/src/model-context/tools/getRegistry.d.ts +14 -0
  105. package/src/model-context/tools/getRegistry.d.ts.map +1 -0
  106. package/src/model-context/tools/getRegistry.js +39 -0
  107. package/src/model-context/tools/getRegistry.js.map +7 -0
  108. package/src/model-context/tools/index.d.ts +9 -0
  109. package/src/model-context/tools/index.d.ts.map +1 -0
  110. package/src/model-context/tools/index.js +16 -0
  111. package/src/model-context/tools/index.js.map +7 -0
  112. package/src/model-context/tools/listEnvs.d.ts +33 -0
  113. package/src/model-context/tools/listEnvs.d.ts.map +1 -0
  114. package/src/model-context/tools/listEnvs.js +37 -0
  115. package/src/model-context/tools/listEnvs.js.map +7 -0
  116. package/src/model-context.d.ts +13 -0
  117. package/src/model-context.d.ts.map +1 -0
  118. package/src/model-context.js +15 -0
  119. package/src/model-context.js.map +7 -0
  120. package/src/shadow-objects.d.ts +2 -0
  121. package/src/shadow-objects.d.ts.map +1 -1
  122. package/src/shadow-objects.js +4 -0
  123. package/src/shadow-objects.js.map +2 -2
  124. package/src/testing/TestEntity.d.ts +79 -0
  125. package/src/testing/TestEntity.d.ts.map +1 -0
  126. package/src/testing/TestEntity.js +135 -0
  127. package/src/testing/TestEntity.js.map +7 -0
  128. package/src/testing/createTestKernel.d.ts +11 -0
  129. package/src/testing/createTestKernel.d.ts.map +1 -0
  130. package/src/testing/createTestKernel.js +132 -0
  131. package/src/testing/createTestKernel.js.map +7 -0
  132. package/src/testing/mountShadowObject.d.ts +21 -0
  133. package/src/testing/mountShadowObject.d.ts.map +1 -0
  134. package/src/testing/mountShadowObject.js +72 -0
  135. package/src/testing/mountShadowObject.js.map +7 -0
  136. package/src/testing/recordKernelErrors.d.ts +21 -0
  137. package/src/testing/recordKernelErrors.d.ts.map +1 -0
  138. package/src/testing/recordKernelErrors.js +39 -0
  139. package/src/testing/recordKernelErrors.js.map +7 -0
  140. package/src/testing/settle.d.ts +15 -0
  141. package/src/testing/settle.d.ts.map +1 -0
  142. package/src/testing/settle.js +19 -0
  143. package/src/testing/settle.js.map +7 -0
  144. package/src/testing/types.d.ts +119 -0
  145. package/src/testing/types.d.ts.map +1 -0
  146. package/src/testing/types.js +1 -0
  147. package/src/testing/types.js.map +7 -0
  148. package/src/testing.d.ts +14 -0
  149. package/src/testing.d.ts.map +1 -0
  150. package/src/testing.js +11 -0
  151. package/src/testing.js.map +7 -0
  152. package/src/types.d.ts +150 -10
  153. package/src/types.d.ts.map +1 -1
  154. package/src/utils/attr-utils.d.ts +5 -0
  155. package/src/utils/attr-utils.d.ts.map +1 -1
  156. package/src/utils/attr-utils.js +2 -0
  157. package/src/utils/attr-utils.js.map +2 -2
  158. package/src/view/IShadowObjectEnvProxy.d.ts +9 -0
  159. package/src/view/IShadowObjectEnvProxy.d.ts.map +1 -1
  160. package/src/view/LocalShadowObjectEnv.d.ts +7 -0
  161. package/src/view/LocalShadowObjectEnv.d.ts.map +1 -1
  162. package/src/view/LocalShadowObjectEnv.js +15 -1
  163. package/src/view/LocalShadowObjectEnv.js.map +2 -2
  164. package/src/view/RemoteWorkerEnv.d.ts +22 -3
  165. package/src/view/RemoteWorkerEnv.d.ts.map +1 -1
  166. package/src/view/RemoteWorkerEnv.js +50 -1
  167. package/src/view/RemoteWorkerEnv.js.map +2 -2
  168. package/src/view/ShadowEnv.d.ts +30 -0
  169. package/src/view/ShadowEnv.d.ts.map +1 -1
  170. package/src/view/ShadowEnv.js +101 -0
  171. package/src/view/ShadowEnv.js.map +3 -3
  172. package/src/worker/MessageRouter.d.ts.map +1 -1
  173. package/src/worker/MessageRouter.js +23 -0
  174. package/src/worker/MessageRouter.js.map +2 -2
@@ -0,0 +1,135 @@
1
+ import { emit } from "@spearwolf/eventize";
2
+ import { value } from "@spearwolf/signalize";
3
+ import { getDisplayName } from "../in-the-dark/displayName.js";
4
+ const toPropertyEntries = (props) => Object.entries(props).map(([key, val]) => [key, val]);
5
+ class TestEntityImpl {
6
+ #testKernel;
7
+ // The token the Kernel no longer answers for, once this Entity is destroyed. A handle stays
8
+ // readable after its Entity is gone -- a test asserts on what it held, and a getter that threw
9
+ // would turn that assertion into a crash.
10
+ #lastKnownToken;
11
+ constructor(testKernel, uuid, token) {
12
+ this.#testKernel = testKernel;
13
+ this.uuid = uuid;
14
+ this.#lastKnownToken = token;
15
+ }
16
+ get #kernel() {
17
+ return this.#testKernel.kernel;
18
+ }
19
+ get entity() {
20
+ return this.#kernel.getEntity(this.uuid);
21
+ }
22
+ get token() {
23
+ const current = this.#kernel.tokenOf(this.uuid);
24
+ if (current !== void 0) {
25
+ this.#lastKnownToken = current;
26
+ }
27
+ return this.#lastKnownToken;
28
+ }
29
+ /**
30
+ * A view onto the test kernel's own log, the way `token` and `entity` are views onto the Kernel.
31
+ * The handle stores nothing: a message is recorded for a uuid whether or not a handle for it
32
+ * existed at the time, so a handle built later answers for everything that Entity already sent.
33
+ */
34
+ get viewMessages() {
35
+ return this.#testKernel.viewMessagesOf(this.uuid);
36
+ }
37
+ createChild(token, props, options) {
38
+ return this.#testKernel.createEntity(token, props, { ...options, parent: this });
39
+ }
40
+ /**
41
+ * Writes through the Kernel rather than through `Entity.setProperties()`: `changeProperties()`
42
+ * re-resolves the constructor set afterwards, which is how a property route -- `token@prop` in a
43
+ * module's `routes` -- puts a Shadow Object on an Entity or takes it off again.
44
+ */
45
+ setProps(props) {
46
+ this.#kernel.changeProperties(this.uuid, toPropertyEntries(props));
47
+ }
48
+ /** A property set to `undefined` is a removal; see `ComponentPropertiesType` in `src/types.ts`. */
49
+ removeProps(...names) {
50
+ this.#kernel.changeProperties(
51
+ this.uuid,
52
+ names.map((name) => [name, void 0])
53
+ );
54
+ }
55
+ readProp(name) {
56
+ return this.entity.getProperty(name);
57
+ }
58
+ /**
59
+ * The effective value of an Entity Context, the one `useContext()` reads. It arrives a microtask
60
+ * after a provider wrote it -- `Entity` runs every context value through a `MicrotaskCollector` --
61
+ * so a test reads it after `settle()`.
62
+ */
63
+ readContext(name) {
64
+ return value(this.entity.useContext(name));
65
+ }
66
+ setToken(token) {
67
+ this.#kernel.changeToken(this.uuid, token);
68
+ }
69
+ setParent(parent, order) {
70
+ this.#kernel.setParent(this.uuid, parent?.uuid, order);
71
+ }
72
+ /**
73
+ * Delivery is synchronous, the way `Kernel.dispatchEventsToEntity()` is: a View event carries no
74
+ * structure, so nothing has to settle before a Shadow Object hears it.
75
+ */
76
+ sendViewEvent(type, data) {
77
+ this.#kernel.dispatchEventsToEntity(this.uuid, [{ type, data }]);
78
+ }
79
+ /**
80
+ * Emits on the Entity's own event bus. Every Shadow Object of this Entity is attached there as an
81
+ * eventize listener object, so a method named like the event is what receives it.
82
+ */
83
+ emit(eventName, ...args) {
84
+ emit(this.entity, eventName, ...args);
85
+ }
86
+ shadowObjects() {
87
+ return this.#kernel.findShadowObjects(this.uuid);
88
+ }
89
+ /**
90
+ * The one Shadow Object on this Entity that came out of `constructa`.
91
+ *
92
+ * Two rules in order, because one does not cover both constructor shapes, and because the second
93
+ * one is not exact. A class instance answers `instanceof` -- the `@ShadowObject` decorator wraps
94
+ * the class in a subclass, which still does -- and so does an object from a function constructor
95
+ * that returns nothing. Identity is asked first and, where anything answers it, alone.
96
+ *
97
+ * A function constructor that returns an object answers nothing: `new fn()` hands back that
98
+ * object, and it carries none of `fn`'s prototype. Only there does the display name decide, and
99
+ * only then, because a name is not an identity. `findShadowObjects()` and
100
+ * `describeShadowObjects()` walk the same bookkeeping in the same order, so the two lists line up
101
+ * index by index.
102
+ */
103
+ shadowObjectOf(constructa) {
104
+ const displayName = getDisplayName(constructa);
105
+ const instances = this.shadowObjects();
106
+ let matches = instances.filter((instance) => instance instanceof constructa);
107
+ if (matches.length === 0) {
108
+ const descriptions = this.describe();
109
+ matches = instances.filter((_, index) => descriptions[index]?.displayName === displayName);
110
+ }
111
+ if (matches.length === 0) {
112
+ throw new Error(`no shadow object built from "${displayName}" on entity ${this.uuid}`);
113
+ }
114
+ if (matches.length > 1) {
115
+ throw new Error(
116
+ `${matches.length} shadow objects built from "${displayName}" on entity ${this.uuid}; use shadowObjects() and pick one`
117
+ );
118
+ }
119
+ return matches[0];
120
+ }
121
+ describe() {
122
+ return this.#kernel.describeShadowObjects(this.uuid);
123
+ }
124
+ clearViewMessages() {
125
+ this.#testKernel.clearViewMessagesOf(this.uuid);
126
+ }
127
+ destroy() {
128
+ this.#kernel.destroyEntity(this.uuid);
129
+ }
130
+ }
131
+ export {
132
+ TestEntityImpl,
133
+ toPropertyEntries
134
+ };
135
+ //# sourceMappingURL=TestEntity.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/testing/TestEntity.ts"],
4
+ "sourcesContent": ["import {emit} from '@spearwolf/eventize';\nimport {value} from '@spearwolf/signalize';\nimport {getDisplayName} from '../in-the-dark/displayName.js';\nimport type {Entity} from '../in-the-dark/Entity.js';\nimport type {Kernel} from '../in-the-dark/Kernel.js';\nimport type {ComponentPropertiesType, ShadowObjectConstructor, ShadowObjectDescription, ShadowObjectType} from '../types.js';\nimport type {\n AnyShadowObjectConstructor,\n CreateEntityOptions,\n ShadowObjectInstance,\n TestEntity,\n ViewMessageRecord,\n} from './types.js';\n\n/** What a `TestEntityImpl` needs from the test kernel that owns it. */\nexport interface TestKernelInternals {\n readonly kernel: Kernel;\n createEntity(token: string, props?: Record<string, unknown>, options?: CreateEntityOptions): TestEntity;\n /** What the test kernel recorded for a uuid. The log is its own, so a handle never holds one. */\n viewMessagesOf(uuid: string): readonly ViewMessageRecord[];\n clearViewMessagesOf(uuid: string): void;\n}\n\n/**\n * Properties as a Kernel call wants them. `Object.entries()` drops symbol keys, which is right:\n * an Entity property is string-keyed. A value of `undefined` is a removal, which is what\n * `types.ts` says about a `ComponentPropertiesType` entry and what `removeProps()` relies on.\n */\nexport const toPropertyEntries = (props: Record<string, unknown>): ComponentPropertiesType =>\n Object.entries(props).map(([key, val]) => [key, val] as [string, unknown]);\n\nexport class TestEntityImpl implements TestEntity {\n readonly uuid: string;\n\n readonly #testKernel: TestKernelInternals;\n\n // The token the Kernel no longer answers for, once this Entity is destroyed. A handle stays\n // readable after its Entity is gone -- a test asserts on what it held, and a getter that threw\n // would turn that assertion into a crash.\n #lastKnownToken: string;\n\n constructor(testKernel: TestKernelInternals, uuid: string, token: string) {\n this.#testKernel = testKernel;\n this.uuid = uuid;\n this.#lastKnownToken = token;\n }\n\n get #kernel(): Kernel {\n return this.#testKernel.kernel;\n }\n\n get entity(): Entity {\n return this.#kernel.getEntity(this.uuid);\n }\n\n get token(): string {\n const current = this.#kernel.tokenOf(this.uuid);\n if (current !== undefined) {\n this.#lastKnownToken = current;\n }\n return this.#lastKnownToken;\n }\n\n /**\n * A view onto the test kernel's own log, the way `token` and `entity` are views onto the Kernel.\n * The handle stores nothing: a message is recorded for a uuid whether or not a handle for it\n * existed at the time, so a handle built later answers for everything that Entity already sent.\n */\n get viewMessages(): readonly ViewMessageRecord[] {\n return this.#testKernel.viewMessagesOf(this.uuid);\n }\n\n createChild(token: string, props?: Record<string, unknown>, options?: Omit<CreateEntityOptions, 'parent'>): TestEntity {\n return this.#testKernel.createEntity(token, props, {...options, parent: this});\n }\n\n /**\n * Writes through the Kernel rather than through `Entity.setProperties()`: `changeProperties()`\n * re-resolves the constructor set afterwards, which is how a property route -- `token@prop` in a\n * module's `routes` -- puts a Shadow Object on an Entity or takes it off again.\n */\n setProps(props: Record<string, unknown>): void {\n this.#kernel.changeProperties(this.uuid, toPropertyEntries(props));\n }\n\n /** A property set to `undefined` is a removal; see `ComponentPropertiesType` in `src/types.ts`. */\n removeProps(...names: string[]): void {\n this.#kernel.changeProperties(\n this.uuid,\n names.map((name) => [name, undefined] as [string, unknown]),\n );\n }\n\n readProp<T = unknown>(name: string): T | undefined {\n return this.entity.getProperty<T | undefined>(name);\n }\n\n /**\n * The effective value of an Entity Context, the one `useContext()` reads. It arrives a microtask\n * after a provider wrote it -- `Entity` runs every context value through a `MicrotaskCollector` --\n * so a test reads it after `settle()`.\n */\n readContext<T = unknown>(name: string | symbol): T | undefined {\n return value(this.entity.useContext<T | undefined>(name));\n }\n\n setToken(token: string): void {\n this.#kernel.changeToken(this.uuid, token);\n }\n\n setParent(parent: TestEntity | undefined, order?: number): void {\n this.#kernel.setParent(this.uuid, parent?.uuid, order);\n }\n\n /**\n * Delivery is synchronous, the way `Kernel.dispatchEventsToEntity()` is: a View event carries no\n * structure, so nothing has to settle before a Shadow Object hears it.\n */\n sendViewEvent(type: string, data?: unknown): void {\n this.#kernel.dispatchEventsToEntity(this.uuid, [{type, data}]);\n }\n\n /**\n * Emits on the Entity's own event bus. Every Shadow Object of this Entity is attached there as an\n * eventize listener object, so a method named like the event is what receives it.\n */\n emit(eventName: string | symbol, ...args: unknown[]): void {\n emit(this.entity, eventName, ...args);\n }\n\n shadowObjects(): ShadowObjectType[] {\n return this.#kernel.findShadowObjects(this.uuid);\n }\n\n /**\n * The one Shadow Object on this Entity that came out of `constructa`.\n *\n * Two rules in order, because one does not cover both constructor shapes, and because the second\n * one is not exact. A class instance answers `instanceof` -- the `@ShadowObject` decorator wraps\n * the class in a subclass, which still does -- and so does an object from a function constructor\n * that returns nothing. Identity is asked first and, where anything answers it, alone.\n *\n * A function constructor that returns an object answers nothing: `new fn()` hands back that\n * object, and it carries none of `fn`'s prototype. Only there does the display name decide, and\n * only then, because a name is not an identity. `findShadowObjects()` and\n * `describeShadowObjects()` walk the same bookkeeping in the same order, so the two lists line up\n * index by index.\n */\n shadowObjectOf<C extends AnyShadowObjectConstructor>(constructa: C): ShadowObjectInstance<C> {\n const displayName = getDisplayName(constructa as ShadowObjectConstructor);\n const instances = this.shadowObjects();\n\n let matches = instances.filter((instance) => instance instanceof (constructa as unknown as new (...args: any[]) => object));\n\n // The display name is the fallback, never a second rule beside identity. Two constructors are\n // free to carry one display name, and an `||` between the two would then count the other one's\n // Shadow Object as a match and report an ambiguity that is not there. Identity, where it\n // answers at all, is exact.\n //\n // What no rule separates is two *function* constructors of one name on one Entity: neither\n // leaves a prototype behind, so nothing tells their objects apart. The throw below names that\n // for what it is and points at `shadowObjects()`.\n if (matches.length === 0) {\n const descriptions = this.describe();\n matches = instances.filter((_, index) => descriptions[index]?.displayName === displayName);\n }\n\n if (matches.length === 0) {\n throw new Error(`no shadow object built from \"${displayName}\" on entity ${this.uuid}`);\n }\n if (matches.length > 1) {\n throw new Error(\n `${matches.length} shadow objects built from \"${displayName}\" on entity ${this.uuid}; use shadowObjects() and pick one`,\n );\n }\n\n return matches[0] as ShadowObjectInstance<C>;\n }\n\n describe(): ShadowObjectDescription[] {\n return this.#kernel.describeShadowObjects(this.uuid);\n }\n\n clearViewMessages(): void {\n this.#testKernel.clearViewMessagesOf(this.uuid);\n }\n\n destroy(): void {\n this.#kernel.destroyEntity(this.uuid);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { TestKernel, TestKernelOptions } from './types.js';
2
+ /**
3
+ * A Kernel for a unit test: its own Registry, object-shaped properties, recorded View messages,
4
+ * recorded Kernel errors, and a `settle()` that drains the microtask cascade.
5
+ *
6
+ * Everything runs on the real Kernel. There is no DOM, no ShadowEnv and no worker in it, and no
7
+ * `structuredClone` between the test and the Shadow Object -- a value handed in as a property or a
8
+ * context arrives by identity, a DOM node and a WebGL handle included.
9
+ */
10
+ export declare function createTestKernel(options?: TestKernelOptions): TestKernel;
11
+ //# sourceMappingURL=createTestKernel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createTestKernel.d.ts","sourceRoot":"","sources":["../../../src/testing/createTestKernel.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAKV,UAAU,EACV,iBAAiB,EAElB,MAAM,YAAY,CAAC;AA0KpB;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,iBAAsB,GAAG,UAAU,CAE5E"}
@@ -0,0 +1,132 @@
1
+ import { on } from "@spearwolf/eventize";
2
+ import { MessageToView } from "../constants.js";
3
+ import { importModule as importShadowObjectsModule } from "../in-the-dark/importModule.js";
4
+ import { Kernel } from "../in-the-dark/Kernel.js";
5
+ import { Registry } from "../in-the-dark/Registry.js";
6
+ import { generateUUID } from "../utils/generateUUID.js";
7
+ import { recordKernelErrors } from "./recordKernelErrors.js";
8
+ import { settle } from "./settle.js";
9
+ import { TestEntityImpl, toPropertyEntries } from "./TestEntity.js";
10
+ const NoViewMessages = Object.freeze([]);
11
+ class TestKernelImpl {
12
+ #handles = /* @__PURE__ */ new Map();
13
+ // The recording lives here rather than on the handles, because a message is dispatched for a uuid
14
+ // long before anything asks for its handle: a Shadow Object that calls `entity.kernel.createEntity()`
15
+ // makes one, and a handle built afterwards has to answer for what that Entity already sent.
16
+ #viewMessages = /* @__PURE__ */ new Map();
17
+ #importedModules = /* @__PURE__ */ new Set();
18
+ #recorder;
19
+ #unsubscribeMessageToView;
20
+ // Only a Registry this test kernel made is a Registry it may empty. One the caller handed in is
21
+ // the caller's, default or not, and clearing it would take the rest of the suite's definitions.
22
+ #ownsRegistry;
23
+ #failOnKernelErrors;
24
+ #disposed = false;
25
+ constructor(options) {
26
+ this.#ownsRegistry = options.registry === void 0;
27
+ this.#failOnKernelErrors = options.failOnKernelErrors ?? true;
28
+ this.registry = options.registry ?? new Registry();
29
+ this.kernel = new Kernel(this.registry);
30
+ this.#recorder = recordKernelErrors(this.kernel.logger, options.echoKernelErrors ?? false);
31
+ this.#unsubscribeMessageToView = on(this.kernel, MessageToView, (message) => {
32
+ const record = {
33
+ type: message.type,
34
+ data: message.data,
35
+ ...message.traverseChildren !== void 0 ? { traverseChildren: message.traverseChildren } : {}
36
+ };
37
+ const recorded = this.#viewMessages.get(message.uuid);
38
+ if (recorded === void 0) {
39
+ this.#viewMessages.set(message.uuid, [record]);
40
+ } else {
41
+ recorded.push(record);
42
+ }
43
+ });
44
+ }
45
+ get errors() {
46
+ return this.#recorder.records;
47
+ }
48
+ define(token, constructa) {
49
+ this.registry.define(token, constructa);
50
+ }
51
+ route(token, targets) {
52
+ this.registry.appendRoute(token, targets);
53
+ }
54
+ async importModule(module) {
55
+ await importShadowObjectsModule(this.kernel, module, this.#importedModules);
56
+ }
57
+ createEntity(token, props, options) {
58
+ const uuid = options?.uuid ?? generateUUID();
59
+ const previous = this.#handles.get(uuid);
60
+ const handle = new TestEntityImpl(this, uuid, token);
61
+ this.#handles.set(uuid, handle);
62
+ try {
63
+ this.kernel.createEntity(
64
+ uuid,
65
+ token,
66
+ options?.parent?.uuid,
67
+ options?.order ?? 0,
68
+ props ? toPropertyEntries(props) : void 0,
69
+ options?.autoDestructionOnParentRemoval ?? false
70
+ );
71
+ } catch (error) {
72
+ if (previous === void 0) {
73
+ this.#handles.delete(uuid);
74
+ } else {
75
+ this.#handles.set(uuid, previous);
76
+ }
77
+ throw error;
78
+ }
79
+ return handle;
80
+ }
81
+ entity(uuid) {
82
+ const known = this.#handles.get(uuid);
83
+ if (known !== void 0) return known;
84
+ const token = this.kernel.tokenOf(uuid);
85
+ if (token === void 0) return void 0;
86
+ const handle = new TestEntityImpl(this, uuid, token);
87
+ this.#handles.set(uuid, handle);
88
+ return handle;
89
+ }
90
+ /** What was recorded for a uuid, whether or not a handle for it existed at the time. @internal */
91
+ viewMessagesOf(uuid) {
92
+ return this.#viewMessages.get(uuid) ?? NoViewMessages;
93
+ }
94
+ /** Empties one uuid's recorded list, and no other's. @internal */
95
+ clearViewMessagesOf(uuid) {
96
+ this.#viewMessages.delete(uuid);
97
+ }
98
+ settle() {
99
+ return settle();
100
+ }
101
+ clearErrors() {
102
+ this.#recorder.clear();
103
+ }
104
+ dispose() {
105
+ if (this.#disposed) return;
106
+ this.#disposed = true;
107
+ this.kernel.destroy();
108
+ this.#unsubscribeMessageToView();
109
+ if (this.#ownsRegistry) {
110
+ this.registry.clear();
111
+ }
112
+ const errors = this.#recorder.records.filter((record) => record.level === "error");
113
+ this.#recorder.unhook();
114
+ this.#handles.clear();
115
+ this.#viewMessages.clear();
116
+ this.#importedModules.clear();
117
+ if (this.#failOnKernelErrors && errors.length > 0) {
118
+ const first = errors[0];
119
+ throw new Error(
120
+ `the kernel reported ${errors.length} error(s) that this test did not acknowledge. The first one was: ${first.args.map((arg) => String(arg)).join(" ")}. Assert on testKernel.errors and call clearErrors(), or pass {failOnKernelErrors: false}.`,
121
+ ...first.error !== void 0 ? [{ cause: first.error }] : []
122
+ );
123
+ }
124
+ }
125
+ }
126
+ function createTestKernel(options = {}) {
127
+ return new TestKernelImpl(options);
128
+ }
129
+ export {
130
+ createTestKernel
131
+ };
132
+ //# sourceMappingURL=createTestKernel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/testing/createTestKernel.ts"],
4
+ "sourcesContent": ["import {on} from '@spearwolf/eventize';\nimport {MessageToView} from '../constants.js';\nimport {importModule as importShadowObjectsModule} from '../in-the-dark/importModule.js';\nimport {Kernel, type MessageToViewEvent} from '../in-the-dark/Kernel.js';\nimport {Registry} from '../in-the-dark/Registry.js';\nimport type {ShadowObjectConstructor, ShadowObjectsModule} from '../types.js';\nimport {generateUUID} from '../utils/generateUUID.js';\nimport {type KernelErrorRecorder, recordKernelErrors} from './recordKernelErrors.js';\nimport {settle} from './settle.js';\nimport {TestEntityImpl, type TestKernelInternals, toPropertyEntries} from './TestEntity.js';\nimport type {\n AnyShadowObjectConstructor,\n CreateEntityOptions,\n KernelErrorRecord,\n TestEntity,\n TestKernel,\n TestKernelOptions,\n ViewMessageRecord,\n} from './types.js';\n\n/** What `viewMessagesOf()` answers for a uuid nothing was ever recorded for. */\nconst NoViewMessages: readonly ViewMessageRecord[] = Object.freeze([]);\n\nclass TestKernelImpl implements TestKernel, TestKernelInternals {\n readonly kernel: Kernel;\n readonly registry: Registry;\n\n readonly #handles = new Map<string, TestEntityImpl>();\n\n // The recording lives here rather than on the handles, because a message is dispatched for a uuid\n // long before anything asks for its handle: a Shadow Object that calls `entity.kernel.createEntity()`\n // makes one, and a handle built afterwards has to answer for what that Entity already sent.\n readonly #viewMessages = new Map<string, ViewMessageRecord[]>();\n\n readonly #importedModules = new Set<ShadowObjectsModule>();\n readonly #recorder: KernelErrorRecorder;\n readonly #unsubscribeMessageToView: () => void;\n // Only a Registry this test kernel made is a Registry it may empty. One the caller handed in is\n // the caller's, default or not, and clearing it would take the rest of the suite's definitions.\n readonly #ownsRegistry: boolean;\n readonly #failOnKernelErrors: boolean;\n\n #disposed = false;\n\n constructor(options: TestKernelOptions) {\n this.#ownsRegistry = options.registry === undefined;\n this.#failOnKernelErrors = options.failOnKernelErrors ?? true;\n this.registry = options.registry ?? new Registry();\n this.kernel = new Kernel(this.registry);\n this.#recorder = recordKernelErrors(this.kernel.logger, options.echoKernelErrors ?? false);\n\n // Nothing clones the payload on the way here, unlike `LocalShadowObjectEnv`, which runs it\n // through `structuredClone`. A test asserts on the object the Shadow Object sent.\n //\n // `traverseChildren` is recorded rather than acted on: the flag is an instruction to the View\n // layer, and there is no View layer here. The spread is conditional because\n // `exactOptionalPropertyTypes` forbids writing `undefined` into an optional field.\n this.#unsubscribeMessageToView = on(this.kernel, MessageToView, (message: MessageToViewEvent) => {\n const record: ViewMessageRecord = {\n type: message.type,\n data: message.data,\n ...(message.traverseChildren !== undefined ? {traverseChildren: message.traverseChildren} : {}),\n };\n\n const recorded = this.#viewMessages.get(message.uuid);\n if (recorded === undefined) {\n this.#viewMessages.set(message.uuid, [record]);\n } else {\n recorded.push(record);\n }\n });\n }\n\n get errors(): readonly KernelErrorRecord[] {\n return this.#recorder.records;\n }\n\n define(token: string, constructa: AnyShadowObjectConstructor): void {\n this.registry.define(token, constructa as ShadowObjectConstructor);\n }\n\n route(token: string, targets: string[]): void {\n this.registry.appendRoute(token, targets);\n }\n\n async importModule(module: ShadowObjectsModule): Promise<void> {\n await importShadowObjectsModule(this.kernel, module, this.#importedModules);\n }\n\n createEntity(token: string, props?: Record<string, unknown>, options?: CreateEntityOptions): TestEntity {\n const uuid = options?.uuid ?? generateUUID();\n\n // The handle goes in before the Kernel call, because a Shadow Object constructor may ask this\n // facade for its own handle, and the one it gets has to be the one this call hands back.\n const previous = this.#handles.get(uuid);\n const handle = new TestEntityImpl(this, uuid, token);\n this.#handles.set(uuid, handle);\n\n try {\n this.kernel.createEntity(\n uuid,\n token,\n options?.parent?.uuid,\n options?.order ?? 0,\n props ? toPropertyEntries(props) : undefined,\n options?.autoDestructionOnParentRemoval ?? false,\n );\n } catch (error) {\n // Restore rather than delete. `options.uuid` may name a uuid this facade already held a handle\n // for -- the Kernel refuses that creation with an `EntityUuidInUseError` -- and deleting would\n // evict a handle the caller still holds, leaving every later message for that uuid on the\n // replacement this failed call put in.\n if (previous === undefined) {\n this.#handles.delete(uuid);\n } else {\n this.#handles.set(uuid, previous);\n }\n throw error;\n }\n\n return handle;\n }\n\n entity(uuid: string): TestEntity | undefined {\n const known = this.#handles.get(uuid);\n if (known !== undefined) return known;\n\n const token = this.kernel.tokenOf(uuid);\n if (token === undefined) return undefined;\n\n const handle = new TestEntityImpl(this, uuid, token);\n this.#handles.set(uuid, handle);\n return handle;\n }\n\n /** What was recorded for a uuid, whether or not a handle for it existed at the time. @internal */\n viewMessagesOf(uuid: string): readonly ViewMessageRecord[] {\n return this.#viewMessages.get(uuid) ?? NoViewMessages;\n }\n\n /** Empties one uuid's recorded list, and no other's. @internal */\n clearViewMessagesOf(uuid: string): void {\n this.#viewMessages.delete(uuid);\n }\n\n settle(): Promise<void> {\n return settle();\n }\n\n clearErrors(): void {\n this.#recorder.clear();\n }\n\n dispose(): void {\n if (this.#disposed) return;\n this.#disposed = true;\n\n this.kernel.destroy();\n this.#unsubscribeMessageToView();\n\n if (this.#ownsRegistry) {\n this.registry.clear();\n }\n\n // Read before the unhook, because unhooking does not clear the records but a later read has no\n // reason to reach the recorder again.\n const errors = this.#recorder.records.filter((record) => record.level === 'error');\n\n this.#recorder.unhook();\n this.#handles.clear();\n this.#viewMessages.clear();\n this.#importedModules.clear();\n\n // Only `error`. A warning is recorded and readable, and never fails a run: `importModule()`\n // warns about a module two `extends` chains have in common, which is a shape of the module\n // graph and not a mistake.\n if (this.#failOnKernelErrors && errors.length > 0) {\n const first = errors[0]!;\n throw new Error(\n `the kernel reported ${errors.length} error(s) that this test did not acknowledge. ` +\n `The first one was: ${first.args.map((arg) => String(arg)).join(' ')}. ` +\n 'Assert on testKernel.errors and call clearErrors(), or pass {failOnKernelErrors: false}.',\n ...(first.error !== undefined ? [{cause: first.error}] : []),\n );\n }\n }\n}\n\n/**\n * A Kernel for a unit test: its own Registry, object-shaped properties, recorded View messages,\n * recorded Kernel errors, and a `settle()` that drains the microtask cascade.\n *\n * Everything runs on the real Kernel. There is no DOM, no ShadowEnv and no worker in it, and no\n * `structuredClone` between the test and the Shadow Object -- a value handed in as a property or a\n * context arrives by identity, a DOM node and a WebGL handle included.\n */\nexport function createTestKernel(options: TestKernelOptions = {}): TestKernel {\n return new TestKernelImpl(options);\n}\n"],
5
+ "mappings": "AAAA,SAAQ,UAAS;AACjB,SAAQ,qBAAoB;AAC5B,SAAQ,gBAAgB,iCAAgC;AACxD,SAAQ,cAAsC;AAC9C,SAAQ,gBAAe;AAEvB,SAAQ,oBAAmB;AAC3B,SAAkC,0BAAyB;AAC3D,SAAQ,cAAa;AACrB,SAAQ,gBAA0C,yBAAwB;AAY1E,MAAM,iBAA+C,OAAO,OAAO,CAAC,CAAC;AAErE,MAAM,eAA0D;AAAA,EAIrD,WAAW,oBAAI,IAA4B;AAAA;AAAA;AAAA;AAAA,EAK3C,gBAAgB,oBAAI,IAAiC;AAAA,EAErD,mBAAmB,oBAAI,IAAyB;AAAA,EAChD;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA,EACA;AAAA,EAET,YAAY;AAAA,EAEZ,YAAY,SAA4B;AACtC,SAAK,gBAAgB,QAAQ,aAAa;AAC1C,SAAK,sBAAsB,QAAQ,sBAAsB;AACzD,SAAK,WAAW,QAAQ,YAAY,IAAI,SAAS;AACjD,SAAK,SAAS,IAAI,OAAO,KAAK,QAAQ;AACtC,SAAK,YAAY,mBAAmB,KAAK,OAAO,QAAQ,QAAQ,oBAAoB,KAAK;AAQzF,SAAK,4BAA4B,GAAG,KAAK,QAAQ,eAAe,CAAC,YAAgC;AAC/F,YAAM,SAA4B;AAAA,QAChC,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ;AAAA,QACd,GAAI,QAAQ,qBAAqB,SAAY,EAAC,kBAAkB,QAAQ,iBAAgB,IAAI,CAAC;AAAA,MAC/F;AAEA,YAAM,WAAW,KAAK,cAAc,IAAI,QAAQ,IAAI;AACpD,UAAI,aAAa,QAAW;AAC1B,aAAK,cAAc,IAAI,QAAQ,MAAM,CAAC,MAAM,CAAC;AAAA,MAC/C,OAAO;AACL,iBAAS,KAAK,MAAM;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,SAAuC;AACzC,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA,EAEA,OAAO,OAAe,YAA8C;AAClE,SAAK,SAAS,OAAO,OAAO,UAAqC;AAAA,EACnE;AAAA,EAEA,MAAM,OAAe,SAAyB;AAC5C,SAAK,SAAS,YAAY,OAAO,OAAO;AAAA,EAC1C;AAAA,EAEA,MAAM,aAAa,QAA4C;AAC7D,UAAM,0BAA0B,KAAK,QAAQ,QAAQ,KAAK,gBAAgB;AAAA,EAC5E;AAAA,EAEA,aAAa,OAAe,OAAiC,SAA2C;AACtG,UAAM,OAAO,SAAS,QAAQ,aAAa;AAI3C,UAAM,WAAW,KAAK,SAAS,IAAI,IAAI;AACvC,UAAM,SAAS,IAAI,eAAe,MAAM,MAAM,KAAK;AACnD,SAAK,SAAS,IAAI,MAAM,MAAM;AAE9B,QAAI;AACF,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,SAAS,SAAS;AAAA,QAClB,QAAQ,kBAAkB,KAAK,IAAI;AAAA,QACnC,SAAS,kCAAkC;AAAA,MAC7C;AAAA,IACF,SAAS,OAAO;AAKd,UAAI,aAAa,QAAW;AAC1B,aAAK,SAAS,OAAO,IAAI;AAAA,MAC3B,OAAO;AACL,aAAK,SAAS,IAAI,MAAM,QAAQ;AAAA,MAClC;AACA,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAsC;AAC3C,UAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AACpC,QAAI,UAAU,OAAW,QAAO;AAEhC,UAAM,QAAQ,KAAK,OAAO,QAAQ,IAAI;AACtC,QAAI,UAAU,OAAW,QAAO;AAEhC,UAAM,SAAS,IAAI,eAAe,MAAM,MAAM,KAAK;AACnD,SAAK,SAAS,IAAI,MAAM,MAAM;AAC9B,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAAe,MAA4C;AACzD,WAAO,KAAK,cAAc,IAAI,IAAI,KAAK;AAAA,EACzC;AAAA;AAAA,EAGA,oBAAoB,MAAoB;AACtC,SAAK,cAAc,OAAO,IAAI;AAAA,EAChC;AAAA,EAEA,SAAwB;AACtB,WAAO,OAAO;AAAA,EAChB;AAAA,EAEA,cAAoB;AAClB,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEA,UAAgB;AACd,QAAI,KAAK,UAAW;AACpB,SAAK,YAAY;AAEjB,SAAK,OAAO,QAAQ;AACpB,SAAK,0BAA0B;AAE/B,QAAI,KAAK,eAAe;AACtB,WAAK,SAAS,MAAM;AAAA,IACtB;AAIA,UAAM,SAAS,KAAK,UAAU,QAAQ,OAAO,CAAC,WAAW,OAAO,UAAU,OAAO;AAEjF,SAAK,UAAU,OAAO;AACtB,SAAK,SAAS,MAAM;AACpB,SAAK,cAAc,MAAM;AACzB,SAAK,iBAAiB,MAAM;AAK5B,QAAI,KAAK,uBAAuB,OAAO,SAAS,GAAG;AACjD,YAAM,QAAQ,OAAO,CAAC;AACtB,YAAM,IAAI;AAAA,QACR,uBAAuB,OAAO,MAAM,oEACZ,MAAM,KAAK,IAAI,CAAC,QAAQ,OAAO,GAAG,CAAC,EAAE,KAAK,GAAG,CAAC;AAAA,QAEtE,GAAI,MAAM,UAAU,SAAY,CAAC,EAAC,OAAO,MAAM,MAAK,CAAC,IAAI,CAAC;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AACF;AAUO,SAAS,iBAAiB,UAA6B,CAAC,GAAe;AAC5E,SAAO,IAAI,eAAe,OAAO;AACnC;",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ import type { AnyShadowObjectConstructor, MountedShadowObject, MountOptions } from './types.js';
2
+ /**
3
+ * Mounts one Shadow Object on one Entity and hands back everything a test asserts on.
4
+ *
5
+ * Asynchronous because the framework is. `contexts` are provided by a synthetic parent Entity, and a
6
+ * context value reaches a reader a microtask after it was written.
7
+ *
8
+ * Two settles, and they buy different things. `settle()` drains the whole microtask cascade, so the
9
+ * second one alone already carries every context value down to every effect. The first is what a
10
+ * constructor gets: it lets the parent's own context signal settle before the object under test is
11
+ * built, so a `useParentContext()` read inside the constructor body answers with the value instead
12
+ * of `undefined` -- that reader is a direct link to the parent and does not pass the Entity's
13
+ * collector.
14
+ *
15
+ * What neither settle can do is hand a context value to a constructor through `useContext()`. That
16
+ * reader does pass the collector, and the value behind it lands after the constructor has returned,
17
+ * in a mounted test as much as in a running application. A context is read inside an effect or a
18
+ * memo.
19
+ */
20
+ export declare function mountShadowObject<C extends AnyShadowObjectConstructor>(constructa: C, options?: MountOptions): Promise<MountedShadowObject<C>>;
21
+ //# sourceMappingURL=mountShadowObject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mountShadowObject.d.ts","sourceRoot":"","sources":["../../../src/testing/mountShadowObject.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,0BAA0B,EAC1B,mBAAmB,EACnB,YAAY,EAIb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,0BAA0B,EAC1E,UAAU,EAAE,CAAC,EACb,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAwBjC"}
@@ -0,0 +1,72 @@
1
+ import { generateUUID } from "../utils/generateUUID.js";
2
+ import { createTestKernel } from "./createTestKernel.js";
3
+ async function mountShadowObject(constructa, options = {}) {
4
+ const testKernel = createTestKernel({
5
+ ...options.registry !== void 0 ? { registry: options.registry } : {},
6
+ ...options.failOnKernelErrors !== void 0 ? { failOnKernelErrors: options.failOnKernelErrors } : {},
7
+ ...options.echoKernelErrors !== void 0 ? { echoKernelErrors: options.echoKernelErrors } : {}
8
+ });
9
+ try {
10
+ return await mount(testKernel, constructa, options);
11
+ } catch (error) {
12
+ try {
13
+ testKernel.dispose();
14
+ } catch {
15
+ }
16
+ throw error;
17
+ }
18
+ }
19
+ async function mount(testKernel, constructa, options) {
20
+ const token = options.token ?? `mounted-shadow-object-${generateUUID()}`;
21
+ testKernel.define(token, constructa);
22
+ let parent;
23
+ const providedContexts = options.contexts ?? {};
24
+ const contextNames = Reflect.ownKeys(providedContexts);
25
+ if (contextNames.length > 0) {
26
+ const providerToken = `mounted-context-provider-${generateUUID()}`;
27
+ testKernel.define(providerToken, function MountedContextProvider({ provideContext }) {
28
+ for (const name of contextNames) {
29
+ provideContext(name, providedContexts[name]);
30
+ }
31
+ });
32
+ parent = testKernel.createEntity(providerToken);
33
+ await testKernel.settle();
34
+ }
35
+ const ent = testKernel.createEntity(token, options.props, parent !== void 0 ? { parent } : {});
36
+ await testKernel.settle();
37
+ const instance = ent.shadowObjectOf(constructa);
38
+ return {
39
+ get uuid() {
40
+ return ent.uuid;
41
+ },
42
+ get token() {
43
+ return ent.token;
44
+ },
45
+ get entity() {
46
+ return ent.entity;
47
+ },
48
+ get viewMessages() {
49
+ return ent.viewMessages;
50
+ },
51
+ instance,
52
+ testKernel,
53
+ setProps: (props) => ent.setProps(props),
54
+ removeProps: (...names) => ent.removeProps(...names),
55
+ readProp: (name) => ent.readProp(name),
56
+ readContext: (name) => ent.readContext(name),
57
+ setToken: (nextToken) => ent.setToken(nextToken),
58
+ setParent: (nextParent, order) => ent.setParent(nextParent, order),
59
+ sendViewEvent: (type, data) => ent.sendViewEvent(type, data),
60
+ emit: (eventName, ...args) => ent.emit(eventName, ...args),
61
+ shadowObjects: () => ent.shadowObjects(),
62
+ shadowObjectOf: (other) => ent.shadowObjectOf(other),
63
+ describe: () => ent.describe(),
64
+ clearViewMessages: () => ent.clearViewMessages(),
65
+ settle: () => testKernel.settle(),
66
+ dispose: () => testKernel.dispose()
67
+ };
68
+ }
69
+ export {
70
+ mountShadowObject
71
+ };
72
+ //# sourceMappingURL=mountShadowObject.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/testing/mountShadowObject.ts"],
4
+ "sourcesContent": ["import type {ShadowObjectCreationAPI} from '../types.js';\nimport {generateUUID} from '../utils/generateUUID.js';\nimport {createTestKernel} from './createTestKernel.js';\nimport type {\n AnyShadowObjectConstructor,\n MountedShadowObject,\n MountOptions,\n ShadowObjectInstance,\n TestEntity,\n TestKernel,\n} from './types.js';\n\n/**\n * Mounts one Shadow Object on one Entity and hands back everything a test asserts on.\n *\n * Asynchronous because the framework is. `contexts` are provided by a synthetic parent Entity, and a\n * context value reaches a reader a microtask after it was written.\n *\n * Two settles, and they buy different things. `settle()` drains the whole microtask cascade, so the\n * second one alone already carries every context value down to every effect. The first is what a\n * constructor gets: it lets the parent's own context signal settle before the object under test is\n * built, so a `useParentContext()` read inside the constructor body answers with the value instead\n * of `undefined` -- that reader is a direct link to the parent and does not pass the Entity's\n * collector.\n *\n * What neither settle can do is hand a context value to a constructor through `useContext()`. That\n * reader does pass the collector, and the value behind it lands after the constructor has returned,\n * in a mounted test as much as in a running application. A context is read inside an effect or a\n * memo.\n */\nexport async function mountShadowObject<C extends AnyShadowObjectConstructor>(\n constructa: C,\n options: MountOptions = {},\n): Promise<MountedShadowObject<C>> {\n const testKernel = createTestKernel({\n ...(options.registry !== undefined ? {registry: options.registry} : {}),\n ...(options.failOnKernelErrors !== undefined ? {failOnKernelErrors: options.failOnKernelErrors} : {}),\n ...(options.echoKernelErrors !== undefined ? {echoKernelErrors: options.echoKernelErrors} : {}),\n });\n\n // Everything from here on can throw -- a Shadow Object constructor propagates through\n // `createEntity()`, and `shadowObjectOf()` throws when nothing matched. The test kernel is\n // unreachable on that path, because the caller is handed an error rather than a mount, so it is\n // torn down here or never. With `{registry: Registry.get()}` \"never\" would leave the generated\n // token and the synthetic provider's token in the process-wide Registry for the rest of the run.\n try {\n return await mount(testKernel, constructa, options);\n } catch (error) {\n try {\n testKernel.dispose();\n } catch {\n // `dispose()` throws over an unacknowledged Kernel error -- and a failed mount is exactly the\n // situation that records one. That error must not take the place of the one the caller is\n // waiting for: the original says why the mount failed, and it is the one that gets through.\n }\n throw error;\n }\n}\n\n/** The body of the mount, split out so the caller above has one place to put the teardown guard. */\nasync function mount<C extends AnyShadowObjectConstructor>(\n testKernel: TestKernel,\n constructa: C,\n options: MountOptions,\n): Promise<MountedShadowObject<C>> {\n const token = options.token ?? `mounted-shadow-object-${generateUUID()}`;\n testKernel.define(token, constructa);\n\n let parent: TestEntity | undefined;\n\n // Hoisted rather than read off `options` inside the closure: a narrowing done out here does not\n // survive into a function body, and the alternative is a non-null assertion per read.\n const providedContexts: Record<string | symbol, unknown> = options.contexts ?? {};\n const contextNames = Reflect.ownKeys(providedContexts);\n\n if (contextNames.length > 0) {\n const providerToken = `mounted-context-provider-${generateUUID()}`;\n // A function as a context value is read by `provideContext()` as a signal reader rather than as\n // the value. That is `provideContext()`'s contract; the documentation names it next to this.\n testKernel.define(providerToken, function MountedContextProvider({provideContext}: ShadowObjectCreationAPI) {\n for (const name of contextNames) {\n provideContext(name, providedContexts[name]);\n }\n });\n parent = testKernel.createEntity(providerToken);\n await testKernel.settle();\n }\n\n const ent = testKernel.createEntity(token, options.props, parent !== undefined ? {parent} : {});\n\n await testKernel.settle();\n\n const instance = ent.shadowObjectOf(constructa);\n\n // Written out rather than delegated through a prototype: `TestEntityImpl` keeps its state in\n // private fields, which are branded per instance, so a method reached through `Object.create()`\n // would throw on the first field access.\n return {\n get uuid() {\n return ent.uuid;\n },\n get token() {\n return ent.token;\n },\n get entity() {\n return ent.entity;\n },\n get viewMessages() {\n return ent.viewMessages;\n },\n instance: instance as ShadowObjectInstance<C>,\n testKernel,\n setProps: (props) => ent.setProps(props),\n removeProps: (...names) => ent.removeProps(...names),\n readProp: <T>(name: string) => ent.readProp<T>(name),\n readContext: <T>(name: string | symbol) => ent.readContext<T>(name),\n setToken: (nextToken) => ent.setToken(nextToken),\n setParent: (nextParent, order) => ent.setParent(nextParent, order),\n sendViewEvent: (type, data) => ent.sendViewEvent(type, data),\n emit: (eventName, ...args) => ent.emit(eventName, ...args),\n shadowObjects: () => ent.shadowObjects(),\n shadowObjectOf: <C2 extends AnyShadowObjectConstructor>(other: C2) => ent.shadowObjectOf(other),\n describe: () => ent.describe(),\n clearViewMessages: () => ent.clearViewMessages(),\n settle: () => testKernel.settle(),\n dispose: () => testKernel.dispose(),\n };\n}\n"],
5
+ "mappings": "AACA,SAAQ,oBAAmB;AAC3B,SAAQ,wBAAuB;AA4B/B,eAAsB,kBACpB,YACA,UAAwB,CAAC,GACQ;AACjC,QAAM,aAAa,iBAAiB;AAAA,IAClC,GAAI,QAAQ,aAAa,SAAY,EAAC,UAAU,QAAQ,SAAQ,IAAI,CAAC;AAAA,IACrE,GAAI,QAAQ,uBAAuB,SAAY,EAAC,oBAAoB,QAAQ,mBAAkB,IAAI,CAAC;AAAA,IACnG,GAAI,QAAQ,qBAAqB,SAAY,EAAC,kBAAkB,QAAQ,iBAAgB,IAAI,CAAC;AAAA,EAC/F,CAAC;AAOD,MAAI;AACF,WAAO,MAAM,MAAM,YAAY,YAAY,OAAO;AAAA,EACpD,SAAS,OAAO;AACd,QAAI;AACF,iBAAW,QAAQ;AAAA,IACrB,QAAQ;AAAA,IAIR;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAe,MACb,YACA,YACA,SACiC;AACjC,QAAM,QAAQ,QAAQ,SAAS,yBAAyB,aAAa,CAAC;AACtE,aAAW,OAAO,OAAO,UAAU;AAEnC,MAAI;AAIJ,QAAM,mBAAqD,QAAQ,YAAY,CAAC;AAChF,QAAM,eAAe,QAAQ,QAAQ,gBAAgB;AAErD,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,gBAAgB,4BAA4B,aAAa,CAAC;AAGhE,eAAW,OAAO,eAAe,SAAS,uBAAuB,EAAC,eAAc,GAA4B;AAC1G,iBAAW,QAAQ,cAAc;AAC/B,uBAAe,MAAM,iBAAiB,IAAI,CAAC;AAAA,MAC7C;AAAA,IACF,CAAC;AACD,aAAS,WAAW,aAAa,aAAa;AAC9C,UAAM,WAAW,OAAO;AAAA,EAC1B;AAEA,QAAM,MAAM,WAAW,aAAa,OAAO,QAAQ,OAAO,WAAW,SAAY,EAAC,OAAM,IAAI,CAAC,CAAC;AAE9F,QAAM,WAAW,OAAO;AAExB,QAAM,WAAW,IAAI,eAAe,UAAU;AAK9C,SAAO;AAAA,IACL,IAAI,OAAO;AACT,aAAO,IAAI;AAAA,IACb;AAAA,IACA,IAAI,QAAQ;AACV,aAAO,IAAI;AAAA,IACb;AAAA,IACA,IAAI,SAAS;AACX,aAAO,IAAI;AAAA,IACb;AAAA,IACA,IAAI,eAAe;AACjB,aAAO,IAAI;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,CAAC,UAAU,IAAI,SAAS,KAAK;AAAA,IACvC,aAAa,IAAI,UAAU,IAAI,YAAY,GAAG,KAAK;AAAA,IACnD,UAAU,CAAI,SAAiB,IAAI,SAAY,IAAI;AAAA,IACnD,aAAa,CAAI,SAA0B,IAAI,YAAe,IAAI;AAAA,IAClE,UAAU,CAAC,cAAc,IAAI,SAAS,SAAS;AAAA,IAC/C,WAAW,CAAC,YAAY,UAAU,IAAI,UAAU,YAAY,KAAK;AAAA,IACjE,eAAe,CAAC,MAAM,SAAS,IAAI,cAAc,MAAM,IAAI;AAAA,IAC3D,MAAM,CAAC,cAAc,SAAS,IAAI,KAAK,WAAW,GAAG,IAAI;AAAA,IACzD,eAAe,MAAM,IAAI,cAAc;AAAA,IACvC,gBAAgB,CAAwC,UAAc,IAAI,eAAe,KAAK;AAAA,IAC9F,UAAU,MAAM,IAAI,SAAS;AAAA,IAC7B,mBAAmB,MAAM,IAAI,kBAAkB;AAAA,IAC/C,QAAQ,MAAM,WAAW,OAAO;AAAA,IAChC,SAAS,MAAM,WAAW,QAAQ;AAAA,EACpC;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ import type { ConsoleLogger } from '../utils/ConsoleLogger.js';
2
+ import type { KernelErrorRecord } from './types.js';
3
+ export interface KernelErrorRecorder {
4
+ readonly records: readonly KernelErrorRecord[];
5
+ clear(): void;
6
+ unhook(): void;
7
+ }
8
+ /**
9
+ * Records what a Kernel reports through its logger, and keeps it off the console.
10
+ *
11
+ * The Kernel answers a failing teardown by reporting it and carrying on -- `runGuarded()` hands
12
+ * nothing back to a caller, because on that path there is no caller left to decide anything. A test
13
+ * therefore has no way to see a Shadow Object whose `onDestroy` threw, unless it watches the logger.
14
+ *
15
+ * `error` and `warn` live on `ConsoleLogger.prototype`, so writing them here shadows them with own
16
+ * properties and `unhook()` deletes those again. `warn` is recorded even though the real method is
17
+ * gated behind `ConsoleLogger.isWarn`, which is off outside a loopback host: a test wants the report
18
+ * whatever the host it runs on happens to be.
19
+ */
20
+ export declare function recordKernelErrors(logger: ConsoleLogger, echo?: boolean): KernelErrorRecorder;
21
+ //# sourceMappingURL=recordKernelErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recordKernelErrors.d.ts","sourceRoot":"","sources":["../../../src/testing/recordKernelErrors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,UAAQ,GAAG,mBAAmB,CAwC3F"}
@@ -0,0 +1,39 @@
1
+ function recordKernelErrors(logger, echo = false) {
2
+ const records = [];
3
+ const originals = {
4
+ error: logger.error,
5
+ warn: logger.warn
6
+ };
7
+ const wasOwn = {
8
+ error: Object.hasOwn(logger, "error"),
9
+ warn: Object.hasOwn(logger, "warn")
10
+ };
11
+ const record = (level) => (...args) => {
12
+ const last = args.length > 0 ? args[args.length - 1] : void 0;
13
+ records.push({ level, args, ...last instanceof Error ? { error: last } : {} });
14
+ if (echo) {
15
+ originals[level].apply(logger, args);
16
+ }
17
+ };
18
+ logger.error = record("error");
19
+ logger.warn = record("warn");
20
+ return {
21
+ records,
22
+ clear() {
23
+ records.length = 0;
24
+ },
25
+ unhook() {
26
+ for (const level of ["error", "warn"]) {
27
+ if (wasOwn[level]) {
28
+ logger[level] = originals[level];
29
+ } else {
30
+ delete logger[level];
31
+ }
32
+ }
33
+ }
34
+ };
35
+ }
36
+ export {
37
+ recordKernelErrors
38
+ };
39
+ //# sourceMappingURL=recordKernelErrors.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/testing/recordKernelErrors.ts"],
4
+ "sourcesContent": ["import type {ConsoleLogger} from '../utils/ConsoleLogger.js';\nimport type {KernelErrorRecord} from './types.js';\n\nexport interface KernelErrorRecorder {\n readonly records: readonly KernelErrorRecord[];\n clear(): void;\n unhook(): void;\n}\n\n/**\n * Records what a Kernel reports through its logger, and keeps it off the console.\n *\n * The Kernel answers a failing teardown by reporting it and carrying on -- `runGuarded()` hands\n * nothing back to a caller, because on that path there is no caller left to decide anything. A test\n * therefore has no way to see a Shadow Object whose `onDestroy` threw, unless it watches the logger.\n *\n * `error` and `warn` live on `ConsoleLogger.prototype`, so writing them here shadows them with own\n * properties and `unhook()` deletes those again. `warn` is recorded even though the real method is\n * gated behind `ConsoleLogger.isWarn`, which is off outside a loopback host: a test wants the report\n * whatever the host it runs on happens to be.\n */\nexport function recordKernelErrors(logger: ConsoleLogger, echo = false): KernelErrorRecorder {\n const records: KernelErrorRecord[] = [];\n\n const originals: Record<'error' | 'warn', (...args: any[]) => void> = {\n error: logger.error,\n warn: logger.warn,\n };\n const wasOwn: Record<'error' | 'warn', boolean> = {\n error: Object.hasOwn(logger, 'error'),\n warn: Object.hasOwn(logger, 'warn'),\n };\n\n const record =\n (level: 'error' | 'warn') =>\n (...args: any[]) => {\n const last = args.length > 0 ? args[args.length - 1] : undefined;\n records.push({level, args, ...(last instanceof Error ? {error: last} : {})});\n if (echo) {\n originals[level].apply(logger, args);\n }\n };\n\n logger.error = record('error');\n logger.warn = record('warn');\n\n return {\n records,\n clear() {\n records.length = 0;\n },\n unhook() {\n for (const level of ['error', 'warn'] as const) {\n if (wasOwn[level]) {\n logger[level] = originals[level];\n } else {\n delete (logger as Partial<ConsoleLogger>)[level];\n }\n }\n },\n };\n}\n"],
5
+ "mappings": "AAqBO,SAAS,mBAAmB,QAAuB,OAAO,OAA4B;AAC3F,QAAM,UAA+B,CAAC;AAEtC,QAAM,YAAgE;AAAA,IACpE,OAAO,OAAO;AAAA,IACd,MAAM,OAAO;AAAA,EACf;AACA,QAAM,SAA4C;AAAA,IAChD,OAAO,OAAO,OAAO,QAAQ,OAAO;AAAA,IACpC,MAAM,OAAO,OAAO,QAAQ,MAAM;AAAA,EACpC;AAEA,QAAM,SACJ,CAAC,UACD,IAAI,SAAgB;AAClB,UAAM,OAAO,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,CAAC,IAAI;AACvD,YAAQ,KAAK,EAAC,OAAO,MAAM,GAAI,gBAAgB,QAAQ,EAAC,OAAO,KAAI,IAAI,CAAC,EAAE,CAAC;AAC3E,QAAI,MAAM;AACR,gBAAU,KAAK,EAAE,MAAM,QAAQ,IAAI;AAAA,IACrC;AAAA,EACF;AAEF,SAAO,QAAQ,OAAO,OAAO;AAC7B,SAAO,OAAO,OAAO,MAAM;AAE3B,SAAO;AAAA,IACL;AAAA,IACA,QAAQ;AACN,cAAQ,SAAS;AAAA,IACnB;AAAA,IACA,SAAS;AACP,iBAAW,SAAS,CAAC,SAAS,MAAM,GAAY;AAC9C,YAAI,OAAO,KAAK,GAAG;AACjB,iBAAO,KAAK,IAAI,UAAU,KAAK;AAAA,QACjC,OAAO;AACL,iBAAQ,OAAkC,KAAK;AAAA,QACjD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Waits until the microtask queue is empty, including everything it grows while it is drained.
3
+ *
4
+ * A single `await Promise.resolve()` clears one generation of microtasks and lies about the rest,
5
+ * and the Kernel produces more than one: `dispatchMessageToView()` queues a microtask of its own,
6
+ * and the `MicrotaskCollector` behind the Entity Contexts is documented to accept writes from
7
+ * inside its own delivery, which schedules the next round. A macrotask hop waits for all of them.
8
+ *
9
+ * `MessageChannel` rather than `setTimeout`, because a test that installs fake timers is a test
10
+ * that has frozen `setTimeout`, and a `settle()` that never resolves under fake timers is an
11
+ * afternoon of debugging per consumer. A message port is not a timer. The `setTimeout` fallback
12
+ * covers a realm that has no `MessageChannel`.
13
+ */
14
+ export declare function settle(): Promise<void>;
15
+ //# sourceMappingURL=settle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settle.d.ts","sourceRoot":"","sources":["../../../src/testing/settle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAetC"}
@@ -0,0 +1,19 @@
1
+ function settle() {
2
+ return new Promise((resolve) => {
3
+ if (typeof MessageChannel === "function") {
4
+ const channel = new MessageChannel();
5
+ channel.port1.onmessage = () => {
6
+ channel.port1.close();
7
+ channel.port2.close();
8
+ resolve();
9
+ };
10
+ channel.port2.postMessage(void 0);
11
+ } else {
12
+ setTimeout(resolve, 0);
13
+ }
14
+ });
15
+ }
16
+ export {
17
+ settle
18
+ };
19
+ //# sourceMappingURL=settle.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/testing/settle.ts"],
4
+ "sourcesContent": ["/**\n * Waits until the microtask queue is empty, including everything it grows while it is drained.\n *\n * A single `await Promise.resolve()` clears one generation of microtasks and lies about the rest,\n * and the Kernel produces more than one: `dispatchMessageToView()` queues a microtask of its own,\n * and the `MicrotaskCollector` behind the Entity Contexts is documented to accept writes from\n * inside its own delivery, which schedules the next round. A macrotask hop waits for all of them.\n *\n * `MessageChannel` rather than `setTimeout`, because a test that installs fake timers is a test\n * that has frozen `setTimeout`, and a `settle()` that never resolves under fake timers is an\n * afternoon of debugging per consumer. A message port is not a timer. The `setTimeout` fallback\n * covers a realm that has no `MessageChannel`.\n */\nexport function settle(): Promise<void> {\n return new Promise<void>((resolve) => {\n if (typeof MessageChannel === 'function') {\n const channel = new MessageChannel();\n // Assigning `onmessage` starts the port; no explicit `start()` is needed.\n channel.port1.onmessage = () => {\n channel.port1.close();\n channel.port2.close();\n resolve();\n };\n channel.port2.postMessage(undefined);\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n"],
5
+ "mappings": "AAaO,SAAS,SAAwB;AACtC,SAAO,IAAI,QAAc,CAAC,YAAY;AACpC,QAAI,OAAO,mBAAmB,YAAY;AACxC,YAAM,UAAU,IAAI,eAAe;AAEnC,cAAQ,MAAM,YAAY,MAAM;AAC9B,gBAAQ,MAAM,MAAM;AACpB,gBAAQ,MAAM,MAAM;AACpB,gBAAQ;AAAA,MACV;AACA,cAAQ,MAAM,YAAY,MAAS;AAAA,IACrC,OAAO;AACL,iBAAW,SAAS,CAAC;AAAA,IACvB;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }