@ng-native/testing 0.16.0 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/configure.d.ts +20 -0
- package/dist/configure.js +22 -0
- package/dist/configure.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/queries.d.ts +10 -1
- package/dist/queries.js +40 -2
- package/dist/queries.js.map +1 -1
- package/dist/test-utils.js +23 -4
- package/dist/test-utils.js.map +1 -1
- package/dist/wait-for.d.ts +1 -12
- package/dist/wait-for.js +13 -1
- package/dist/wait-for.js.map +1 -1
- package/package.json +7 -7
- package/runner/vitest.mjs +30 -2
|
@@ -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"]}
|
package/dist/queries.d.ts
CHANGED
|
@@ -24,10 +24,19 @@ export interface TextMatchOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
includeHiddenElements?: boolean;
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* `ByRole`'s options: a role alone matches every node with it, `name` narrows it to one, and a
|
|
29
|
+
* state narrows it to the nodes in that state, as React Native Testing Library's do.
|
|
30
|
+
*/
|
|
28
31
|
export interface ByRoleOptions extends TextMatchOptions {
|
|
29
32
|
/** The accessible name: `accessibilityLabel`, or failing that the text inside the node. */
|
|
30
33
|
name?: Matcher;
|
|
34
|
+
/** Disabled, from `accessibilityState` or `aria-disabled`; a field that is not editable is. */
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
selected?: boolean;
|
|
37
|
+
checked?: boolean | 'mixed';
|
|
38
|
+
busy?: boolean;
|
|
39
|
+
expanded?: boolean;
|
|
31
40
|
}
|
|
32
41
|
interface QueryArgs {
|
|
33
42
|
Role: [role: Matcher, options?: ByRoleOptions];
|
package/dist/queries.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
import { waitFor } from "./wait-for.js";
|
|
2
|
+
/** The states `ByRole` filters on. */
|
|
3
|
+
const STATES = ['disabled', 'selected', 'checked', 'busy', 'expanded'];
|
|
4
|
+
/** The roles React Native Testing Library reads `checked` for; any other has none. */
|
|
5
|
+
const CHECKABLE = new Set(['checkbox', 'radio', 'switch']);
|
|
6
|
+
/** What a state is when nothing set it: off, or for `checked` and `expanded`, not had at all. */
|
|
7
|
+
const UNSET = {
|
|
8
|
+
disabled: false,
|
|
9
|
+
selected: false,
|
|
10
|
+
checked: undefined,
|
|
11
|
+
busy: false,
|
|
12
|
+
expanded: undefined,
|
|
13
|
+
};
|
|
14
|
+
/** Whether a node can have the state at all: `checked` only on a checkbox, radio or switch. */
|
|
15
|
+
const has = (node, state) => state !== 'checked' || CHECKABLE.has(node.props['accessibilityRole'] ?? node.props['role']);
|
|
16
|
+
/**
|
|
17
|
+
* A state as a screen reader has it: what the committed `accessibilityState` says, which the
|
|
18
|
+
* components and the `aria-*` attributes write. Unset is false for the states that are off until
|
|
19
|
+
* something says otherwise, and stays unset for `checked` and `expanded`, which a node only has
|
|
20
|
+
* when it can be checked or expanded; `checked` only counts on a checkbox, radio or switch. React
|
|
21
|
+
* Native Testing Library's rules.
|
|
22
|
+
*/
|
|
23
|
+
function stateOf(node, state) {
|
|
24
|
+
if (state === 'disabled' && isTextInput(node) && node.props['editable'] === false)
|
|
25
|
+
return true;
|
|
26
|
+
if (!has(node, state))
|
|
27
|
+
return undefined;
|
|
28
|
+
const value = node.props['accessibilityState']?.[state];
|
|
29
|
+
return value === true || value === false || value === 'mixed' ? value : UNSET[state];
|
|
30
|
+
}
|
|
31
|
+
/** Whether a node is in every state the options ask for. */
|
|
32
|
+
const inStates = (node, options) => STATES.every((state) => options[state] === undefined || stateOf(node, state) === options[state]);
|
|
33
|
+
/** The states the options ask for, for an error message. */
|
|
34
|
+
const describeStates = (options) => STATES.filter((state) => options[state] !== undefined)
|
|
35
|
+
.map((state) => ` and ${state} ${String(options[state])}`)
|
|
36
|
+
.join('');
|
|
2
37
|
const normalize = (text) => text.replace(/\s+/g, ' ').trim();
|
|
3
38
|
function matches(value, matcher, exact = true) {
|
|
4
39
|
if (typeof value !== 'string')
|
|
@@ -59,9 +94,12 @@ const nameOf = (node) => typeof node.props['accessibilityLabel'] === 'string'
|
|
|
59
94
|
: textContent(node);
|
|
60
95
|
const QUERIES = {
|
|
61
96
|
Role: (role, options = {}) => ({
|
|
62
|
-
describe: `role ${show(role)}` +
|
|
97
|
+
describe: `role ${show(role)}` +
|
|
98
|
+
(options.name === undefined ? '' : ` and name ${show(options.name)}`) +
|
|
99
|
+
describeStates(options),
|
|
63
100
|
select: (node) => (matches(node.props['accessibilityRole'], role) || matches(node.props['role'], role)) &&
|
|
64
|
-
(options.name === undefined || matches(nameOf(node), options.name, options.exact))
|
|
101
|
+
(options.name === undefined || matches(nameOf(node), options.name, options.exact)) &&
|
|
102
|
+
inStates(node, options),
|
|
65
103
|
}),
|
|
66
104
|
// `Paragraph` alone: a nested `<text>` commits as a `Text` span inside one, so the paragraph is
|
|
67
105
|
// the whole run of text and the span is only a part of it.
|
package/dist/queries.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Testing Library's queries, over the tree the renderer committed to the fake Fabric.\n *\n * There is no DOM here, so `@testing-library/dom` has nothing to query: a query reads the props a\n * native view would actually receive. That makes the names mean what React Native Testing Library\n * means by them rather than what the web does - a role is `accessibilityRole`, a label is\n * `accessibilityLabel`, text is what a `Paragraph` holds.\n *\n * Every query re-reads the most recent commit. The fake builds a fresh node object per commit, so\n * a node held across an interaction is a snapshot of the frame it was found in; query again to see\n * what changed.\n */\nimport type { FakeFabricNode } from './test-utils.ts';\nimport { waitFor, type WaitForOptions } from './wait-for.ts';\n\n/** What a query matches against: a whole string (normalised), or a RegExp tested against it. */\nexport type Matcher = string | RegExp;\n\n/** How a string `Matcher` compares. */\nexport interface TextMatchOptions {\n /** `false` matches a case-insensitive substring instead of the whole, trimmed string. */\n exact?: boolean;\n /**\n * Also find what nobody could see or reach: views under `display: none`, and those hidden from\n * accessibility. React Native Testing Library's option, and its default of `false`.\n */\n includeHiddenElements?: boolean;\n}\n\n/** `ByRole`'s options: a role alone matches every node with it, `name` narrows it to one. */\nexport interface ByRoleOptions extends TextMatchOptions {\n /** The accessible name: `accessibilityLabel`, or failing that the text inside the node. */\n name?: Matcher;\n}\n\ninterface QueryArgs {\n Role: [role: Matcher, options?: ByRoleOptions];\n Text: [text: Matcher, options?: TextMatchOptions];\n TestId: [testId: Matcher, options?: TextMatchOptions];\n LabelText: [label: Matcher, options?: TextMatchOptions];\n PlaceholderText: [placeholder: Matcher, options?: TextMatchOptions];\n DisplayValue: [value: Matcher, options?: TextMatchOptions];\n}\n\ntype Name = keyof QueryArgs;\ntype FindArgs<K extends Name> = [...QueryArgs[K], waitForOptions?: WaitForOptions];\n\n/**\n * The query matrix: `getBy`, `getAllBy`, `queryBy`, `queryAllBy`, `findBy` and `findAllBy`, for\n * each of `Role`, `Text`, `TestId`, `LabelText`, `PlaceholderText` and `DisplayValue`.\n */\nexport type BoundQueries = {\n [K in Name as `getBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode;\n} & {\n [K in Name as `getAllBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode[];\n} & {\n [K in Name as `queryBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode | null;\n} & {\n [K in Name as `queryAllBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode[];\n} & {\n [K in Name as `findBy${K}`]: (...args: FindArgs<K>) => Promise<FakeFabricNode>;\n} & {\n [K in Name as `findAllBy${K}`]: (...args: FindArgs<K>) => Promise<FakeFabricNode[]>;\n};\n\nconst normalize = (text: string): string => text.replace(/\\s+/g, ' ').trim();\n\nfunction matches(value: unknown, matcher: Matcher, exact = true): boolean {\n if (typeof value !== 'string') return false;\n const text = normalize(value);\n if (matcher instanceof RegExp) {\n matcher.lastIndex = 0;\n return matcher.test(text);\n }\n const wanted = normalize(matcher);\n return exact ? text === wanted : text.toLowerCase().includes(wanted.toLowerCase());\n}\n\nconst show = (matcher: Matcher): string =>\n matcher instanceof RegExp ? String(matcher) : JSON.stringify(matcher);\n\nexport const flatten = (nodes: readonly FakeFabricNode[]): FakeFabricNode[] =>\n nodes.flatMap((node) => [node, ...flatten(node.children)]);\n\n/**\n * `nodes` with each node's `parent` set through the tree beneath, so a test can go up from what a\n * query found: the row a text is in. Not enumerable, so a node compares and prints as it did.\n */\nexport function withParents(\n nodes: readonly FakeFabricNode[],\n parent?: FakeFabricNode,\n): readonly FakeFabricNode[] {\n for (const node of nodes) {\n // The nodes a walk starts from keep the parent they have: `within(row)` starts from the row.\n if (parent || !('parent' in node)) {\n const value = parent ?? null;\n Object.defineProperty(node, 'parent', { value, configurable: true, writable: true });\n }\n withParents(node.children, node);\n }\n return nodes;\n}\n\n/**\n * A view that takes itself and everything under it out of sight or out of the accessibility tree,\n * as React Native Testing Library's `isHiddenFromAccessibility` decides it. And one with no\n * opacity that takes no touch, which nobody can see or reach: a row a list keeps to use again.\n * An element that is `display: none` has no view to ask about.\n */\nfunction hides(node: FakeFabricNode): boolean {\n const props = node.props;\n return (\n (props['opacity'] === 0 && props['pointerEvents'] === 'none') ||\n props['accessibilityElementsHidden'] === true ||\n props['aria-hidden'] === true ||\n props['importantForAccessibility'] === 'no-hide-descendants'\n );\n}\n\n/** Every node someone using the app could see or reach. */\nconst reachable = (nodes: readonly FakeFabricNode[]): FakeFabricNode[] =>\n nodes.flatMap((node) => (hides(node) ? [] : [node, ...reachable(node.children)]));\n\n/** What a screen reader would read out: every `RawText` under the node, in order. */\nexport function textContent(node: FakeFabricNode): string {\n if (node.viewName === 'RawText') return String(node.props['text'] ?? '');\n return node.children.map(textContent).join('');\n}\n\n/** A text field under any of the names the platforms register it by. */\nexport const isTextInput = (node: FakeFabricNode): boolean => /TextInput/.test(node.viewName);\n\n/** The navigation bar's search field, which reports text as `topChangeText` with no count. */\nexport const isSearchBar = (node: FakeFabricNode): boolean => node.viewName === 'RNSSearchBar';\n\ntype Query = { describe: string; select: (node: FakeFabricNode) => boolean };\n\n/** What a screen reader reads out: the label, or failing that the text inside. */\nconst nameOf = (node: FakeFabricNode): string =>\n typeof node.props['accessibilityLabel'] === 'string'\n ? node.props['accessibilityLabel']\n : textContent(node);\n\nconst QUERIES: { [K in Name]: (...args: QueryArgs[K]) => Query } = {\n Role: (role, options = {}) => ({\n describe:\n `role ${show(role)}` + (options.name === undefined ? '' : ` and name ${show(options.name)}`),\n select: (node) =>\n (matches(node.props['accessibilityRole'], role) || matches(node.props['role'], role)) &&\n (options.name === undefined || matches(nameOf(node), options.name, options.exact)),\n }),\n // `Paragraph` alone: a nested `<text>` commits as a `Text` span inside one, so the paragraph is\n // the whole run of text and the span is only a part of it.\n Text: (text, options = {}) => ({\n describe: `text ${show(text)}`,\n select: (node) =>\n node.viewName === 'Paragraph' && matches(textContent(node), text, options.exact),\n }),\n // Both, because the components commit both: `testID` from `testID`, `nativeID` from `nativeID`\n // or its web spelling `id`, which is what most templates in this project reach for.\n TestId: (testId, options = {}) => ({\n describe: `testID ${show(testId)}`,\n select: (node) =>\n matches(node.props['testID'], testId, options.exact) ||\n matches(node.props['nativeID'], testId, options.exact),\n }),\n LabelText: (label, options = {}) => ({\n describe: `accessibilityLabel ${show(label)}`,\n select: (node) => matches(node.props['accessibilityLabel'], label, options.exact),\n }),\n PlaceholderText: (placeholder, options = {}) => ({\n describe: `placeholder ${show(placeholder)}`,\n // A text field and the navigation bar's search field draw a placeholder. A wrapper with a\n // `placeholder` input of its own has the attribute on its host view too, which nobody sees.\n select: (node) =>\n (isTextInput(node) || isSearchBar(node)) &&\n matches(node.props['placeholder'], placeholder, options.exact),\n }),\n // A text field's value is its `text` prop, which is what `<text-input>` binds its model to.\n DisplayValue: (value, options = {}) => ({\n describe: `display value ${show(value)}`,\n select: (node) => isTextInput(node) && matches(node.props['text'], value, options.exact),\n }),\n};\n\n/**\n * Every query, bound to a root that is looked up again on each call.\n *\n * `found` sees every node a query hands back, so an event fired on one later knows which render\n * it came from.\n */\nexport function bindQueries(\n roots: () => readonly FakeFabricNode[],\n tree: () => string,\n found: (node: FakeFabricNode) => void,\n): BoundQueries {\n const bound: Record<string, unknown> = {};\n\n for (const name of Object.keys(QUERIES) as Name[]) {\n const all = (...args: unknown[]): FakeFabricNode[] => {\n const query = (QUERIES[name] as (...a: unknown[]) => Query)(...args);\n const options = args[1] as TextMatchOptions | undefined;\n const walk = options?.includeHiddenElements ? flatten : reachable;\n const hits = walk(withParents(roots())).filter(query.select);\n hits.forEach(found);\n return hits;\n };\n const describe = (args: unknown[]): string =>\n (QUERIES[name] as (...a: unknown[]) => Query)(...args).describe;\n const one = (...args: unknown[]): FakeFabricNode => {\n const hits = all(...args);\n if (hits.length === 1) return hits[0]!;\n const problem = hits.length\n ? `Found ${hits.length} nodes with ${describe(args)}, and expected one. Use getAllBy${name} if more than one is expected.`\n : `Unable to find a node with ${describe(args)}.`;\n throw new Error(`${problem}\\n\\n${tree()}`);\n };\n // The last argument of a `findBy` is the `waitFor` options, and it is only ever that when the\n // query itself was given its options too.\n const split = (args: unknown[]): [unknown[], WaitForOptions | undefined] =>\n args.length > 2 ? [args.slice(0, 2), args[2] as WaitForOptions] : [args, undefined];\n\n bound[`getAllBy${name}`] = (...args: unknown[]) => {\n const hits = all(...args);\n if (!hits.length)\n throw new Error(`Unable to find a node with ${describe(args)}.\\n\\n${tree()}`);\n return hits;\n };\n bound[`queryAllBy${name}`] = all;\n bound[`getBy${name}`] = one;\n bound[`queryBy${name}`] = (...args: unknown[]) => {\n const hits = all(...args);\n if (hits.length > 1) return one(...args);\n return hits[0] ?? null;\n };\n bound[`findBy${name}`] = (...args: unknown[]) => {\n const [query, options] = split(args);\n return waitFor(() => one(...query), options);\n };\n bound[`findAllBy${name}`] = (...args: unknown[]) => {\n const [query, options] = split(args);\n return waitFor(() => (bound[`getAllBy${name}`] as typeof all)(...query), options);\n };\n }\n\n return bound as BoundQueries;\n}\n\n/** The debug tree: view names, text, and the props a query can find a node by. */\nexport function describeTree(nodes: readonly FakeFabricNode[], depth = 0): string {\n return nodes\n .map((node) => {\n const text = node.viewName === 'RawText' ? ` ${JSON.stringify(node.props['text'])}` : '';\n const props = IDENTIFYING.filter((key) => node.props[key] != null && node.props[key] !== '')\n .map((key) => ` ${key === 'text' ? 'value' : key}=${JSON.stringify(node.props[key])}`)\n .join('');\n const line = ' '.repeat(depth) + node.viewName + text + (text ? '' : props);\n return node.children.length ? `${line}\\n${describeTree(node.children, depth + 1)}` : line;\n })\n .join('\\n');\n}\n\nconst IDENTIFYING = [\n 'testID',\n 'nativeID',\n 'accessibilityRole',\n 'role',\n 'accessibilityLabel',\n 'placeholder',\n 'text',\n];\n"]}
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../src/queries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Testing Library's queries, over the tree the renderer committed to the fake Fabric.\n *\n * There is no DOM here, so `@testing-library/dom` has nothing to query: a query reads the props a\n * native view would actually receive. That makes the names mean what React Native Testing Library\n * means by them rather than what the web does - a role is `accessibilityRole`, a label is\n * `accessibilityLabel`, text is what a `Paragraph` holds.\n *\n * Every query re-reads the most recent commit. The fake builds a fresh node object per commit, so\n * a node held across an interaction is a snapshot of the frame it was found in; query again to see\n * what changed.\n */\nimport type { FakeFabricNode } from './test-utils.ts';\nimport { waitFor, type WaitForOptions } from './wait-for.ts';\n\n/** What a query matches against: a whole string (normalised), or a RegExp tested against it. */\nexport type Matcher = string | RegExp;\n\n/** How a string `Matcher` compares. */\nexport interface TextMatchOptions {\n /** `false` matches a case-insensitive substring instead of the whole, trimmed string. */\n exact?: boolean;\n /**\n * Also find what nobody could see or reach: views under `display: none`, and those hidden from\n * accessibility. React Native Testing Library's option, and its default of `false`.\n */\n includeHiddenElements?: boolean;\n}\n\n/**\n * `ByRole`'s options: a role alone matches every node with it, `name` narrows it to one, and a\n * state narrows it to the nodes in that state, as React Native Testing Library's do.\n */\nexport interface ByRoleOptions extends TextMatchOptions {\n /** The accessible name: `accessibilityLabel`, or failing that the text inside the node. */\n name?: Matcher;\n /** Disabled, from `accessibilityState` or `aria-disabled`; a field that is not editable is. */\n disabled?: boolean;\n selected?: boolean;\n checked?: boolean | 'mixed';\n busy?: boolean;\n expanded?: boolean;\n}\n\n/** The states `ByRole` filters on. */\nconst STATES = ['disabled', 'selected', 'checked', 'busy', 'expanded'] as const;\ntype State = (typeof STATES)[number];\n\n/** The roles React Native Testing Library reads `checked` for; any other has none. */\nconst CHECKABLE: ReadonlySet<unknown> = new Set(['checkbox', 'radio', 'switch']);\n\n/** What a state is when nothing set it: off, or for `checked` and `expanded`, not had at all. */\nconst UNSET: Readonly<Record<State, false | undefined>> = {\n disabled: false,\n selected: false,\n checked: undefined,\n busy: false,\n expanded: undefined,\n};\n\n/** Whether a node can have the state at all: `checked` only on a checkbox, radio or switch. */\nconst has = (node: FakeFabricNode, state: State): boolean =>\n state !== 'checked' || CHECKABLE.has(node.props['accessibilityRole'] ?? node.props['role']);\n\n/**\n * A state as a screen reader has it: what the committed `accessibilityState` says, which the\n * components and the `aria-*` attributes write. Unset is false for the states that are off until\n * something says otherwise, and stays unset for `checked` and `expanded`, which a node only has\n * when it can be checked or expanded; `checked` only counts on a checkbox, radio or switch. React\n * Native Testing Library's rules.\n */\nfunction stateOf(node: FakeFabricNode, state: State): boolean | 'mixed' | undefined {\n if (state === 'disabled' && isTextInput(node) && node.props['editable'] === false) return true;\n if (!has(node, state)) return undefined;\n const value = (node.props['accessibilityState'] as Record<string, unknown> | undefined)?.[state];\n return value === true || value === false || value === 'mixed' ? value : UNSET[state];\n}\n\n/** Whether a node is in every state the options ask for. */\nconst inStates = (node: FakeFabricNode, options: ByRoleOptions): boolean =>\n STATES.every((state) => options[state] === undefined || stateOf(node, state) === options[state]);\n\n/** The states the options ask for, for an error message. */\nconst describeStates = (options: ByRoleOptions): string =>\n STATES.filter((state) => options[state] !== undefined)\n .map((state) => ` and ${state} ${String(options[state])}`)\n .join('');\n\ninterface QueryArgs {\n Role: [role: Matcher, options?: ByRoleOptions];\n Text: [text: Matcher, options?: TextMatchOptions];\n TestId: [testId: Matcher, options?: TextMatchOptions];\n LabelText: [label: Matcher, options?: TextMatchOptions];\n PlaceholderText: [placeholder: Matcher, options?: TextMatchOptions];\n DisplayValue: [value: Matcher, options?: TextMatchOptions];\n}\n\ntype Name = keyof QueryArgs;\ntype FindArgs<K extends Name> = [...QueryArgs[K], waitForOptions?: WaitForOptions];\n\n/**\n * The query matrix: `getBy`, `getAllBy`, `queryBy`, `queryAllBy`, `findBy` and `findAllBy`, for\n * each of `Role`, `Text`, `TestId`, `LabelText`, `PlaceholderText` and `DisplayValue`.\n */\nexport type BoundQueries = {\n [K in Name as `getBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode;\n} & {\n [K in Name as `getAllBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode[];\n} & {\n [K in Name as `queryBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode | null;\n} & {\n [K in Name as `queryAllBy${K}`]: (...args: QueryArgs[K]) => FakeFabricNode[];\n} & {\n [K in Name as `findBy${K}`]: (...args: FindArgs<K>) => Promise<FakeFabricNode>;\n} & {\n [K in Name as `findAllBy${K}`]: (...args: FindArgs<K>) => Promise<FakeFabricNode[]>;\n};\n\nconst normalize = (text: string): string => text.replace(/\\s+/g, ' ').trim();\n\nfunction matches(value: unknown, matcher: Matcher, exact = true): boolean {\n if (typeof value !== 'string') return false;\n const text = normalize(value);\n if (matcher instanceof RegExp) {\n matcher.lastIndex = 0;\n return matcher.test(text);\n }\n const wanted = normalize(matcher);\n return exact ? text === wanted : text.toLowerCase().includes(wanted.toLowerCase());\n}\n\nconst show = (matcher: Matcher): string =>\n matcher instanceof RegExp ? String(matcher) : JSON.stringify(matcher);\n\nexport const flatten = (nodes: readonly FakeFabricNode[]): FakeFabricNode[] =>\n nodes.flatMap((node) => [node, ...flatten(node.children)]);\n\n/**\n * `nodes` with each node's `parent` set through the tree beneath, so a test can go up from what a\n * query found: the row a text is in. Not enumerable, so a node compares and prints as it did.\n */\nexport function withParents(\n nodes: readonly FakeFabricNode[],\n parent?: FakeFabricNode,\n): readonly FakeFabricNode[] {\n for (const node of nodes) {\n // The nodes a walk starts from keep the parent they have: `within(row)` starts from the row.\n if (parent || !('parent' in node)) {\n const value = parent ?? null;\n Object.defineProperty(node, 'parent', { value, configurable: true, writable: true });\n }\n withParents(node.children, node);\n }\n return nodes;\n}\n\n/**\n * A view that takes itself and everything under it out of sight or out of the accessibility tree,\n * as React Native Testing Library's `isHiddenFromAccessibility` decides it. And one with no\n * opacity that takes no touch, which nobody can see or reach: a row a list keeps to use again.\n * An element that is `display: none` has no view to ask about.\n */\nfunction hides(node: FakeFabricNode): boolean {\n const props = node.props;\n return (\n (props['opacity'] === 0 && props['pointerEvents'] === 'none') ||\n props['accessibilityElementsHidden'] === true ||\n props['aria-hidden'] === true ||\n props['importantForAccessibility'] === 'no-hide-descendants'\n );\n}\n\n/** Every node someone using the app could see or reach. */\nconst reachable = (nodes: readonly FakeFabricNode[]): FakeFabricNode[] =>\n nodes.flatMap((node) => (hides(node) ? [] : [node, ...reachable(node.children)]));\n\n/** What a screen reader would read out: every `RawText` under the node, in order. */\nexport function textContent(node: FakeFabricNode): string {\n if (node.viewName === 'RawText') return String(node.props['text'] ?? '');\n return node.children.map(textContent).join('');\n}\n\n/** A text field under any of the names the platforms register it by. */\nexport const isTextInput = (node: FakeFabricNode): boolean => /TextInput/.test(node.viewName);\n\n/** The navigation bar's search field, which reports text as `topChangeText` with no count. */\nexport const isSearchBar = (node: FakeFabricNode): boolean => node.viewName === 'RNSSearchBar';\n\ntype Query = { describe: string; select: (node: FakeFabricNode) => boolean };\n\n/** What a screen reader reads out: the label, or failing that the text inside. */\nconst nameOf = (node: FakeFabricNode): string =>\n typeof node.props['accessibilityLabel'] === 'string'\n ? node.props['accessibilityLabel']\n : textContent(node);\n\nconst QUERIES: { [K in Name]: (...args: QueryArgs[K]) => Query } = {\n Role: (role, options = {}) => ({\n describe:\n `role ${show(role)}` +\n (options.name === undefined ? '' : ` and name ${show(options.name)}`) +\n describeStates(options),\n select: (node) =>\n (matches(node.props['accessibilityRole'], role) || matches(node.props['role'], role)) &&\n (options.name === undefined || matches(nameOf(node), options.name, options.exact)) &&\n inStates(node, options),\n }),\n // `Paragraph` alone: a nested `<text>` commits as a `Text` span inside one, so the paragraph is\n // the whole run of text and the span is only a part of it.\n Text: (text, options = {}) => ({\n describe: `text ${show(text)}`,\n select: (node) =>\n node.viewName === 'Paragraph' && matches(textContent(node), text, options.exact),\n }),\n // Both, because the components commit both: `testID` from `testID`, `nativeID` from `nativeID`\n // or its web spelling `id`, which is what most templates in this project reach for.\n TestId: (testId, options = {}) => ({\n describe: `testID ${show(testId)}`,\n select: (node) =>\n matches(node.props['testID'], testId, options.exact) ||\n matches(node.props['nativeID'], testId, options.exact),\n }),\n LabelText: (label, options = {}) => ({\n describe: `accessibilityLabel ${show(label)}`,\n select: (node) => matches(node.props['accessibilityLabel'], label, options.exact),\n }),\n PlaceholderText: (placeholder, options = {}) => ({\n describe: `placeholder ${show(placeholder)}`,\n // A text field and the navigation bar's search field draw a placeholder. A wrapper with a\n // `placeholder` input of its own has the attribute on its host view too, which nobody sees.\n select: (node) =>\n (isTextInput(node) || isSearchBar(node)) &&\n matches(node.props['placeholder'], placeholder, options.exact),\n }),\n // A text field's value is its `text` prop, which is what `<text-input>` binds its model to.\n DisplayValue: (value, options = {}) => ({\n describe: `display value ${show(value)}`,\n select: (node) => isTextInput(node) && matches(node.props['text'], value, options.exact),\n }),\n};\n\n/**\n * Every query, bound to a root that is looked up again on each call.\n *\n * `found` sees every node a query hands back, so an event fired on one later knows which render\n * it came from.\n */\nexport function bindQueries(\n roots: () => readonly FakeFabricNode[],\n tree: () => string,\n found: (node: FakeFabricNode) => void,\n): BoundQueries {\n const bound: Record<string, unknown> = {};\n\n for (const name of Object.keys(QUERIES) as Name[]) {\n const all = (...args: unknown[]): FakeFabricNode[] => {\n const query = (QUERIES[name] as (...a: unknown[]) => Query)(...args);\n const options = args[1] as TextMatchOptions | undefined;\n const walk = options?.includeHiddenElements ? flatten : reachable;\n const hits = walk(withParents(roots())).filter(query.select);\n hits.forEach(found);\n return hits;\n };\n const describe = (args: unknown[]): string =>\n (QUERIES[name] as (...a: unknown[]) => Query)(...args).describe;\n const one = (...args: unknown[]): FakeFabricNode => {\n const hits = all(...args);\n if (hits.length === 1) return hits[0]!;\n const problem = hits.length\n ? `Found ${hits.length} nodes with ${describe(args)}, and expected one. Use getAllBy${name} if more than one is expected.`\n : `Unable to find a node with ${describe(args)}.`;\n throw new Error(`${problem}\\n\\n${tree()}`);\n };\n // The last argument of a `findBy` is the `waitFor` options, and it is only ever that when the\n // query itself was given its options too.\n const split = (args: unknown[]): [unknown[], WaitForOptions | undefined] =>\n args.length > 2 ? [args.slice(0, 2), args[2] as WaitForOptions] : [args, undefined];\n\n bound[`getAllBy${name}`] = (...args: unknown[]) => {\n const hits = all(...args);\n if (!hits.length)\n throw new Error(`Unable to find a node with ${describe(args)}.\\n\\n${tree()}`);\n return hits;\n };\n bound[`queryAllBy${name}`] = all;\n bound[`getBy${name}`] = one;\n bound[`queryBy${name}`] = (...args: unknown[]) => {\n const hits = all(...args);\n if (hits.length > 1) return one(...args);\n return hits[0] ?? null;\n };\n bound[`findBy${name}`] = (...args: unknown[]) => {\n const [query, options] = split(args);\n return waitFor(() => one(...query), options);\n };\n bound[`findAllBy${name}`] = (...args: unknown[]) => {\n const [query, options] = split(args);\n return waitFor(() => (bound[`getAllBy${name}`] as typeof all)(...query), options);\n };\n }\n\n return bound as BoundQueries;\n}\n\n/** The debug tree: view names, text, and the props a query can find a node by. */\nexport function describeTree(nodes: readonly FakeFabricNode[], depth = 0): string {\n return nodes\n .map((node) => {\n const text = node.viewName === 'RawText' ? ` ${JSON.stringify(node.props['text'])}` : '';\n const props = IDENTIFYING.filter((key) => node.props[key] != null && node.props[key] !== '')\n .map((key) => ` ${key === 'text' ? 'value' : key}=${JSON.stringify(node.props[key])}`)\n .join('');\n const line = ' '.repeat(depth) + node.viewName + text + (text ? '' : props);\n return node.children.length ? `${line}\\n${describeTree(node.children, depth + 1)}` : line;\n })\n .join('\\n');\n}\n\nconst IDENTIFYING = [\n 'testID',\n 'nativeID',\n 'accessibilityRole',\n 'role',\n 'accessibilityLabel',\n 'placeholder',\n 'text',\n];\n"]}
|
package/dist/test-utils.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
172
|
+
cloneNodeWithNewChildrenAndProps(n, ...rest) {
|
|
157
173
|
calls.cloneWithChildrenAndProps++;
|
|
158
|
-
|
|
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;
|
package/dist/test-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AA+DA;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAA4C,EAAE,cAAuB;IACvF,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,gBAAgB,EAAE;QAC5C,KAAK,EAAE,cAAc;QACrB,UAAU,EAAE,KAAK;QACjB,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IACH,OAAO,IAAsB,CAAC;AAChC,CAAC;AAED,sFAAsF;AACtF,MAAM,OAAO,GAAG,CAAC,KAAa,EAA2B,EAAE,CACzD,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;AAEvF,MAAM,UAAU,gBAAgB;IAC9B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,SAAS,GAAqB,EAAE,CAAC;IACrC,IAAI,OAAO,GAAyD,IAAI,CAAC;IACzE,MAAM,cAAc,GAAiE,EAAE,CAAC;IACxF,MAAM,QAAQ,GAAmE,EAAE,CAAC;IACpF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmE,CAAC;IAC1F,MAAM,KAAK,GAAG;QACZ,UAAU,EAAE,CAAC;QACb,iBAAiB,EAAE,CAAC;QACpB,cAAc,EAAE,CAAC;QACjB,yBAAyB,EAAE,CAAC;QAC5B,YAAY,EAAE,CAAC;KAChB,CAAC;IAEF;;;;;OAKG;IACH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC;IAChB,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,KAAqB,EAAQ,EAAE;QAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,oBAAoB,MAAM,UAAU,GAAG,WAAW;gBACnF,sEAAsE,CACzE,CAAC;QACJ,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC,CAAC;IAEF;;;;;OAKG;IACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,OAAO,GAAG,CAAC,CAAiB,EAAQ,EAAE;QAC1C,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,QAAQ;YAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,CAAC,CAAC;IACF,kGAAkG;IAClG,MAAM,IAAI,GAAG,CAAC,MAAwB,EAAE,KAAuB,EAAQ,EAAE;QACvE,IAAI,MAAM,KAAK,KAAK;YAAE,OAAO;QAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;YACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,CAAC,GAAG;gBAAE,OAAO,CAAC,GAAG,CAAC,CAAC;iBAClB,IAAI,GAAG,KAAK,GAAG;gBAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,CAAC;IAEF;;;;;OAKG;IACH,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,kBAAkB,GAAG,GAAS,EAAE;QACpC,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC9C,MAAM,IAAI,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC;QAC7B,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,cAAc,CAAC,GAAG,EAAE;YAClB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;gBACvB,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;gBACvC,IAAI,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,KAAK;oBAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;YAC7E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,MAAM,SAAS,GAAG,CAAC,KAAuB,EAAE,GAAW,EAA8B,EAAE;QACrF,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;YACtB,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;QACtB,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IACF,wEAAwE;IACxE,MAAM,OAAO,GAAG,CAAC,MAAsB,EAAW,EAAE,CAClD,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,KAAK,CAAC;IAC/E,2FAA2F;IAC3F,MAAM,SAAS,GAAG,CAAC,MAAsB,EAAE,KAA0C,EAAQ,EAAE;QAC7F,IAAI,MAAM,CAAC,QAAQ,KAAK,eAAe,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS,IAAI,KAAK,CAAC;YAAE,OAAO;QACnF,IAAI,KAAK,CAAC,SAAS,CAAC,KAAK,KAAK;YAAE,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;aAC7D,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,KAAK;YAAE,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC9E,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,CAAC,CAAa,EAAE,KAAc,EAAE,YAAY,GAAG,KAAK,EAAc,EAAE;QAChF,MAAM,MAAM,GAAG,CAA8B,CAAC;QAC9C,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACjD,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzB,OAAO,UAAU,CACf;YACE,GAAG,MAAM;YACT,MAAM,EAAE,EAAE,MAAM;YAChB,oFAAoF;YACpF,2FAA2F;YAC3F,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK;YAC3D,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;SAC9C,EACD,MAAM,CAAC,cAAc,CACG,CAAC;IAC7B,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,CACX,QAAgB,EAChB,QAAgB,EAChB,KAAa,EACb,cAAuB,EACP,EAAE,CAClB,UAAU,CACR,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,EAC7E,cAAc,CACf,CAAC;IAEJ,6FAA6F;IAC7F,8FAA8F;IAC9F,sEAAsE;IACtE,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,KAAc,EAAW,EAAE,CACvD,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACzD,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5F,CAAC,CAAC,KAAK,CAAC;IAEZ,MAAM,IAAI,GAAG,CAAC,KAAuB,EAAE,KAAa,EAAE,SAAkB,EAAY,EAAE,CACpF,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;QAClB,uFAAuF;QACvF,+EAA+E;QAC/E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACpC,MAAM,KAAK,GACT,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpF,OAAO;YACL,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,GAAG,IAAI,GAAG,KAAK;YAC9C,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,GAAG,CAAC,EAAE,SAAS,CAAC;SAC1C,CAAC;IACJ,CAAC,CAAC,CAAC;IAEL,MAAM,IAAI,GAAe;QACvB,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc;YAC5D,KAAK,CAAC,UAAU,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,CAA0B,CAAC;QAClF,CAAC;QACD,wBAAwB,CAAC,CAAC;YACxB,KAAK,CAAC,iBAAiB,EAAE,CAAC;YAC1B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,qBAAqB,CAAC,CAAC,EAAE,QAAQ;YAC/B,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,gCAAgC,CAAC,CAAC,EAAE,QAAQ;YAC1C,KAAK,CAAC,yBAAyB,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC5B,CAAC;QACD,WAAW,CAAC,MAAM,EAAE,KAAK;YACvB,MAAM,IAAI,GAAG,MAAmC,CAAC;YACjD,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAkC,CAAC,CAAC;YACzD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAkC,CAAC,CAAC;YACvD,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,cAAc;YACZ,OAAO,EAAmB,CAAC;QAC7B,CAAC;QACD,gBAAgB,CAAC,GAAG,EAAE,KAAK;YACzB,KAAK,CAAC,IAAI,EAAE,KAAkC,CAAC,CAAC;YAC/C,GAAwB,CAAC,IAAI,CAAC,KAAkC,CAAC,CAAC;QACrE,CAAC;QACD,YAAY,CAAC,QAAQ,EAAE,GAAG;YACxB,KAAK,CAAC,YAAY,EAAE,CAAC;YACrB,IAAI,CAAC,SAAS,EAAE,GAAuB,CAAC,CAAC;YACzC,SAAS,GAAG,GAAuB,CAAC;YACpC,kBAAkB,EAAE,CAAC;QACvB,CAAC;QACD,oBAAoB,CAAC,EAAE;YACrB,OAAO,GAAG,EAAE,CAAC;QACf,CAAC;QACD,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK;YACvC,cAAc,CAAC,IAAI,CAAC;gBAClB,QAAQ,EAAG,IAAkC,CAAC,QAAQ;gBACtD,WAAW;gBACX,KAAK;aACN,CAAC,CAAC;QACL,CAAC;QACD,eAAe,CAAC,IAAI,EAAE,SAAS;YAC7B,2FAA2F;YAC3F,mFAAmF;YACnF,MAAM,IAAI,GAAG,IAAiC,CAAC;YAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACtF,IAAI,KAAK;gBAAE,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACpE,CAAC;QACD,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI;YAC9B,MAAM,OAAO,GAAG,EAAE,QAAQ,EAAG,IAAkC,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YACvF,qFAAqF;YACrF,8DAA8D;YAC9D,MAAM,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC;YAC3E,QAAQ,CAAC,IAAI,CAAC,OAAoD,CAAC,CAAC;QACtE,CAAC;QACD,cAAc;QACd,QAAQ;QACR,MAAM;QACN,IAAI,SAAS;YACX,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,KAAK;QACL,MAAM,CAAC,OAAO;YACZ,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChE,CAAC;QACD,IAAI,CAAC,QAAQ;YACX,MAAM,MAAM,GAAG,CAAC,KAAuB,EAA8B,EAAE;gBACrE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACtB,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ;wBAAE,OAAO,CAAC,CAAC;oBACtC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;oBAC/B,IAAI,GAAG;wBAAE,OAAO,GAAG,CAAC;gBACtB,CAAC;gBACD,OAAO,SAAS,CAAC;YACnB,CAAC,CAAC;YACF,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,EAAE;YACjC,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;YACpE,IAAI,IAAI,KAAK,YAAY,IAAI,MAAM,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;gBACjE,IAAI,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;oBAAE,OAAO;gBAC3C,IAAI,OAAO,CAAC,MAAM,CAAC;oBAAE,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACtD,CAAC;YACD,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAC5F,CAAC;QACD,KAAK;YACH,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;YACrB,KAAK,CAAC,iBAAiB,GAAG,CAAC,CAAC;YAC5B,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC;YACzB,KAAK,CAAC,yBAAyB,GAAG,CAAC,CAAC;YACpC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC;QACzB,CAAC;KACF,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC","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"]}
|
package/dist/wait-for.d.ts
CHANGED
|
@@ -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 =
|
|
20
|
+
export async function waitFor(callback, { timeout = config().asyncUtilTimeout, interval = 50 } = {}) {
|
|
9
21
|
const deadline = Date.now() + timeout;
|
|
10
22
|
for (;;) {
|
|
11
23
|
try {
|
package/dist/wait-for.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait-for.js","sourceRoot":"","sources":["../src/wait-for.ts"],"names":[],"mappings":"
|
|
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.
|
|
3
|
+
"version": "0.17.1",
|
|
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.
|
|
38
|
-
"@ng-native/metro": "0.
|
|
39
|
-
"@ng-native/platform": "0.
|
|
37
|
+
"@ng-native/fabric": "0.17.1",
|
|
38
|
+
"@ng-native/metro": "0.17.1",
|
|
39
|
+
"@ng-native/platform": "0.17.1"
|
|
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.
|
|
46
|
+
"@ng-native/components": "0.17.1"
|
|
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.
|
|
66
|
-
"@ng-native/router": "0.
|
|
65
|
+
"@ng-native/components": "0.17.1",
|
|
66
|
+
"@ng-native/router": "0.17.1",
|
|
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
|
-
*
|
|
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;
|