@ng-native/testing 0.16.0 → 0.17.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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Testing Library's `configure`, with React Native Testing Library's name for its one option here:
3
+ * how long `waitFor`, every `findBy` query and `waitForElementToBeRemoved` keep trying when a call
4
+ * does not say. A suite on a slow machine sets it once, in a setup file, rather than passing a
5
+ * `timeout` to every call.
6
+ *
7
+ * It holds for every test after it, in every file the runner loads into the same process, until
8
+ * `resetToDefaults()`; a test that changes it puts it back in `finally`.
9
+ */
10
+ /** What `configure` sets. */
11
+ export interface Config {
12
+ /** How long an async query keeps trying, in milliseconds. Default 1000. */
13
+ readonly asyncUtilTimeout: number;
14
+ }
15
+ /** Change what the async queries do when a call does not say. */
16
+ export declare function configure(options: Partial<Config>): void;
17
+ /** Put back the defaults. */
18
+ export declare function resetToDefaults(): void;
19
+ /** What the async queries do now. */
20
+ export declare const config: () => Config;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Testing Library's `configure`, with React Native Testing Library's name for its one option here:
3
+ * how long `waitFor`, every `findBy` query and `waitForElementToBeRemoved` keep trying when a call
4
+ * does not say. A suite on a slow machine sets it once, in a setup file, rather than passing a
5
+ * `timeout` to every call.
6
+ *
7
+ * It holds for every test after it, in every file the runner loads into the same process, until
8
+ * `resetToDefaults()`; a test that changes it puts it back in `finally`.
9
+ */
10
+ const DEFAULTS = { asyncUtilTimeout: 1000 };
11
+ let current = DEFAULTS;
12
+ /** Change what the async queries do when a call does not say. */
13
+ export function configure(options) {
14
+ current = { ...current, ...options };
15
+ }
16
+ /** Put back the defaults. */
17
+ export function resetToDefaults() {
18
+ current = DEFAULTS;
19
+ }
20
+ /** What the async queries do now. */
21
+ export const config = () => current;
22
+ //# sourceMappingURL=configure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure.js","sourceRoot":"","sources":["../src/configure.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,MAAM,QAAQ,GAAW,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC;AAEpD,IAAI,OAAO,GAAW,QAAQ,CAAC;AAE/B,iEAAiE;AACjE,MAAM,UAAU,SAAS,CAAC,OAAwB;IAChD,OAAO,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;AACvC,CAAC;AAED,6BAA6B;AAC7B,MAAM,UAAU,eAAe;IAC7B,OAAO,GAAG,QAAQ,CAAC;AACrB,CAAC;AAED,qCAAqC;AACrC,MAAM,CAAC,MAAM,MAAM,GAAG,GAAW,EAAE,CAAC,OAAO,CAAC","sourcesContent":["/**\n * Testing Library's `configure`, with React Native Testing Library's name for its one option here:\n * how long `waitFor`, every `findBy` query and `waitForElementToBeRemoved` keep trying when a call\n * does not say. A suite on a slow machine sets it once, in a setup file, rather than passing a\n * `timeout` to every call.\n *\n * It holds for every test after it, in every file the runner loads into the same process, until\n * `resetToDefaults()`; a test that changes it puts it back in `finally`.\n */\n\n/** What `configure` sets. */\nexport interface Config {\n /** How long an async query keeps trying, in milliseconds. Default 1000. */\n readonly asyncUtilTimeout: number;\n}\n\nconst DEFAULTS: Config = { asyncUtilTimeout: 1000 };\n\nlet current: Config = DEFAULTS;\n\n/** Change what the async queries do when a call does not say. */\nexport function configure(options: Partial<Config>): void {\n current = { ...current, ...options };\n}\n\n/** Put back the defaults. */\nexport function resetToDefaults(): void {\n current = DEFAULTS;\n}\n\n/** What the async queries do now. */\nexport const config = (): Config => current;\n"]}
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { memoryDatabase, type MemoryDatabase } from './memory-database.ts';
13
13
  export { compileCss, type CompileCssOptions } from './compile-css.ts';
14
14
  export type { BoundQueries, ByRoleOptions, Matcher, TextMatchOptions } from './queries.ts';
15
15
  export { waitFor, type WaitForOptions } from './wait-for.ts';
16
+ export { configure, resetToDefaults, type Config } from './configure.ts';
16
17
  export { gestureOf } from './gesture-of.ts';
17
18
  export type { TestGesture } from './gesture-handler.ts';
18
19
  export { fireEvent, userEvent, type EventPayload, type TypeOptions, type UserEvent, } from './events.ts';
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ export { injectService } from "./inject-service.js";
12
12
  export { memoryDatabase } from "./memory-database.js";
13
13
  export { compileCss } from "./compile-css.js";
14
14
  export { waitFor } from "./wait-for.js";
15
+ export { configure, resetToDefaults } from "./configure.js";
15
16
  export { gestureOf } from "./gesture-of.js";
16
17
  export { fireEvent, userEvent, } from "./events.js";
17
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAwC,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,yBAAyB,EACzB,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAA0B,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAuB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EACL,SAAS,EACT,SAAS,GAIV,MAAM,aAAa,CAAC","sourcesContent":["/**\n * The fake `nativeFabricUIManager`, and a Testing Library on top of it. Kept out of the engine so\n * nothing in a shipped bundle can reach it, and out of `test/` so an app's own tests can use it.\n *\n * The runners live beside `src/` rather than in it: `@ng-native/testing/vitest` and\n * `@ng-native/testing/register` are plain JavaScript, because they are loaded by Node before\n * anything can compile TypeScript for it.\n */\nexport { createFakeFabric, type FakeFabric, type FakeFabricNode } from './test-utils.ts';\nexport {\n cleanup,\n render,\n screen,\n settle,\n waitForElementToBeRemoved,\n within,\n type RenderOptions,\n type RenderResult,\n} from './render.ts';\nexport { injectService, type InjectServiceOptions } from './inject-service.ts';\nexport { memoryDatabase, type MemoryDatabase } from './memory-database.ts';\nexport { compileCss, type CompileCssOptions } from './compile-css.ts';\nexport type { BoundQueries, ByRoleOptions, Matcher, TextMatchOptions } from './queries.ts';\nexport { waitFor, type WaitForOptions } from './wait-for.ts';\nexport { gestureOf } from './gesture-of.ts';\nexport type { TestGesture } from './gesture-handler.ts';\nexport {\n fireEvent,\n userEvent,\n type EventPayload,\n type TypeOptions,\n type UserEvent,\n} from './events.ts';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAwC,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,yBAAyB,EACzB,MAAM,GAGP,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAA6B,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAA0B,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAE,OAAO,EAAuB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAe,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EACL,SAAS,EACT,SAAS,GAIV,MAAM,aAAa,CAAC","sourcesContent":["/**\n * The fake `nativeFabricUIManager`, and a Testing Library on top of it. Kept out of the engine so\n * nothing in a shipped bundle can reach it, and out of `test/` so an app's own tests can use it.\n *\n * The runners live beside `src/` rather than in it: `@ng-native/testing/vitest` and\n * `@ng-native/testing/register` are plain JavaScript, because they are loaded by Node before\n * anything can compile TypeScript for it.\n */\nexport { createFakeFabric, type FakeFabric, type FakeFabricNode } from './test-utils.ts';\nexport {\n cleanup,\n render,\n screen,\n settle,\n waitForElementToBeRemoved,\n within,\n type RenderOptions,\n type RenderResult,\n} from './render.ts';\nexport { injectService, type InjectServiceOptions } from './inject-service.ts';\nexport { memoryDatabase, type MemoryDatabase } from './memory-database.ts';\nexport { compileCss, type CompileCssOptions } from './compile-css.ts';\nexport type { BoundQueries, ByRoleOptions, Matcher, TextMatchOptions } from './queries.ts';\nexport { waitFor, type WaitForOptions } from './wait-for.ts';\nexport { configure, resetToDefaults, type Config } from './configure.ts';\nexport { gestureOf } from './gesture-of.ts';\nexport type { TestGesture } from './gesture-handler.ts';\nexport {\n fireEvent,\n userEvent,\n type EventPayload,\n type TypeOptions,\n type UserEvent,\n} from './events.ts';\n"]}
@@ -121,6 +121,21 @@ export function createFakeFabric() {
121
121
  children: keepChildren ? source.children : [],
122
122
  }, source.instanceHandle);
123
123
  };
124
+ /**
125
+ * A clone with the children it was handed, as native takes them: in the call that makes it, as
126
+ * an array (`shadowNodeListFromValue`). Anything else is a mistake here, such as props passed
127
+ * where the children go, and native would read it as no list of views at all.
128
+ */
129
+ const holding = (parent, children) => {
130
+ if (!Array.isArray(children))
131
+ throw new TypeError('A clone takes its children as an array.');
132
+ const into = parent;
133
+ for (const child of children) {
134
+ adopt(into.reactTag, child);
135
+ into.children.push(child);
136
+ }
137
+ return parent;
138
+ };
124
139
  const node = (reactTag, viewName, props, instanceHandle) => withHandle({ handle: ++handle, reactTag, viewName, props: defined(props), children: [] }, instanceHandle);
125
140
  // Keys sorted at every depth, so a golden does not depend on the order props were set in. An
126
141
  // array of keys as the replacer would sort too, but it is an allow-list for nested objects as
@@ -145,17 +160,21 @@ export function createFakeFabric() {
145
160
  calls.createNode++;
146
161
  return node(reactTag, viewName, props, instanceHandle);
147
162
  },
148
- cloneNodeWithNewChildren(n) {
163
+ cloneNodeWithNewChildren(n, ...children) {
149
164
  calls.cloneWithChildren++;
150
- return clone(n);
165
+ // As `UIManagerBinding.cpp` reads it: no second argument is no children.
166
+ return holding(clone(n), children.length ? children[0] : []);
151
167
  },
152
168
  cloneNodeWithNewProps(n, newProps) {
153
169
  calls.cloneWithProps++;
154
170
  return clone(n, newProps, true);
155
171
  },
156
- cloneNodeWithNewChildrenAndProps(n, newProps) {
172
+ cloneNodeWithNewChildrenAndProps(n, ...rest) {
157
173
  calls.cloneWithChildrenAndProps++;
158
- return clone(n, newProps);
174
+ // As the binding reads it: with three arguments the children are the second, and with any
175
+ // other count there are none and the props are the second.
176
+ const [children, newProps] = rest.length === 2 ? rest : [[], rest[0]];
177
+ return holding(clone(n, newProps), children);
159
178
  },
160
179
  appendChild(parent, child) {
161
180
  const into = parent;
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fake `global.nativeFabricUIManager`. Records every host call so tests can assert on\n * the committed tree without a simulator. This is what lets the whole seam be tested in Node.\n */\nimport type { FabricNode, FabricNodeSet, FabricUIManager } from '@ng-native/fabric';\n\nexport interface FakeFabricNode {\n handle: number;\n reactTag: number;\n viewName: string;\n props: Record<string, unknown>;\n children: FakeFabricNode[];\n instanceHandle: unknown;\n /**\n * The node this one is under, as of the last query: null at the top. Set by the queries as they\n * walk the committed tree. A commit shares the nodes it did not change with the one before, so\n * on a node held across commits a later query may set it again, to the parent in that commit.\n */\n readonly parent?: FakeFabricNode | null;\n}\n\nexport interface FakeFabric extends FabricUIManager {\n /** Root child set from the most recent `completeRoot`. */\n readonly committed: FakeFabricNode[];\n readonly calls: {\n createNode: number;\n cloneWithChildren: number;\n cloneWithProps: number;\n cloneWithChildrenAndProps: number;\n completeRoot: number;\n };\n /**\n * Indented view-name tree, for golden assertions. `props: true` includes the flattened\n * prop payload, so a parity test catches prop regressions as well as shape ones.\n */\n render(options?: { props?: boolean }): string;\n find(viewName: string): FakeFabricNode | undefined;\n /** Drive the registered Fabric event handler, as the C++ side would. */\n emit(target: FakeFabricNode, topLevelType: string, nativeEvent?: unknown): void;\n /** Every `setIsJSResponder` the renderer made, in order. */\n readonly responderCalls: { viewName: string; isResponder: boolean; block: boolean }[];\n /**\n * Every `dispatchCommand`, in order. `node` is the node it was sent to, as a non-enumerable\n * property: there to read, and left out of a `deepEqual`.\n */\n readonly commands: {\n viewName: string;\n name: string;\n args: readonly unknown[];\n readonly node?: FakeFabricNode;\n }[];\n /**\n * What `measureInWindow` answers, keyed by `nativeID` and falling back to view name.\n *\n * A test sets the frames it cares about; a node with no entry is not measured at all, which is\n * what the real thing does for a view the platform has not laid out. The two keys are for the\n * two shapes a test comes in: one candidate on screen, where the view name says all there is to\n * say, and several, where which one got measured is the whole question.\n */\n readonly frames: Map<string, { x: number; y: number; width: number; height: number }>;\n reset(): void;\n}\n\n/**\n * Keep the instance handle off the node's enumerable props. It is the engine's own node, which\n * reaches the whole application, and a node a query returns ends up printed in any failed\n * assertion about it: Node's inspector and Vitest's formatter both walk enumerable keys only.\n */\nfunction withHandle(node: Omit<FakeFabricNode, 'instanceHandle'>, instanceHandle: unknown) {\n Object.defineProperty(node, 'instanceHandle', {\n value: instanceHandle,\n enumerable: false,\n writable: true,\n });\n return node as FakeFabricNode;\n}\n\n/** The props JSI hands native: a key whose value is undefined is not there at all. */\nconst defined = (props: object): Record<string, unknown> =>\n Object.fromEntries(Object.entries(props).filter(([, value]) => value !== undefined));\n\nexport function createFakeFabric(): FakeFabric {\n let handle = 0;\n let committed: FakeFabricNode[] = [];\n let handler: ((h: unknown, t: string, e: unknown) => void) | null = null;\n const responderCalls: { viewName: string; isResponder: boolean; block: boolean }[] = [];\n const commands: { viewName: string; name: string; args: readonly unknown[] }[] = [];\n const frames = new Map<string, { x: number; y: number; width: number; height: number }>();\n const calls = {\n createNode: 0,\n cloneWithChildren: 0,\n cloneWithProps: 0,\n cloneWithChildrenAndProps: 0,\n completeRoot: 0,\n };\n\n /*\n * The parent each view has been appended to, by tag. Native keeps one parent per view for as\n * long as it lives - `ShadowNodeFamily::setParent` asserts it, in a debug build, and a release\n * build goes on with the old parent and measures and lays out against the wrong ancestry - so a\n * view that moves has to be created again, as React does. This fails the test that tries.\n */\n const parents = new Map<number, number>();\n const ROOT = -1;\n const adopt = (parent: number, child: FakeFabricNode): void => {\n const was = parents.get(child.reactTag);\n if (was !== undefined && was !== parent) {\n throw new Error(\n `${child.viewName} ${child.reactTag} was appended to ${parent} after ${was}: native ` +\n 'never re-parents a view, and asserts on it. Create it again instead.',\n );\n }\n parents.set(child.reactTag, parent);\n };\n\n /*\n * The views that have been unmounted, by tag. Fabric drops a view's event target once no\n * committed tree holds it (`EventEmitter::setEnabled`) and never makes a new one, so a view\n * committed again after leaving the tree dispatches every event with a null instance handle, as\n * `emit` does here. A view that comes back has to be created again, as React does.\n */\n const unmounted = new Set<number>();\n const unmount = (n: FakeFabricNode): void => {\n unmounted.add(n.reactTag);\n for (const child of n.children) unmount(child);\n };\n // Walks only what changed, as `updateMountedFlag` does: a subtree committed as it was is skipped.\n const diff = (before: FakeFabricNode[], after: FakeFabricNode[]): void => {\n if (before === after) return;\n const next = new Map(after.map((n) => [n.reactTag, n]));\n for (const was of before) {\n const now = next.get(was.reactTag);\n if (!now) unmount(was);\n else if (now !== was) diff(was.children, now.children);\n }\n };\n\n /*\n * iOS animates a presented modal out when it is committed with `visible: false`, then reports\n * `topDismiss` once, and React Native's Modal.js keeps it in the tree until then. The fake\n * reports it as soon as that commit is done, and lets one dismissal through per close, so a test\n * that also sends it by hand does not see `(dismiss)` twice.\n */\n const dismissing = new Set<number>();\n const dismissed = new Set<number>();\n const dismissAfterCommit = (): void => {\n if (dismissing.size === 0 || !handler) return;\n const tags = [...dismissing];\n dismissing.clear();\n queueMicrotask(() => {\n for (const tag of tags) {\n const host = findByTag(committed, tag);\n if (host && host.props['visible'] === false) fake.emit(host, 'topDismiss');\n }\n });\n };\n const findByTag = (nodes: FakeFabricNode[], tag: number): FakeFabricNode | undefined => {\n for (const n of nodes) {\n const hit = n.reactTag === tag ? n : findByTag(n.children, tag);\n if (hit) return hit;\n }\n return undefined;\n };\n /** A modal host committed as closing, or that left the tree closing. */\n const closing = (target: FakeFabricNode): boolean =>\n (findByTag(committed, target.reactTag) ?? target).props['visible'] === false;\n /** A modal host's props are changing: a close owes one dismissal, and a show resets it. */\n const noteModal = (source: FakeFabricNode, props: Record<string, unknown> | undefined): void => {\n if (source.viewName !== 'ModalHostView' || !props || !('visible' in props)) return;\n if (props['visible'] !== false) dismissed.delete(source.reactTag);\n else if (source.props['visible'] !== false) dismissing.add(source.reactTag);\n };\n\n const clone = (n: FabricNode, props?: object, keepChildren = false): FabricNode => {\n const source = n as unknown as FakeFabricNode;\n const given = props ? defined(props) : undefined;\n noteModal(source, given);\n return withHandle(\n {\n ...source,\n handle: ++handle,\n // Fabric merges raw props onto the source props rather than replacing them, and JSI\n // drops a key whose value is undefined on the way (JSIDynamic.cpp), so it changes nothing.\n props: given ? { ...source.props, ...given } : source.props,\n children: keepChildren ? source.children : [],\n },\n source.instanceHandle,\n ) as unknown as FabricNode;\n };\n\n const node = (\n reactTag: number,\n viewName: string,\n props: object,\n instanceHandle: unknown,\n ): FakeFabricNode =>\n withHandle(\n { handle: ++handle, reactTag, viewName, props: defined(props), children: [] },\n instanceHandle,\n );\n\n // Keys sorted at every depth, so a golden does not depend on the order props were set in. An\n // array of keys as the replacer would sort too, but it is an allow-list for nested objects as\n // well, and printed `accessibilityState: { disabled: true }` as `{}`.\n const sorted = (_key: string, value: unknown): unknown =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n : value;\n\n const walk = (nodes: FakeFabricNode[], depth: number, withProps: boolean): string[] =>\n nodes.flatMap((n) => {\n // The text is printed for itself, so the payload beside it is every other prop: a text\n // field's placeholder and keyboard, where a `RawText` has nothing more to say.\n const { text: said, ...rest } = n.props;\n const text = typeof said === 'string' ? ` \"${said}\"` : '';\n const shown = text ? rest : n.props;\n const props =\n withProps && Object.keys(shown).length ? ' ' + JSON.stringify(shown, sorted) : '';\n return [\n ' '.repeat(depth) + n.viewName + text + props,\n ...walk(n.children, depth + 1, withProps),\n ];\n });\n\n const fake: FakeFabric = {\n createNode(reactTag, viewName, _rootTag, props, instanceHandle) {\n calls.createNode++;\n return node(reactTag, viewName, props, instanceHandle) as unknown as FabricNode;\n },\n cloneNodeWithNewChildren(n) {\n calls.cloneWithChildren++;\n return clone(n);\n },\n cloneNodeWithNewProps(n, newProps) {\n calls.cloneWithProps++;\n return clone(n, newProps, true);\n },\n cloneNodeWithNewChildrenAndProps(n, newProps) {\n calls.cloneWithChildrenAndProps++;\n return clone(n, newProps);\n },\n appendChild(parent, child) {\n const into = parent as unknown as FakeFabricNode;\n adopt(into.reactTag, child as unknown as FakeFabricNode);\n into.children.push(child as unknown as FakeFabricNode);\n return parent;\n },\n createChildSet() {\n return [] as FabricNodeSet;\n },\n appendChildToSet(set, child) {\n adopt(ROOT, child as unknown as FakeFabricNode);\n (set as FakeFabricNode[]).push(child as unknown as FakeFabricNode);\n },\n completeRoot(_rootTag, set) {\n calls.completeRoot++;\n diff(committed, set as FakeFabricNode[]);\n committed = set as FakeFabricNode[];\n dismissAfterCommit();\n },\n registerEventHandler(fn) {\n handler = fn;\n },\n setIsJSResponder(node, isResponder, block) {\n responderCalls.push({\n viewName: (node as unknown as FakeFabricNode).viewName,\n isResponder,\n block,\n });\n },\n measureInWindow(node, onSuccess) {\n // By `nativeID` first, so a test that cares which of two views was measured can say so; by\n // view name otherwise, which is enough when there is only one candidate on screen.\n const fake = node as unknown as FakeFabricNode;\n const frame = frames.get(String(fake.props['nativeID'])) ?? frames.get(fake.viewName);\n if (frame) onSuccess(frame.x, frame.y, frame.width, frame.height);\n },\n dispatchCommand(node, name, args) {\n const command = { viewName: (node as unknown as FakeFabricNode).viewName, name, args };\n // Which node, for a test that has several of one view; off the enumerable keys, so a\n // `deepEqual` against `{ viewName, name, args }` still holds.\n Object.defineProperty(command, 'node', { value: node, enumerable: false });\n commands.push(command as typeof command & { node: FakeFabricNode });\n },\n responderCalls,\n commands,\n frames,\n get committed() {\n return committed;\n },\n calls,\n render(options) {\n return walk(committed, 0, options?.props === true).join('\\n');\n },\n find(viewName) {\n const search = (nodes: FakeFabricNode[]): FakeFabricNode | undefined => {\n for (const n of nodes) {\n if (n.viewName === viewName) return n;\n const hit = search(n.children);\n if (hit) return hit;\n }\n return undefined;\n };\n return search(committed);\n },\n emit(target, type, nativeEvent = {}) {\n if (!handler) throw new Error('no Fabric event handler registered');\n if (type === 'topDismiss' && target.viewName === 'ModalHostView') {\n if (dismissed.has(target.reactTag)) return;\n if (closing(target)) dismissed.add(target.reactTag);\n }\n handler(unmounted.has(target.reactTag) ? null : target.instanceHandle, type, nativeEvent);\n },\n reset() {\n calls.createNode = 0;\n calls.cloneWithChildren = 0;\n calls.cloneWithProps = 0;\n calls.cloneWithChildrenAndProps = 0;\n calls.completeRoot = 0;\n },\n };\n\n return fake;\n}\n"]}
1
+ {"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fake `global.nativeFabricUIManager`. Records every host call so tests can assert on\n * the committed tree without a simulator. This is what lets the whole seam be tested in Node.\n */\nimport type { FabricNode, FabricNodeSet, FabricUIManager } from '@ng-native/fabric';\n\nexport interface FakeFabricNode {\n handle: number;\n reactTag: number;\n viewName: string;\n props: Record<string, unknown>;\n children: FakeFabricNode[];\n instanceHandle: unknown;\n /**\n * The node this one is under, as of the last query: null at the top. Set by the queries as they\n * walk the committed tree. A commit shares the nodes it did not change with the one before, so\n * on a node held across commits a later query may set it again, to the parent in that commit.\n */\n readonly parent?: FakeFabricNode | null;\n}\n\nexport interface FakeFabric extends FabricUIManager {\n /** Root child set from the most recent `completeRoot`. */\n readonly committed: FakeFabricNode[];\n readonly calls: {\n createNode: number;\n cloneWithChildren: number;\n cloneWithProps: number;\n cloneWithChildrenAndProps: number;\n completeRoot: number;\n };\n /**\n * Indented view-name tree, for golden assertions. `props: true` includes the flattened\n * prop payload, so a parity test catches prop regressions as well as shape ones.\n */\n render(options?: { props?: boolean }): string;\n find(viewName: string): FakeFabricNode | undefined;\n /** Drive the registered Fabric event handler, as the C++ side would. */\n emit(target: FakeFabricNode, topLevelType: string, nativeEvent?: unknown): void;\n /** Every `setIsJSResponder` the renderer made, in order. */\n readonly responderCalls: { viewName: string; isResponder: boolean; block: boolean }[];\n /**\n * Every `dispatchCommand`, in order. `node` is the node it was sent to, as a non-enumerable\n * property: there to read, and left out of a `deepEqual`.\n */\n readonly commands: {\n viewName: string;\n name: string;\n args: readonly unknown[];\n readonly node?: FakeFabricNode;\n }[];\n /**\n * What `measureInWindow` answers, keyed by `nativeID` and falling back to view name.\n *\n * A test sets the frames it cares about; a node with no entry is not measured at all, which is\n * what the real thing does for a view the platform has not laid out. The two keys are for the\n * two shapes a test comes in: one candidate on screen, where the view name says all there is to\n * say, and several, where which one got measured is the whole question.\n */\n readonly frames: Map<string, { x: number; y: number; width: number; height: number }>;\n reset(): void;\n}\n\n/**\n * Keep the instance handle off the node's enumerable props. It is the engine's own node, which\n * reaches the whole application, and a node a query returns ends up printed in any failed\n * assertion about it: Node's inspector and Vitest's formatter both walk enumerable keys only.\n */\nfunction withHandle(node: Omit<FakeFabricNode, 'instanceHandle'>, instanceHandle: unknown) {\n Object.defineProperty(node, 'instanceHandle', {\n value: instanceHandle,\n enumerable: false,\n writable: true,\n });\n return node as FakeFabricNode;\n}\n\n/** The props JSI hands native: a key whose value is undefined is not there at all. */\nconst defined = (props: object): Record<string, unknown> =>\n Object.fromEntries(Object.entries(props).filter(([, value]) => value !== undefined));\n\nexport function createFakeFabric(): FakeFabric {\n let handle = 0;\n let committed: FakeFabricNode[] = [];\n let handler: ((h: unknown, t: string, e: unknown) => void) | null = null;\n const responderCalls: { viewName: string; isResponder: boolean; block: boolean }[] = [];\n const commands: { viewName: string; name: string; args: readonly unknown[] }[] = [];\n const frames = new Map<string, { x: number; y: number; width: number; height: number }>();\n const calls = {\n createNode: 0,\n cloneWithChildren: 0,\n cloneWithProps: 0,\n cloneWithChildrenAndProps: 0,\n completeRoot: 0,\n };\n\n /*\n * The parent each view has been appended to, by tag. Native keeps one parent per view for as\n * long as it lives - `ShadowNodeFamily::setParent` asserts it, in a debug build, and a release\n * build goes on with the old parent and measures and lays out against the wrong ancestry - so a\n * view that moves has to be created again, as React does. This fails the test that tries.\n */\n const parents = new Map<number, number>();\n const ROOT = -1;\n const adopt = (parent: number, child: FakeFabricNode): void => {\n const was = parents.get(child.reactTag);\n if (was !== undefined && was !== parent) {\n throw new Error(\n `${child.viewName} ${child.reactTag} was appended to ${parent} after ${was}: native ` +\n 'never re-parents a view, and asserts on it. Create it again instead.',\n );\n }\n parents.set(child.reactTag, parent);\n };\n\n /*\n * The views that have been unmounted, by tag. Fabric drops a view's event target once no\n * committed tree holds it (`EventEmitter::setEnabled`) and never makes a new one, so a view\n * committed again after leaving the tree dispatches every event with a null instance handle, as\n * `emit` does here. A view that comes back has to be created again, as React does.\n */\n const unmounted = new Set<number>();\n const unmount = (n: FakeFabricNode): void => {\n unmounted.add(n.reactTag);\n for (const child of n.children) unmount(child);\n };\n // Walks only what changed, as `updateMountedFlag` does: a subtree committed as it was is skipped.\n const diff = (before: FakeFabricNode[], after: FakeFabricNode[]): void => {\n if (before === after) return;\n const next = new Map(after.map((n) => [n.reactTag, n]));\n for (const was of before) {\n const now = next.get(was.reactTag);\n if (!now) unmount(was);\n else if (now !== was) diff(was.children, now.children);\n }\n };\n\n /*\n * iOS animates a presented modal out when it is committed with `visible: false`, then reports\n * `topDismiss` once, and React Native's Modal.js keeps it in the tree until then. The fake\n * reports it as soon as that commit is done, and lets one dismissal through per close, so a test\n * that also sends it by hand does not see `(dismiss)` twice.\n */\n const dismissing = new Set<number>();\n const dismissed = new Set<number>();\n const dismissAfterCommit = (): void => {\n if (dismissing.size === 0 || !handler) return;\n const tags = [...dismissing];\n dismissing.clear();\n queueMicrotask(() => {\n for (const tag of tags) {\n const host = findByTag(committed, tag);\n if (host && host.props['visible'] === false) fake.emit(host, 'topDismiss');\n }\n });\n };\n const findByTag = (nodes: FakeFabricNode[], tag: number): FakeFabricNode | undefined => {\n for (const n of nodes) {\n const hit = n.reactTag === tag ? n : findByTag(n.children, tag);\n if (hit) return hit;\n }\n return undefined;\n };\n /** A modal host committed as closing, or that left the tree closing. */\n const closing = (target: FakeFabricNode): boolean =>\n (findByTag(committed, target.reactTag) ?? target).props['visible'] === false;\n /** A modal host's props are changing: a close owes one dismissal, and a show resets it. */\n const noteModal = (source: FakeFabricNode, props: Record<string, unknown> | undefined): void => {\n if (source.viewName !== 'ModalHostView' || !props || !('visible' in props)) return;\n if (props['visible'] !== false) dismissed.delete(source.reactTag);\n else if (source.props['visible'] !== false) dismissing.add(source.reactTag);\n };\n\n const clone = (n: FabricNode, props?: object, keepChildren = false): FabricNode => {\n const source = n as unknown as FakeFabricNode;\n const given = props ? defined(props) : undefined;\n noteModal(source, given);\n return withHandle(\n {\n ...source,\n handle: ++handle,\n // Fabric merges raw props onto the source props rather than replacing them, and JSI\n // drops a key whose value is undefined on the way (JSIDynamic.cpp), so it changes nothing.\n props: given ? { ...source.props, ...given } : source.props,\n children: keepChildren ? source.children : [],\n },\n source.instanceHandle,\n ) as unknown as FabricNode;\n };\n\n /**\n * A clone with the children it was handed, as native takes them: in the call that makes it, as\n * an array (`shadowNodeListFromValue`). Anything else is a mistake here, such as props passed\n * where the children go, and native would read it as no list of views at all.\n */\n const holding = (parent: FabricNode, children: unknown): FabricNode => {\n if (!Array.isArray(children)) throw new TypeError('A clone takes its children as an array.');\n const into = parent as unknown as FakeFabricNode;\n for (const child of children as FakeFabricNode[]) {\n adopt(into.reactTag, child);\n into.children.push(child);\n }\n return parent;\n };\n\n const node = (\n reactTag: number,\n viewName: string,\n props: object,\n instanceHandle: unknown,\n ): FakeFabricNode =>\n withHandle(\n { handle: ++handle, reactTag, viewName, props: defined(props), children: [] },\n instanceHandle,\n );\n\n // Keys sorted at every depth, so a golden does not depend on the order props were set in. An\n // array of keys as the replacer would sort too, but it is an allow-list for nested objects as\n // well, and printed `accessibilityState: { disabled: true }` as `{}`.\n const sorted = (_key: string, value: unknown): unknown =>\n value && typeof value === 'object' && !Array.isArray(value)\n ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))\n : value;\n\n const walk = (nodes: FakeFabricNode[], depth: number, withProps: boolean): string[] =>\n nodes.flatMap((n) => {\n // The text is printed for itself, so the payload beside it is every other prop: a text\n // field's placeholder and keyboard, where a `RawText` has nothing more to say.\n const { text: said, ...rest } = n.props;\n const text = typeof said === 'string' ? ` \"${said}\"` : '';\n const shown = text ? rest : n.props;\n const props =\n withProps && Object.keys(shown).length ? ' ' + JSON.stringify(shown, sorted) : '';\n return [\n ' '.repeat(depth) + n.viewName + text + props,\n ...walk(n.children, depth + 1, withProps),\n ];\n });\n\n const fake: FakeFabric = {\n createNode(reactTag, viewName, _rootTag, props, instanceHandle) {\n calls.createNode++;\n return node(reactTag, viewName, props, instanceHandle) as unknown as FabricNode;\n },\n cloneNodeWithNewChildren(n, ...children: unknown[]) {\n calls.cloneWithChildren++;\n // As `UIManagerBinding.cpp` reads it: no second argument is no children.\n return holding(clone(n), children.length ? children[0] : []);\n },\n cloneNodeWithNewProps(n, newProps) {\n calls.cloneWithProps++;\n return clone(n, newProps, true);\n },\n cloneNodeWithNewChildrenAndProps(n, ...rest: unknown[]) {\n calls.cloneWithChildrenAndProps++;\n // As the binding reads it: with three arguments the children are the second, and with any\n // other count there are none and the props are the second.\n const [children, newProps] = rest.length === 2 ? rest : [[], rest[0]];\n return holding(clone(n, newProps as object), children);\n },\n appendChild(parent, child) {\n const into = parent as unknown as FakeFabricNode;\n adopt(into.reactTag, child as unknown as FakeFabricNode);\n into.children.push(child as unknown as FakeFabricNode);\n return parent;\n },\n createChildSet() {\n return [] as FabricNodeSet;\n },\n appendChildToSet(set, child) {\n adopt(ROOT, child as unknown as FakeFabricNode);\n (set as FakeFabricNode[]).push(child as unknown as FakeFabricNode);\n },\n completeRoot(_rootTag, set) {\n calls.completeRoot++;\n diff(committed, set as FakeFabricNode[]);\n committed = set as FakeFabricNode[];\n dismissAfterCommit();\n },\n registerEventHandler(fn) {\n handler = fn;\n },\n setIsJSResponder(node, isResponder, block) {\n responderCalls.push({\n viewName: (node as unknown as FakeFabricNode).viewName,\n isResponder,\n block,\n });\n },\n measureInWindow(node, onSuccess) {\n // By `nativeID` first, so a test that cares which of two views was measured can say so; by\n // view name otherwise, which is enough when there is only one candidate on screen.\n const fake = node as unknown as FakeFabricNode;\n const frame = frames.get(String(fake.props['nativeID'])) ?? frames.get(fake.viewName);\n if (frame) onSuccess(frame.x, frame.y, frame.width, frame.height);\n },\n dispatchCommand(node, name, args) {\n const command = { viewName: (node as unknown as FakeFabricNode).viewName, name, args };\n // Which node, for a test that has several of one view; off the enumerable keys, so a\n // `deepEqual` against `{ viewName, name, args }` still holds.\n Object.defineProperty(command, 'node', { value: node, enumerable: false });\n commands.push(command as typeof command & { node: FakeFabricNode });\n },\n responderCalls,\n commands,\n frames,\n get committed() {\n return committed;\n },\n calls,\n render(options) {\n return walk(committed, 0, options?.props === true).join('\\n');\n },\n find(viewName) {\n const search = (nodes: FakeFabricNode[]): FakeFabricNode | undefined => {\n for (const n of nodes) {\n if (n.viewName === viewName) return n;\n const hit = search(n.children);\n if (hit) return hit;\n }\n return undefined;\n };\n return search(committed);\n },\n emit(target, type, nativeEvent = {}) {\n if (!handler) throw new Error('no Fabric event handler registered');\n if (type === 'topDismiss' && target.viewName === 'ModalHostView') {\n if (dismissed.has(target.reactTag)) return;\n if (closing(target)) dismissed.add(target.reactTag);\n }\n handler(unmounted.has(target.reactTag) ? null : target.instanceHandle, type, nativeEvent);\n },\n reset() {\n calls.createNode = 0;\n calls.cloneWithChildren = 0;\n calls.cloneWithProps = 0;\n calls.cloneWithChildrenAndProps = 0;\n calls.completeRoot = 0;\n },\n };\n\n return fake;\n}\n"]}
@@ -1,17 +1,6 @@
1
- /**
2
- * `waitFor`, as Testing Library means it: run the callback until it stops throwing, or give up and
3
- * throw what it last threw.
4
- *
5
- * Polled on real timers. Each wait is a macrotask, which is also what lets zoneless change
6
- * detection run and commit between attempts, so a retry sees the next frame rather than the same
7
- * one again.
8
- *
9
- * ponytail: real timers only. Under a fake clock the interval never elapses; advance the clock
10
- * yourself, or use `settle()`, until someone needs the Jest/Vitest timer integration.
11
- */
12
1
  /** How long `waitFor`, and every `findBy` query, keeps trying. */
13
2
  export interface WaitForOptions {
14
- /** How long to keep trying, in milliseconds. Default 1000. */
3
+ /** How long to keep trying, in milliseconds. Default 1000, or what `configure` set. */
15
4
  timeout?: number;
16
5
  /** How long between attempts, in milliseconds. Default 50. */
17
6
  interval?: number;
package/dist/wait-for.js CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * `waitFor`, as Testing Library means it: run the callback until it stops throwing, or give up and
3
+ * throw what it last threw.
4
+ *
5
+ * Polled on real timers. Each wait is a macrotask, which is also what lets zoneless change
6
+ * detection run and commit between attempts, so a retry sees the next frame rather than the same
7
+ * one again.
8
+ *
9
+ * ponytail: real timers only. Under a fake clock the interval never elapses; advance the clock
10
+ * yourself, or use `settle()`, until someone needs the Jest/Vitest timer integration.
11
+ */
12
+ import { config } from "./configure.js";
1
13
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2
14
  /**
3
15
  * Retry `callback` until it returns without throwing, and resolve to what it returned.
@@ -5,7 +17,7 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5
17
  * Rejects with the callback's last error once `timeout` has passed, so a failing `waitFor` reads
6
18
  * like the assertion inside it rather than like a timeout.
7
19
  */
8
- export async function waitFor(callback, { timeout = 1000, interval = 50 } = {}) {
20
+ export async function waitFor(callback, { timeout = config().asyncUtilTimeout, interval = 50 } = {}) {
9
21
  const deadline = Date.now() + timeout;
10
22
  for (;;) {
11
23
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"wait-for.js","sourceRoot":"","sources":["../src/wait-for.ts"],"names":[],"mappings":"AAmBA,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAE/F;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,QAA8B,EAC9B,EAAE,OAAO,GAAG,IAAI,EAAE,QAAQ,GAAG,EAAE,KAAqB,EAAE;IAEtD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;gBAAE,MAAM,KAAK,CAAC;QAC1C,CAAC;QACD,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;AACH,CAAC","sourcesContent":["/**\n * `waitFor`, as Testing Library means it: run the callback until it stops throwing, or give up and\n * throw what it last threw.\n *\n * Polled on real timers. Each wait is a macrotask, which is also what lets zoneless change\n * detection run and commit between attempts, so a retry sees the next frame rather than the same\n * one again.\n *\n * ponytail: real timers only. Under a fake clock the interval never elapses; advance the clock\n * yourself, or use `settle()`, until someone needs the Jest/Vitest timer integration.\n */\n/** How long `waitFor`, and every `findBy` query, keeps trying. */\nexport interface WaitForOptions {\n /** How long to keep trying, in milliseconds. Default 1000. */\n timeout?: number;\n /** How long between attempts, in milliseconds. Default 50. */\n interval?: number;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Retry `callback` until it returns without throwing, and resolve to what it returned.\n *\n * Rejects with the callback's last error once `timeout` has passed, so a failing `waitFor` reads\n * like the assertion inside it rather than like a timeout.\n */\nexport async function waitFor<T>(\n callback: () => T | Promise<T>,\n { timeout = 1000, interval = 50 }: WaitForOptions = {},\n): Promise<T> {\n const deadline = Date.now() + timeout;\n for (;;) {\n try {\n return await callback();\n } catch (error) {\n if (Date.now() >= deadline) throw error;\n }\n await sleep(interval);\n }\n}\n"]}
1
+ {"version":3,"file":"wait-for.js","sourceRoot":"","sources":["../src/wait-for.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAUxC,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAAC,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAE/F;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,QAA8B,EAC9B,EAAE,OAAO,GAAG,MAAM,EAAE,CAAC,gBAAgB,EAAE,QAAQ,GAAG,EAAE,KAAqB,EAAE;IAE3E,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;IACtC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,MAAM,QAAQ,EAAE,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,QAAQ;gBAAE,MAAM,KAAK,CAAC;QAC1C,CAAC;QACD,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC;AACH,CAAC","sourcesContent":["/**\n * `waitFor`, as Testing Library means it: run the callback until it stops throwing, or give up and\n * throw what it last threw.\n *\n * Polled on real timers. Each wait is a macrotask, which is also what lets zoneless change\n * detection run and commit between attempts, so a retry sees the next frame rather than the same\n * one again.\n *\n * ponytail: real timers only. Under a fake clock the interval never elapses; advance the clock\n * yourself, or use `settle()`, until someone needs the Jest/Vitest timer integration.\n */\nimport { config } from './configure.ts';\n\n/** How long `waitFor`, and every `findBy` query, keeps trying. */\nexport interface WaitForOptions {\n /** How long to keep trying, in milliseconds. Default 1000, or what `configure` set. */\n timeout?: number;\n /** How long between attempts, in milliseconds. Default 50. */\n interval?: number;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Retry `callback` until it returns without throwing, and resolve to what it returned.\n *\n * Rejects with the callback's last error once `timeout` has passed, so a failing `waitFor` reads\n * like the assertion inside it rather than like a timeout.\n */\nexport async function waitFor<T>(\n callback: () => T | Promise<T>,\n { timeout = config().asyncUtilTimeout, interval = 50 }: WaitForOptions = {},\n): Promise<T> {\n const deadline = Date.now() + timeout;\n for (;;) {\n try {\n return await callback();\n } catch (error) {\n if (Date.now() >= deadline) throw error;\n }\n await sleep(interval);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-native/testing",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Test Angular Native components in Node with no simulator: a fake Fabric, Testing Library queries and events, and Vitest and node:test integration.",
5
5
  "keywords": [
6
6
  "angular",
@@ -34,16 +34,16 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@ng-native/fabric": "0.16.0",
38
- "@ng-native/metro": "0.16.0",
39
- "@ng-native/platform": "0.16.0"
37
+ "@ng-native/fabric": "0.17.0",
38
+ "@ng-native/metro": "0.17.0",
39
+ "@ng-native/platform": "0.17.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@angular/compiler": "^22.0.0",
43
43
  "@angular/core": "^22.0.0",
44
44
  "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
45
45
  "vitest": "^4.0.8 || ^5.0.0",
46
- "@ng-native/components": "0.16.0"
46
+ "@ng-native/components": "0.17.0"
47
47
  },
48
48
  "peerDependenciesMeta": {
49
49
  "@angular/compiler": {
@@ -62,8 +62,8 @@
62
62
  "@angular/core": "22.2.0",
63
63
  "@angular/forms": "22.2.0",
64
64
  "@angular/router": "22.2.0",
65
- "@ng-native/components": "0.16.0",
66
- "@ng-native/router": "0.16.0",
65
+ "@ng-native/components": "0.17.0",
66
+ "@ng-native/router": "0.17.0",
67
67
  "vite": "^8.3.1",
68
68
  "vitest": "^5.0.2"
69
69
  },
package/runner/vitest.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `ngNative()`: the Vite plugin that lets Vitest run Angular Native code in Node, with no DOM.
3
3
  *
4
- * Seven things, each of which Vitest would otherwise get wrong on its own:
4
+ * Eight things, each of which Vitest would otherwise get wrong on its own:
5
5
  *
6
6
  * - It compiles. Every decorated `.ts` goes through `@ng-native/metro`'s AOT transform and every
7
7
  * partial-compiled package through the linker (see `compile.mjs`), before Vite strips types.
@@ -20,11 +20,14 @@
20
20
  * - It resolves as Metro does. A workspace library's copy of a package at the app's version
21
21
  * resolves to the app's copy (`appCopy`), so a test has one `@ng-native/components`, as the
22
22
  * bundle does.
23
+ * - It cleans up. Vitest leaves a copy of every module it ran in the system temp directory; the
24
+ * plugin removes it as Vitest closes (`removeModuleCopies`).
23
25
  *
24
26
  * The environment is Vitest's default, `node`. There is no DOM to emulate: the renderer talks to
25
27
  * a fake Fabric, and jsdom would only give Angular a `document` to misread.
26
28
  */
27
- import { existsSync, readFileSync } from 'node:fs';
29
+ import { existsSync, readFileSync, rmSync } from 'node:fs';
30
+ import { tmpdir } from 'node:os';
28
31
  import path from 'node:path';
29
32
  import { fileURLToPath } from 'node:url';
30
33
  import { defaultClientConditions, defaultServerConditions } from 'vite';
@@ -245,6 +248,25 @@ function compiled(context, source, file, options) {
245
248
  return { code: hideRequire(result.code, file), map: result.map ?? null };
246
249
  }
247
250
 
251
+ /**
252
+ * Removes, as Vitest closes, the directory in the system temp directory it copies each transformed
253
+ * module into for the forks pool, which it leaves behind when the config has no `projects`: a
254
+ * project removes the directory it made itself, but the modules go to the one Vitest made, and
255
+ * nothing removes that. One is left by every run, a hundred megabytes for an app's suite.
256
+ *
257
+ * ponytail: `_tmpDir` is Vitest 5's own, unexported; on a Vitest without it this does nothing.
258
+ *
259
+ * @param {{ _tmpDir?: unknown, onClose(fn: () => void): void }} vitest
260
+ */
261
+ function removeModuleCopies(vitest) {
262
+ const dir = vitest._tmpDir;
263
+ if (typeof dir !== 'string' || path.dirname(dir) !== path.resolve(tmpdir())) return;
264
+ vitest.onClose(() => rmSync(dir, { recursive: true, force: true }));
265
+ }
266
+
267
+ /** @type {WeakSet<object>} */
268
+ const cleaned = new WeakSet();
269
+
248
270
  /**
249
271
  * @param {{ inline?: (string | RegExp)[], libraryStyles?: string[] | false }} [options]
250
272
  * @returns {import('vitest/config').Plugin}
@@ -272,6 +294,12 @@ export function ngNative(options = {}) {
272
294
  configResolved(config) {
273
295
  root = config.root;
274
296
  },
297
+ // Once for each project; the directory is the Vitest instance's, so it is removed once.
298
+ configureVitest({ vitest }) {
299
+ if (cleaned.has(vitest)) return;
300
+ cleaned.add(vitest);
301
+ removeModuleCopies(vitest);
302
+ },
275
303
  resolveId(source, importer, resolveOptions) {
276
304
  if (Object.hasOwn(STAND_INS, source)) return STAND_INS[source];
277
305
  return importer ? appCopy(this, source, importer, resolveOptions, root) : null;