@ng-native/testing 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/compile-css.d.ts +17 -0
  2. package/dist/compile-css.js +14 -0
  3. package/dist/compile-css.js.map +1 -0
  4. package/dist/events.d.ts +9 -0
  5. package/dist/events.js +14 -0
  6. package/dist/events.js.map +1 -1
  7. package/dist/gesture-handler.d.ts +3 -39
  8. package/dist/gesture-handler.js +2 -41
  9. package/dist/gesture-handler.js.map +1 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/inject-service.d.ts +11 -2
  14. package/dist/inject-service.js +26 -6
  15. package/dist/inject-service.js.map +1 -1
  16. package/dist/memory-database.d.ts +23 -0
  17. package/dist/memory-database.js +60 -0
  18. package/dist/memory-database.js.map +1 -0
  19. package/dist/queries.d.ts +5 -0
  20. package/dist/queries.js +21 -3
  21. package/dist/queries.js.map +1 -1
  22. package/dist/reanimated-library.d.ts +5 -59
  23. package/dist/reanimated-library.js +5 -92
  24. package/dist/reanimated-library.js.map +1 -1
  25. package/dist/reanimated.d.ts +3 -0
  26. package/dist/reanimated.js +24 -7
  27. package/dist/reanimated.js.map +1 -1
  28. package/dist/render.js +49 -4
  29. package/dist/render.js.map +1 -1
  30. package/dist/test-utils.d.ts +6 -0
  31. package/dist/test-utils.js +6 -4
  32. package/dist/test-utils.js.map +1 -1
  33. package/dist/worklets-library.d.ts +2 -10
  34. package/dist/worklets-library.js +2 -22
  35. package/dist/worklets-library.js.map +1 -1
  36. package/package.json +8 -7
  37. package/runner/compile.mjs +10 -1
  38. package/runner/register.mjs +14 -0
  39. package/runner/setup.mjs +13 -0
  40. package/runner/vitest.d.mts +6 -0
  41. package/runner/vitest.mjs +22 -14
@@ -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) return matcher.test(text);\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 * 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.\n */\nfunction hides(node: FakeFabricNode): boolean {\n const props = node.props;\n return (\n props['display'] === '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\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) &&\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 select: (node) => 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(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 '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/** `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) return matcher.test(text);\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.\n */\nfunction hides(node: FakeFabricNode): boolean {\n const props = node.props;\n return (\n props['display'] === '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\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 alone draws a placeholder. A wrapper with a `placeholder` input of its own has\n // the attribute on its host view too, which nobody sees.\n select: (node) =>\n isTextInput(node) && 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,62 +1,8 @@
1
1
  /**
2
- * What a test gets for `react-native-reanimated`, whose source Node cannot load.
3
- *
4
- * An animation finishes the moment it is assigned: `withTiming(100)` is `100`, and its callback
5
- * is called with `true` at once, so a test sees where an animation ends and what ends it does.
6
- * `interpolate` and `Extrapolation` are the library's own arithmetic; `Easing` curves are the
7
- * identity, since nothing in a test is between its ends.
8
- *
9
- * ponytail: the functions apps call from components, not the React hooks; add one when an app
10
- * under test imports it.
2
+ * What a test gets for `react-native-reanimated`, whose source Node cannot load: the stand-in a
3
+ * browser build gets too, with a shared value that is a signal, which a `[workletStyle]` in a test
4
+ * follows (see `reanimated.ts`).
11
5
  */
12
- import { sharedValue } from './reanimated.ts';
6
+ export * from '@ng-native/components/stand-ins/reanimated';
7
+ export { sharedValue as makeMutable } from './reanimated.ts';
13
8
  export type { SharedValue } from './reanimated.ts';
14
- type Done = ((finished?: boolean) => void) | undefined;
15
- export declare const makeMutable: typeof sharedValue;
16
- export declare function withTiming<T>(to: T, _config?: object, callback?: Done): T;
17
- export declare function withSpring<T>(to: T, _config?: object, callback?: Done): T;
18
- export declare function withDecay(_config: {
19
- velocity?: number;
20
- }, callback?: Done): number;
21
- export declare function withDelay<T>(_delay: number, animation: T): T;
22
- export declare function withSequence<T>(...animations: T[]): T;
23
- export declare function withRepeat<T>(animation: T, _times?: number, _reverse?: boolean, callback?: Done): T;
24
- export declare function cancelAnimation(_value: unknown): void;
25
- /** What `@ng-native/components/reanimated` itself starts a style with; nothing runs it here. */
26
- export declare function startMapper(_worklet: () => void, _inputs?: unknown[]): number;
27
- export declare function stopMapper(_id: number): void;
28
- export declare const Extrapolation: {
29
- readonly IDENTITY: "identity";
30
- readonly CLAMP: "clamp";
31
- readonly EXTEND: "extend";
32
- };
33
- type Extrapolate = (typeof Extrapolation)[keyof typeof Extrapolation];
34
- export declare function interpolate(x: number, input: readonly number[], output: readonly number[], extrapolate?: Extrapolate | {
35
- extrapolateLeft?: Extrapolate;
36
- extrapolateRight?: Extrapolate;
37
- }): number;
38
- export declare const Easing: {
39
- linear: (t: number) => number;
40
- ease: (t: number) => number;
41
- quad: (t: number) => number;
42
- cubic: (t: number) => number;
43
- sin: (t: number) => number;
44
- circle: (t: number) => number;
45
- exp: (t: number) => number;
46
- bounce: (t: number) => number;
47
- poly: () => (t: number) => number;
48
- elastic: () => (t: number) => number;
49
- back: () => (t: number) => number;
50
- bezier: () => (t: number) => number;
51
- bezierFn: () => (t: number) => number;
52
- in: (easing: (t: number) => number) => (t: number) => number;
53
- out: (easing: (t: number) => number) => (t: number) => number;
54
- inOut: (easing: (t: number) => number) => (t: number) => number;
55
- };
56
- export declare const ReduceMotion: {
57
- readonly System: "system";
58
- readonly Always: "always";
59
- readonly Never: "never";
60
- };
61
- export declare function runOnJS<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
62
- export declare function runOnUI<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
@@ -1,95 +1,8 @@
1
1
  /**
2
- * What a test gets for `react-native-reanimated`, whose source Node cannot load.
3
- *
4
- * An animation finishes the moment it is assigned: `withTiming(100)` is `100`, and its callback
5
- * is called with `true` at once, so a test sees where an animation ends and what ends it does.
6
- * `interpolate` and `Extrapolation` are the library's own arithmetic; `Easing` curves are the
7
- * identity, since nothing in a test is between its ends.
8
- *
9
- * ponytail: the functions apps call from components, not the React hooks; add one when an app
10
- * under test imports it.
2
+ * What a test gets for `react-native-reanimated`, whose source Node cannot load: the stand-in a
3
+ * browser build gets too, with a shared value that is a signal, which a `[workletStyle]` in a test
4
+ * follows (see `reanimated.ts`).
11
5
  */
12
- import { sharedValue } from "./reanimated.js";
13
- const settle = (to, callback) => {
14
- callback?.(true);
15
- return to;
16
- };
17
- export const makeMutable = sharedValue;
18
- export function withTiming(to, _config, callback) {
19
- return settle(to, callback);
20
- }
21
- export function withSpring(to, _config, callback) {
22
- return settle(to, callback);
23
- }
24
- export function withDecay(_config, callback) {
25
- return settle(0, callback);
26
- }
27
- export function withDelay(_delay, animation) {
28
- return animation;
29
- }
30
- export function withSequence(...animations) {
31
- return animations[animations.length - 1];
32
- }
33
- export function withRepeat(animation, _times, _reverse, callback) {
34
- return settle(animation, callback);
35
- }
36
- export function cancelAnimation(_value) { }
37
- /** What `@ng-native/components/reanimated` itself starts a style with; nothing runs it here. */
38
- export function startMapper(_worklet, _inputs) {
39
- return 0;
40
- }
41
- export function stopMapper(_id) { }
42
- export const Extrapolation = { IDENTITY: 'identity', CLAMP: 'clamp', EXTEND: 'extend' };
43
- /** Past an end of the input range: the end's output, the input itself, or `undefined` to extend. */
44
- function beyond(x, output, how) {
45
- if (how === 'clamp')
46
- return output;
47
- if (how === 'identity')
48
- return x;
49
- return undefined;
50
- }
51
- export function interpolate(x, input, output, extrapolate) {
52
- const left = typeof extrapolate === 'object' ? extrapolate.extrapolateLeft : extrapolate;
53
- const right = typeof extrapolate === 'object' ? extrapolate.extrapolateRight : extrapolate;
54
- const last = input.length - 1;
55
- const outside = x < input[0]
56
- ? beyond(x, output[0], left)
57
- : x > input[last]
58
- ? beyond(x, output[last], right)
59
- : undefined;
60
- if (outside !== undefined)
61
- return outside;
62
- let i = 0;
63
- while (i < last - 1 && x > input[i + 1])
64
- i++;
65
- const [a, b, p, q] = [input[i], input[i + 1], output[i], output[i + 1]];
66
- return b === a ? p : p + ((x - a) / (b - a)) * (q - p);
67
- }
68
- const identity = (t) => t;
69
- const curve = () => identity;
70
- export const Easing = {
71
- linear: identity,
72
- ease: identity,
73
- quad: identity,
74
- cubic: identity,
75
- sin: identity,
76
- circle: identity,
77
- exp: identity,
78
- bounce: identity,
79
- poly: curve,
80
- elastic: curve,
81
- back: curve,
82
- bezier: curve,
83
- bezierFn: curve,
84
- in: (easing) => easing,
85
- out: (easing) => easing,
86
- inOut: (easing) => easing,
87
- };
88
- export const ReduceMotion = { System: 'system', Always: 'always', Never: 'never' };
89
- export function runOnJS(fn) {
90
- return fn;
91
- }
92
- export function runOnUI(fn) {
93
- return fn;
94
- }
6
+ export * from '@ng-native/components/stand-ins/reanimated';
7
+ export { sharedValue as makeMutable } from "./reanimated.js";
95
8
  //# sourceMappingURL=reanimated-library.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reanimated-library.js","sourceRoot":"","sources":["../src/reanimated-library.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a test gets for `react-native-reanimated`, whose source Node cannot load.\n *\n * An animation finishes the moment it is assigned: `withTiming(100)` is `100`, and its callback\n * is called with `true` at once, so a test sees where an animation ends and what ends it does.\n * `interpolate` and `Extrapolation` are the library's own arithmetic; `Easing` curves are the\n * identity, since nothing in a test is between its ends.\n *\n * ponytail: the functions apps call from components, not the React hooks; add one when an app\n * under test imports it.\n */\nimport { sharedValue } from './reanimated.ts';\n\nexport type { SharedValue } from './reanimated.ts';\n\ntype Done = ((finished?: boolean) => void) | undefined;\n\nconst settle = <T>(to: T, callback: Done): T => {\n callback?.(true);\n return to;\n};\n\nexport const makeMutable = sharedValue;\n\nexport function withTiming<T>(to: T, _config?: object, callback?: Done): T {\n return settle(to, callback);\n}\n\nexport function withSpring<T>(to: T, _config?: object, callback?: Done): T {\n return settle(to, callback);\n}\n\nexport function withDecay(_config: { velocity?: number }, callback?: Done): number {\n return settle(0, callback);\n}\n\nexport function withDelay<T>(_delay: number, animation: T): T {\n return animation;\n}\n\nexport function withSequence<T>(...animations: T[]): T {\n return animations[animations.length - 1]!;\n}\n\nexport function withRepeat<T>(\n animation: T,\n _times?: number,\n _reverse?: boolean,\n callback?: Done,\n): T {\n return settle(animation, callback);\n}\n\nexport function cancelAnimation(_value: unknown): void {}\n\n/** What `@ng-native/components/reanimated` itself starts a style with; nothing runs it here. */\nexport function startMapper(_worklet: () => void, _inputs?: unknown[]): number {\n return 0;\n}\n\nexport function stopMapper(_id: number): void {}\n\nexport const Extrapolation = { IDENTITY: 'identity', CLAMP: 'clamp', EXTEND: 'extend' } as const;\ntype Extrapolate = (typeof Extrapolation)[keyof typeof Extrapolation];\n\n/** Past an end of the input range: the end's output, the input itself, or `undefined` to extend. */\nfunction beyond(x: number, output: number, how: Extrapolate | undefined): number | undefined {\n if (how === 'clamp') return output;\n if (how === 'identity') return x;\n return undefined;\n}\n\nexport function interpolate(\n x: number,\n input: readonly number[],\n output: readonly number[],\n extrapolate?: Extrapolate | { extrapolateLeft?: Extrapolate; extrapolateRight?: Extrapolate },\n): number {\n const left = typeof extrapolate === 'object' ? extrapolate.extrapolateLeft : extrapolate;\n const right = typeof extrapolate === 'object' ? extrapolate.extrapolateRight : extrapolate;\n const last = input.length - 1;\n const outside =\n x < input[0]!\n ? beyond(x, output[0]!, left)\n : x > input[last]!\n ? beyond(x, output[last]!, right)\n : undefined;\n if (outside !== undefined) return outside;\n let i = 0;\n while (i < last - 1 && x > input[i + 1]!) i++;\n const [a, b, p, q] = [input[i]!, input[i + 1]!, output[i]!, output[i + 1]!];\n return b === a ? p : p + ((x - a) / (b - a)) * (q - p);\n}\n\nconst identity = (t: number) => t;\nconst curve = () => identity;\nexport const Easing = {\n linear: identity,\n ease: identity,\n quad: identity,\n cubic: identity,\n sin: identity,\n circle: identity,\n exp: identity,\n bounce: identity,\n poly: curve,\n elastic: curve,\n back: curve,\n bezier: curve,\n bezierFn: curve,\n in: (easing: (t: number) => number) => easing,\n out: (easing: (t: number) => number) => easing,\n inOut: (easing: (t: number) => number) => easing,\n};\n\nexport const ReduceMotion = { System: 'system', Always: 'always', Never: 'never' } as const;\n\nexport function runOnJS<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R {\n return fn;\n}\n\nexport function runOnUI<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R {\n return fn;\n}\n"]}
1
+ {"version":3,"file":"reanimated-library.js","sourceRoot":"","sources":["../src/reanimated-library.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,4CAA4C,CAAC;AAC3D,OAAO,EAAE,WAAW,IAAI,WAAW,EAAE,MAAM,iBAAiB,CAAC","sourcesContent":["/**\n * What a test gets for `react-native-reanimated`, whose source Node cannot load: the stand-in a\n * browser build gets too, with a shared value that is a signal, which a `[workletStyle]` in a test\n * follows (see `reanimated.ts`).\n */\nexport * from '@ng-native/components/stand-ins/reanimated';\nexport { sharedValue as makeMutable } from './reanimated.ts';\nexport type { SharedValue } from './reanimated.ts';\n"]}
@@ -4,6 +4,9 @@ export interface SharedValue<T = number> {
4
4
  value: T;
5
5
  get(): T;
6
6
  set(value: T | ((current: T) => T)): void;
7
+ modify(modifier?: (value: T) => T, forceUpdate?: boolean): void;
8
+ addListener(id: number, listener: (value: T) => void): void;
9
+ removeListener(id: number): void;
7
10
  }
8
11
  export interface WorkletStyleSpec {
9
12
  readonly values: readonly unknown[];
@@ -11,21 +11,36 @@
11
11
  * event the view emits. So a test sees the style an animation settles on, not the frames between:
12
12
  * `react-native-reanimated`'s stand-in finishes every animation at once.
13
13
  */
14
- import { Directive, ElementRef, Renderer2, effect, inject, input, signal } from '@angular/core';
14
+ import { Directive, ElementRef, Renderer2, effect, inject, input, signal, untracked, } from '@angular/core';
15
15
  import { HostEngine } from '@ng-native/fabric';
16
16
  import * as i0 from "@angular/core";
17
17
  export function sharedValue(initial) {
18
- const state = signal(initial, /* @ts-ignore */
19
- ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
18
+ // A write of the same value is skipped, as Reanimated's is, unless it is forced. A forced one
19
+ // reaches every reader, which a signal's own equality check would stop.
20
+ let forced = false;
21
+ const state = signal(initial, { ...(ngDevMode ? { debugName: "state" } : /* istanbul ignore next */ {}), equal: (a, b) => !forced && Object.is(a, b) });
22
+ const listeners = new Map();
23
+ const write = (next, force = false) => {
24
+ if (!force && Object.is(next, untracked(state)))
25
+ return;
26
+ forced = force;
27
+ state.set(next);
28
+ forced = false;
29
+ for (const listener of listeners.values())
30
+ listener(next);
31
+ };
20
32
  return {
21
33
  get value() {
22
34
  return state();
23
35
  },
24
36
  set value(next) {
25
- state.set(next);
37
+ write(next);
26
38
  },
27
39
  get: () => state(),
28
- set: (next) => state.set(typeof next === 'function' ? next(state()) : next),
40
+ set: (next) => write(typeof next === 'function' ? next(state()) : next),
41
+ modify: (modifier, forceUpdate = true) => write(modifier ? modifier(untracked(state)) : untracked(state), forceUpdate),
42
+ addListener: (id, listener) => void listeners.set(id, listener),
43
+ removeListener: (id) => void listeners.delete(id),
29
44
  };
30
45
  }
31
46
  export function workletStyle(values, updater) {
@@ -44,11 +59,13 @@ export class WorkletStyle {
44
59
  effect(() => {
45
60
  const { values, updater } = this.workletStyle();
46
61
  const style = updater(...values);
62
+ // As styles, which is what they are: written as props, `opacity` and `height` are names
63
+ // no view declares, and the unknown-prop check says so.
47
64
  for (const key of written)
48
65
  if (!(key in style))
49
- renderer.setProperty(node, key, undefined);
66
+ renderer.removeStyle(node, key);
50
67
  for (const [key, value] of Object.entries(style))
51
- renderer.setProperty(node, key, value);
68
+ renderer.setStyle(node, key, value);
52
69
  written = Object.keys(style);
53
70
  });
54
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"reanimated.js","sourceRoot":"","sources":["../src/reanimated.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a test gets for `@ng-native/components/reanimated`.\n *\n * The real entry point reaches Reanimated's and react-native-worklets' source, which Node cannot\n * load, so a test of any component with a `[workletStyle]` or a `[workletScroll]` failed to import\n * before it ran. `ngNative()` resolves the entry point here instead, with the same selectors,\n * inputs and factories, and the one runtime a test has standing in for the UI thread's.\n *\n * A shared value is a signal. A `[workletStyle]` writes what its worklet returns onto its view,\n * again whenever a value it read changes; a `[workletScroll]` runs its worklet for every scroll\n * event the view emits. So a test sees the style an animation settles on, not the frames between:\n * `react-native-reanimated`'s stand-in finishes every animation at once.\n */\nimport { Directive, ElementRef, Renderer2, effect, inject, input, signal } from '@angular/core';\nimport { HostEngine, type HostNode } from '@ng-native/fabric';\n\n/** A value both runtimes can see; here there is one runtime, and it is a signal. */\nexport interface SharedValue<T = number> {\n value: T;\n get(): T;\n set(value: T | ((current: T) => T)): void;\n}\n\nexport interface WorkletStyleSpec {\n readonly values: readonly unknown[];\n updater(...values: readonly unknown[]): Record<string, unknown>;\n}\n\nexport interface WorkletScrollSpec {\n readonly values: readonly unknown[];\n handler(event: never, ...values: readonly unknown[]): void;\n}\n\nexport function sharedValue<T>(initial: T): SharedValue<T> {\n const state = signal(initial);\n return {\n get value() {\n return state();\n },\n set value(next: T) {\n state.set(next);\n },\n get: () => state(),\n set: (next) =>\n state.set(typeof next === 'function' ? (next as (current: T) => T)(state()) : next),\n };\n}\n\nexport function workletStyle<const T extends readonly SharedValue<unknown>[]>(\n values: T,\n updater: (...values: T) => Record<string, unknown>,\n): WorkletStyleSpec {\n return { values, updater } as WorkletStyleSpec;\n}\n\nexport function workletScroll<const T extends readonly SharedValue<unknown>[]>(\n values: T,\n handler: (event: never, ...values: T) => void,\n): WorkletScrollSpec {\n return { values, handler } as WorkletScrollSpec;\n}\n\n@Directive({ selector: '[workletStyle]' })\nexport class WorkletStyle {\n readonly workletStyle = input.required<WorkletStyleSpec>();\n\n constructor() {\n const node = inject(ElementRef).nativeElement as HostNode;\n const renderer = inject(Renderer2);\n let written: readonly string[] = [];\n effect(() => {\n const { values, updater } = this.workletStyle();\n const style = updater(...values);\n for (const key of written) if (!(key in style)) renderer.setProperty(node, key, undefined);\n for (const [key, value] of Object.entries(style)) renderer.setProperty(node, key, value);\n written = Object.keys(style);\n });\n }\n}\n\n@Directive({ selector: '[workletScroll]' })\nexport class WorkletScroll {\n readonly workletScroll = input.required<WorkletScrollSpec>();\n\n constructor() {\n const node = inject(ElementRef).nativeElement as HostNode;\n const engine = inject(HostEngine);\n effect((onCleanup) => {\n const { values, handler } = this.workletScroll();\n onCleanup(\n engine.setEventListener(node, 'topScroll', (event) =>\n handler((event as { nativeEvent: never }).nativeEvent, ...values),\n ),\n );\n });\n }\n}\n"]}
1
+ {"version":3,"file":"reanimated.js","sourceRoot":"","sources":["../src/reanimated.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a test gets for `@ng-native/components/reanimated`.\n *\n * The real entry point reaches Reanimated's and react-native-worklets' source, which Node cannot\n * load, so a test of any component with a `[workletStyle]` or a `[workletScroll]` failed to import\n * before it ran. `ngNative()` resolves the entry point here instead, with the same selectors,\n * inputs and factories, and the one runtime a test has standing in for the UI thread's.\n *\n * A shared value is a signal. A `[workletStyle]` writes what its worklet returns onto its view,\n * again whenever a value it read changes; a `[workletScroll]` runs its worklet for every scroll\n * event the view emits. So a test sees the style an animation settles on, not the frames between:\n * `react-native-reanimated`'s stand-in finishes every animation at once.\n */\nimport {\n Directive,\n ElementRef,\n Renderer2,\n effect,\n inject,\n input,\n signal,\n untracked,\n} from '@angular/core';\nimport { HostEngine, type HostNode } from '@ng-native/fabric';\n\n/** A value both runtimes can see; here there is one runtime, and it is a signal. */\nexport interface SharedValue<T = number> {\n value: T;\n get(): T;\n set(value: T | ((current: T) => T)): void;\n modify(modifier?: (value: T) => T, forceUpdate?: boolean): void;\n addListener(id: number, listener: (value: T) => void): void;\n removeListener(id: number): void;\n}\n\nexport interface WorkletStyleSpec {\n readonly values: readonly unknown[];\n updater(...values: readonly unknown[]): Record<string, unknown>;\n}\n\nexport interface WorkletScrollSpec {\n readonly values: readonly unknown[];\n handler(event: never, ...values: readonly unknown[]): void;\n}\n\nexport function sharedValue<T>(initial: T): SharedValue<T> {\n // A write of the same value is skipped, as Reanimated's is, unless it is forced. A forced one\n // reaches every reader, which a signal's own equality check would stop.\n let forced = false;\n const state = signal(initial, { equal: (a, b) => !forced && Object.is(a, b) });\n const listeners = new Map<number, (value: T) => void>();\n const write = (next: T, force = false) => {\n if (!force && Object.is(next, untracked(state))) return;\n forced = force;\n state.set(next);\n forced = false;\n for (const listener of listeners.values()) listener(next);\n };\n return {\n get value() {\n return state();\n },\n set value(next: T) {\n write(next);\n },\n get: () => state(),\n set: (next) => write(typeof next === 'function' ? (next as (current: T) => T)(state()) : next),\n modify: (modifier, forceUpdate = true) =>\n write(modifier ? modifier(untracked(state)) : untracked(state), forceUpdate),\n addListener: (id, listener) => void listeners.set(id, listener),\n removeListener: (id) => void listeners.delete(id),\n };\n}\n\nexport function workletStyle<const T extends readonly SharedValue<unknown>[]>(\n values: T,\n updater: (...values: T) => Record<string, unknown>,\n): WorkletStyleSpec {\n return { values, updater } as WorkletStyleSpec;\n}\n\nexport function workletScroll<const T extends readonly SharedValue<unknown>[]>(\n values: T,\n handler: (event: never, ...values: T) => void,\n): WorkletScrollSpec {\n return { values, handler } as WorkletScrollSpec;\n}\n\n@Directive({ selector: '[workletStyle]' })\nexport class WorkletStyle {\n readonly workletStyle = input.required<WorkletStyleSpec>();\n\n constructor() {\n const node = inject(ElementRef).nativeElement as HostNode;\n const renderer = inject(Renderer2);\n let written: readonly string[] = [];\n effect(() => {\n const { values, updater } = this.workletStyle();\n const style = updater(...values);\n // As styles, which is what they are: written as props, `opacity` and `height` are names\n // no view declares, and the unknown-prop check says so.\n for (const key of written) if (!(key in style)) renderer.removeStyle(node, key);\n for (const [key, value] of Object.entries(style)) renderer.setStyle(node, key, value);\n written = Object.keys(style);\n });\n }\n}\n\n@Directive({ selector: '[workletScroll]' })\nexport class WorkletScroll {\n readonly workletScroll = input.required<WorkletScrollSpec>();\n\n constructor() {\n const node = inject(ElementRef).nativeElement as HostNode;\n const engine = inject(HostEngine);\n effect((onCleanup) => {\n const { values, handler } = this.workletScroll();\n onCleanup(\n engine.setEventListener(node, 'topScroll', (event) =>\n handler((event as { nativeEvent: never }).nativeEvent, ...values),\n ),\n );\n });\n }\n}\n"]}
package/dist/render.js CHANGED
@@ -13,7 +13,11 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
13
13
  * A render is a real `mount()` - the same bootstrap an app runs, with the same renderer and engine
14
14
  * - onto a `createFakeFabric()`, so what a query reads is what native would have been handed.
15
15
  */
16
- import { ChangeDetectorRef, Component, } from '@angular/core';
16
+ import { ChangeDetectorRef, Component,
17
+ // The rule is for templates an app ships, which are checked when they are compiled. The one use
18
+ // here is the host a test's template string is compiled into: see `hostFor`.
19
+ // eslint-disable-next-line no-restricted-syntax
20
+ NO_ERRORS_SCHEMA, outputBinding, } from '@angular/core';
17
21
  import { mount } from '@ng-native/platform';
18
22
  import { destroyServiceApps } from "./inject-service.js";
19
23
  import { bindQueries, describeTree, flatten } from "./queries.js";
@@ -58,6 +62,34 @@ const tree = (nodes) => describeTree(nodes) || '(nothing)';
58
62
  function queriesFor(roots, owner) {
59
63
  return bindQueries(roots, () => tree(roots()), (node) => owners.set(node, owner()));
60
64
  }
65
+ /**
66
+ * The names a template can bind as inputs on `type`: its own, by alias where one has one, and
67
+ * those its host directives forward, by the name each is forwarded under. Null where the
68
+ * definition cannot be read, and nothing is checked.
69
+ */
70
+ function inputNames(type) {
71
+ const def = type.ɵcmp;
72
+ if (!def?.inputs)
73
+ return null;
74
+ const forwarded = (def.hostDirectives ?? []).flatMap((entry) => Object.values((typeof entry === 'function' ? entry() : entry).inputs ?? {}));
75
+ return [...Object.keys(def.inputs), ...forwarded];
76
+ }
77
+ /**
78
+ * Throws for a name in `inputs` that is no input of `type`. Angular only logs one, and renders the
79
+ * component with its defaults. Checked before any input is set, so a refused batch changes nothing
80
+ * and a required input left unset by a misspelling is named for what it is.
81
+ */
82
+ function refuseUnknownInputs(type, inputs = {}) {
83
+ const known = inputNames(type);
84
+ if (!known)
85
+ return;
86
+ const unknown = Object.keys(inputs).filter((name) => !known.includes(name));
87
+ if (!unknown.length)
88
+ return;
89
+ throw new Error(`[angular-native] ${type.name} has no input named ` +
90
+ `${unknown.map((name) => `'${name}'`).join(', ')}. ` +
91
+ (known.length ? `Its inputs are ${known.join(', ')}.` : 'It has no inputs.'));
92
+ }
61
93
  /**
62
94
  * The template form, compiled just in time.
63
95
  *
@@ -65,6 +97,8 @@ function queriesFor(roots, owner) {
65
97
  * class form is AOT like the app. An AOT compile of the string would mean evaluating generated
66
98
  * code that imports the caller's own classes by name; do that if JIT ever disagrees with AOT.
67
99
  */
100
+ /** Wrappers built so far: each takes the next as a host attribute, so no two share an ID. */
101
+ let wrappers = 0;
68
102
  async function hostFor(template, options) {
69
103
  await import(__rewriteRelativeImportExtension('@angular/compiler'));
70
104
  const properties = options.componentProperties ?? {};
@@ -75,8 +109,16 @@ async function hostFor(template, options) {
75
109
  }
76
110
  return Component({
77
111
  selector: 'ng-native-wrapper',
112
+ // Angular derives a component's ID from its metadata, not its template's text, so wrappers
113
+ // alike in shape collided (NG0912) without it.
114
+ host: { 'data-wrapper': String(++wrappers) },
78
115
  template,
79
116
  imports: (options.imports ?? []),
117
+ // A component's own host bindings are checked against the schemas of the template it is used
118
+ // in, and only where that template was compiled just in time: the app's, compiled ahead, has
119
+ // no such check. Without this every native prop a component binds on its host logs NG0303.
120
+ // eslint-disable-next-line no-restricted-syntax -- as above
121
+ schemas: [NO_ERRORS_SCHEMA],
80
122
  })(WrapperComponent);
81
123
  }
82
124
  /**
@@ -92,10 +134,15 @@ export async function render(component, options = {}) {
92
134
  const isTemplate = typeof component === 'string';
93
135
  const type = isTemplate ? await hostFor(component, options) : component;
94
136
  const fabric = createFakeFabric();
137
+ if (!isTemplate)
138
+ refuseUnknownInputs(type, inputs);
95
139
  const app = mount(1, type, fabric, {
96
140
  ...engine,
97
141
  providers,
98
142
  inputs: isTemplate ? {} : inputs,
143
+ // As a template would bind them, so an output a host directive forwards is heard, by the name
144
+ // it is forwarded under, and a name that is no output is refused by Angular, by name.
145
+ bindings: Object.entries(on ?? {}).map(([name, handler]) => outputBinding(name, handler)),
99
146
  });
100
147
  const self = {
101
148
  fabric,
@@ -106,9 +153,6 @@ export async function render(component, options = {}) {
106
153
  mounted.push(self);
107
154
  const componentRef = app.componentRef;
108
155
  const instance = componentRef.instance;
109
- for (const [name, handler] of Object.entries(on ?? {})) {
110
- instance[name].subscribe(handler);
111
- }
112
156
  const detectChanges = async () => {
113
157
  // The component's own view, not the host's: `componentRef.changeDetectorRef` marks the host
114
158
  // and upwards, which leaves an OnPush component with a changed field exactly as it was.
@@ -117,6 +161,7 @@ export async function render(component, options = {}) {
117
161
  await settle();
118
162
  };
119
163
  const rerender = async (changes = {}) => {
164
+ refuseUnknownInputs(type, changes.inputs);
120
165
  for (const [name, value] of Object.entries(changes.inputs ?? {})) {
121
166
  componentRef.setInput(name, value);
122
167
  }
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `render()`, `screen` and `within()`, shaped like Angular Testing Library's and running on the\n * fake Fabric instead of TestBed and a DOM.\n *\n * A render is a real `mount()` - the same bootstrap an app runs, with the same renderer and engine\n * - onto a `createFakeFabric()`, so what a query reads is what native would have been handed.\n */\nimport {\n ChangeDetectorRef,\n Component,\n type ApplicationRef,\n type ComponentRef,\n type EnvironmentProviders,\n type Provider,\n type Type,\n} from '@angular/core';\nimport type { Engine, EngineOptions } from '@ng-native/fabric';\nimport { mount } from '@ng-native/platform';\nimport { destroyServiceApps } from './inject-service.ts';\nimport { bindQueries, describeTree, flatten, type BoundQueries } from './queries.ts';\nimport { createFakeFabric, type FakeFabric, type FakeFabricNode } from './test-utils.ts';\nimport { waitFor, type WaitForOptions } from './wait-for.ts';\n\n/**\n * What `render()` takes: Angular Testing Library's options, plus the engine's own.\n *\n * The engine options (`globalStyles`, `conditions`, `tokens` and the rest) are what `mount()`\n * takes on a device, so a test styles a component with the same compiled sheet an app would.\n */\nexport interface RenderOptions<T> extends EngineOptions {\n /** The component's inputs, set before its first change detection, so `required` ones work. */\n inputs?: Record<string, unknown>;\n /** Output handlers, by output name. */\n on?: Record<string, (value: never) => void>;\n /** Environment providers for the app the component is mounted in. */\n providers?: (Provider | EnvironmentProviders)[];\n /** The template form only: what the template may use. */\n imports?: unknown[];\n /** Fields set on the instance: the template form's host, or the component itself. */\n componentProperties?: Partial<T> & Record<string, unknown>;\n}\n\n/**\n * What `render()` resolves to: every query, bound to this render, and the handles to drive it.\n *\n * The queries here stay on this render even after a later one; `screen` moves to the latest.\n */\nexport interface RenderResult<T> extends BoundQueries {\n fabric: FakeFabric;\n componentRef: ComponentRef<T>;\n instance: T;\n /** `ComponentFixture`'s shape, for tests written against it. */\n fixture: {\n componentRef: ComponentRef<T>;\n componentInstance: T;\n detectChanges(): Promise<void>;\n };\n /** Set inputs and properties again, then let the result commit. */\n rerender(changes?: {\n inputs?: Record<string, unknown>;\n componentProperties?: Record<string, unknown>;\n }): Promise<void>;\n /** Run change detection and wait for the commit. */\n detectChanges(): Promise<void>;\n /** Print the committed tree, or one node's subtree. */\n debug(node?: FakeFabricNode): void;\n unmount(): void;\n}\n\ninterface Mounted {\n fabric: FakeFabric;\n engine: Engine;\n applicationRef: ApplicationRef;\n componentRef: ComponentRef<unknown>;\n}\n\nconst mounted: Mounted[] = [];\n/** Which render a node a query returned belongs to, so an event on it reaches the right fake. */\nconst owners = new WeakMap<FakeFabricNode, Mounted>();\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Let zoneless change detection run and commit.\n *\n * One macrotask is enough for a change Angular sees: it schedules its pass on `setTimeout(0)`\n * (racing an animation frame), so a timeout queued after the change runs after the pass. A change\n * the engine sees outside change detection, such as a modal's reported dismissal with no\n * `(dismiss)` listener, commits on the platform's next frame instead, 16ms away in Node, so this\n * waits for that frame while a commit is owed.\n */\nexport async function settle(): Promise<void> {\n await sleep(0);\n for (let frames = 0; frames < 3 && mounted.some((m) => m.engine.pending); frames++) {\n await sleep(16);\n }\n}\n\nfunction latest(): Mounted {\n const current = mounted.at(-1);\n if (!current) throw new Error('Nothing is rendered: call render() first.');\n return current;\n}\n\nexport function ownerOf(node: FakeFabricNode): Mounted {\n return owners.get(node) ?? latest();\n}\n\n/** The node as it is in the most recent commit, found by its tag, or null once it is gone. */\nexport function currentOf(node: FakeFabricNode): FakeFabricNode | null {\n const hit = flatten(ownerOf(node).fabric.committed).find((n) => n.reactTag === node.reactTag);\n if (hit) owners.set(hit, ownerOf(node));\n return hit ?? null;\n}\n\nconst tree = (nodes: readonly FakeFabricNode[]): string => describeTree(nodes) || '(nothing)';\n\nfunction queriesFor(roots: () => readonly FakeFabricNode[], owner: () => Mounted): BoundQueries {\n return bindQueries(\n roots,\n () => tree(roots()),\n (node) => owners.set(node, owner()),\n );\n}\n\n/**\n * The template form, compiled just in time.\n *\n * ponytail: this needs `@angular/compiler` at runtime, the one place anything here does - the\n * class form is AOT like the app. An AOT compile of the string would mean evaluating generated\n * code that imports the caller's own classes by name; do that if JIT ever disagrees with AOT.\n */\nasync function hostFor<T>(template: string, options: RenderOptions<T>): Promise<Type<unknown>> {\n await import('@angular/compiler' as string);\n const properties = options.componentProperties ?? {};\n class WrapperComponent {\n constructor() {\n Object.assign(this, properties);\n }\n }\n return Component({\n selector: 'ng-native-wrapper',\n template,\n imports: (options.imports ?? []) as Type<unknown>[],\n })(WrapperComponent);\n}\n\n/**\n * Mount a component, or a template string, onto a fresh fake Fabric and wait for the first commit.\n *\n * The same `mount()` an app boots with, so what the queries read is exactly what native would\n * have been sent. A string is compiled as a host component's template, with `imports` and\n * `componentProperties` for what it uses; a class is rendered as-is, with `inputs` set before its\n * first change detection so `input.required()` works.\n */\nexport async function render<T>(\n component: Type<T> | string,\n options: RenderOptions<T> = {},\n): Promise<RenderResult<T>> {\n const { inputs, on, providers, imports: _, componentProperties, ...engine } = options;\n const isTemplate = typeof component === 'string';\n const type = isTemplate ? await hostFor(component, options) : component;\n const fabric = createFakeFabric();\n const app = mount(1, type, fabric, {\n ...engine,\n providers,\n inputs: isTemplate ? {} : inputs,\n });\n const self: Mounted = {\n fabric,\n engine: app.engine,\n applicationRef: app.applicationRef,\n componentRef: app.componentRef,\n };\n mounted.push(self);\n\n const componentRef = app.componentRef as ComponentRef<T>;\n const instance = componentRef.instance as T & Record<string, unknown>;\n for (const [name, handler] of Object.entries(on ?? {})) {\n (instance[name] as { subscribe(fn: (value: never) => void): void }).subscribe(handler);\n }\n\n const detectChanges = async (): Promise<void> => {\n // The component's own view, not the host's: `componentRef.changeDetectorRef` marks the host\n // and upwards, which leaves an OnPush component with a changed field exactly as it was.\n componentRef.injector.get(ChangeDetectorRef).markForCheck();\n app.applicationRef.tick();\n await settle();\n };\n const rerender: RenderResult<T>['rerender'] = async (changes = {}) => {\n for (const [name, value] of Object.entries(changes.inputs ?? {})) {\n componentRef.setInput(name, value);\n }\n Object.assign(instance, changes.componentProperties);\n await detectChanges();\n };\n\n if (!isTemplate && componentProperties) await rerender({ componentProperties });\n else await settle();\n\n return {\n ...queriesFor(\n () => fabric.committed,\n () => self,\n ),\n fabric,\n componentRef,\n instance,\n fixture: { componentRef, componentInstance: instance, detectChanges },\n rerender,\n detectChanges,\n debug: (node) => console.log(tree(node ? [currentOf(node) ?? node] : fabric.committed)),\n unmount: () => unmount(self),\n };\n}\n\nfunction unmount(target: Mounted): void {\n const at = mounted.indexOf(target);\n if (at === -1) return;\n mounted.splice(at, 1);\n target.applicationRef.destroy();\n}\n\n/**\n * Unmount everything rendered so far, and destroy every app `injectService` created. Registered\n * as an `afterEach` where one is global.\n */\nexport function cleanup(): void {\n for (const target of [...mounted]) unmount(target);\n destroyServiceApps();\n}\n\n/** Queries over whatever was rendered most recently, as Testing Library's `screen` is. */\nexport const screen: BoundQueries & { debug(node?: FakeFabricNode): void } = {\n ...queriesFor(() => latest().fabric.committed, latest),\n debug: (node) => console.log(tree(node ? [currentOf(node) ?? node] : latest().fabric.committed)),\n};\n\n/** Queries scoped to one node and what is under it, in whichever commit is current. */\nexport function within(node: FakeFabricNode): BoundQueries {\n const owner = ownerOf(node);\n return queriesFor(\n () => {\n const current = currentOf(node);\n return current ? [current] : [];\n },\n () => owner,\n );\n}\n\ntype Present = FakeFabricNode | readonly FakeFabricNode[] | null | undefined;\n\n/** Wait for a node, or whatever a callback finds, to leave the committed tree. */\nexport async function waitForElementToBeRemoved(\n target: FakeFabricNode | readonly FakeFabricNode[] | (() => Present),\n options?: WaitForOptions,\n): Promise<void> {\n const present = (): boolean => {\n let found: Present;\n try {\n found = typeof target === 'function' ? target() : target;\n } catch {\n return false;\n }\n const nodes = found ? ([] as FakeFabricNode[]).concat(found) : [];\n return nodes.some((node) => currentOf(node) !== null);\n };\n if (!present()) {\n throw new Error(\n 'waitForElementToBeRemoved needs the node to be there when it starts; it already is not.',\n );\n }\n await waitFor(() => {\n if (present())\n throw new Error(`Still rendered after the timeout.\\n\\n${tree(latest().fabric.committed)}`);\n }, options);\n}\n\n// Testing Library's own convention: clean up after every test when the runner exposes `afterEach`\n// globally (Vitest with `globals: true`, Jest). Otherwise each `render()` stands alone.\nconst globalAfterEach = (globalThis as { afterEach?: (fn: () => void) => void }).afterEach;\nif (typeof globalAfterEach === 'function') globalAfterEach(cleanup);\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `render()`, `screen` and `within()`, shaped like Angular Testing Library's and running on the\n * fake Fabric instead of TestBed and a DOM.\n *\n * A render is a real `mount()` - the same bootstrap an app runs, with the same renderer and engine\n * - onto a `createFakeFabric()`, so what a query reads is what native would have been handed.\n */\nimport {\n ChangeDetectorRef,\n Component,\n // The rule is for templates an app ships, which are checked when they are compiled. The one use\n // here is the host a test's template string is compiled into: see `hostFor`.\n // eslint-disable-next-line no-restricted-syntax\n NO_ERRORS_SCHEMA,\n outputBinding,\n type ApplicationRef,\n type ComponentRef,\n type EnvironmentProviders,\n type Provider,\n type Type,\n} from '@angular/core';\nimport type { Engine, EngineOptions } from '@ng-native/fabric';\nimport { mount } from '@ng-native/platform';\nimport { destroyServiceApps } from './inject-service.ts';\nimport { bindQueries, describeTree, flatten, type BoundQueries } from './queries.ts';\nimport { createFakeFabric, type FakeFabric, type FakeFabricNode } from './test-utils.ts';\nimport { waitFor, type WaitForOptions } from './wait-for.ts';\n\n/**\n * What `render()` takes: Angular Testing Library's options, plus the engine's own.\n *\n * The engine options (`globalStyles`, `conditions`, `tokens` and the rest) are what `mount()`\n * takes on a device, so a test styles a component with the same compiled sheet an app would.\n */\nexport interface RenderOptions<T> extends EngineOptions {\n /** The component's inputs, set before its first change detection, so `required` ones work. */\n inputs?: Record<string, unknown>;\n /** Output handlers, by output name. */\n on?: Record<string, (value: never) => void>;\n /** Environment providers for the app the component is mounted in. */\n providers?: (Provider | EnvironmentProviders)[];\n /** The template form only: what the template may use. */\n imports?: unknown[];\n /** Fields set on the instance: the template form's host, or the component itself. */\n componentProperties?: Partial<T> & Record<string, unknown>;\n}\n\n/**\n * What `render()` resolves to: every query, bound to this render, and the handles to drive it.\n *\n * The queries here stay on this render even after a later one; `screen` moves to the latest.\n */\nexport interface RenderResult<T> extends BoundQueries {\n fabric: FakeFabric;\n componentRef: ComponentRef<T>;\n instance: T;\n /** `ComponentFixture`'s shape, for tests written against it. */\n fixture: {\n componentRef: ComponentRef<T>;\n componentInstance: T;\n detectChanges(): Promise<void>;\n };\n /** Set inputs and properties again, then let the result commit. */\n rerender(changes?: {\n inputs?: Record<string, unknown>;\n componentProperties?: Record<string, unknown>;\n }): Promise<void>;\n /** Run change detection and wait for the commit. */\n detectChanges(): Promise<void>;\n /** Print the committed tree, or one node's subtree. */\n debug(node?: FakeFabricNode): void;\n unmount(): void;\n}\n\ninterface Mounted {\n fabric: FakeFabric;\n engine: Engine;\n applicationRef: ApplicationRef;\n componentRef: ComponentRef<unknown>;\n}\n\nconst mounted: Mounted[] = [];\n/** Which render a node a query returned belongs to, so an event on it reaches the right fake. */\nconst owners = new WeakMap<FakeFabricNode, Mounted>();\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Let zoneless change detection run and commit.\n *\n * One macrotask is enough for a change Angular sees: it schedules its pass on `setTimeout(0)`\n * (racing an animation frame), so a timeout queued after the change runs after the pass. A change\n * the engine sees outside change detection, such as a modal's reported dismissal with no\n * `(dismiss)` listener, commits on the platform's next frame instead, 16ms away in Node, so this\n * waits for that frame while a commit is owed.\n */\nexport async function settle(): Promise<void> {\n await sleep(0);\n for (let frames = 0; frames < 3 && mounted.some((m) => m.engine.pending); frames++) {\n await sleep(16);\n }\n}\n\nfunction latest(): Mounted {\n const current = mounted.at(-1);\n if (!current) throw new Error('Nothing is rendered: call render() first.');\n return current;\n}\n\nexport function ownerOf(node: FakeFabricNode): Mounted {\n return owners.get(node) ?? latest();\n}\n\n/** The node as it is in the most recent commit, found by its tag, or null once it is gone. */\nexport function currentOf(node: FakeFabricNode): FakeFabricNode | null {\n const hit = flatten(ownerOf(node).fabric.committed).find((n) => n.reactTag === node.reactTag);\n if (hit) owners.set(hit, ownerOf(node));\n return hit ?? null;\n}\n\nconst tree = (nodes: readonly FakeFabricNode[]): string => describeTree(nodes) || '(nothing)';\n\nfunction queriesFor(roots: () => readonly FakeFabricNode[], owner: () => Mounted): BoundQueries {\n return bindQueries(\n roots,\n () => tree(roots()),\n (node) => owners.set(node, owner()),\n );\n}\n\n/** The part of a component or directive definition that says what can be bound on its host. */\ninterface Bindable {\n readonly inputs?: Readonly<Record<string, unknown>>;\n readonly hostDirectives?: readonly (HostDirective | (() => HostDirective))[] | null;\n}\ninterface HostDirective {\n readonly inputs?: Readonly<Record<string, string>>;\n}\n\n/**\n * The names a template can bind as inputs on `type`: its own, by alias where one has one, and\n * those its host directives forward, by the name each is forwarded under. Null where the\n * definition cannot be read, and nothing is checked.\n */\nfunction inputNames(type: Type<unknown>): string[] | null {\n const def = (type as { ɵcmp?: Bindable }).ɵcmp;\n if (!def?.inputs) return null;\n const forwarded = (def.hostDirectives ?? []).flatMap((entry) =>\n Object.values((typeof entry === 'function' ? entry() : entry).inputs ?? {}),\n );\n return [...Object.keys(def.inputs), ...forwarded];\n}\n\n/**\n * Throws for a name in `inputs` that is no input of `type`. Angular only logs one, and renders the\n * component with its defaults. Checked before any input is set, so a refused batch changes nothing\n * and a required input left unset by a misspelling is named for what it is.\n */\nfunction refuseUnknownInputs(type: Type<unknown>, inputs: Record<string, unknown> = {}): void {\n const known = inputNames(type);\n if (!known) return;\n const unknown = Object.keys(inputs).filter((name) => !known.includes(name));\n if (!unknown.length) return;\n throw new Error(\n `[angular-native] ${type.name} has no input named ` +\n `${unknown.map((name) => `'${name}'`).join(', ')}. ` +\n (known.length ? `Its inputs are ${known.join(', ')}.` : 'It has no inputs.'),\n );\n}\n\n/**\n * The template form, compiled just in time.\n *\n * ponytail: this needs `@angular/compiler` at runtime, the one place anything here does - the\n * class form is AOT like the app. An AOT compile of the string would mean evaluating generated\n * code that imports the caller's own classes by name; do that if JIT ever disagrees with AOT.\n */\n/** Wrappers built so far: each takes the next as a host attribute, so no two share an ID. */\nlet wrappers = 0;\n\nasync function hostFor<T>(template: string, options: RenderOptions<T>): Promise<Type<unknown>> {\n await import('@angular/compiler' as string);\n const properties = options.componentProperties ?? {};\n class WrapperComponent {\n constructor() {\n Object.assign(this, properties);\n }\n }\n return Component({\n selector: 'ng-native-wrapper',\n // Angular derives a component's ID from its metadata, not its template's text, so wrappers\n // alike in shape collided (NG0912) without it.\n host: { 'data-wrapper': String(++wrappers) },\n template,\n imports: (options.imports ?? []) as Type<unknown>[],\n // A component's own host bindings are checked against the schemas of the template it is used\n // in, and only where that template was compiled just in time: the app's, compiled ahead, has\n // no such check. Without this every native prop a component binds on its host logs NG0303.\n // eslint-disable-next-line no-restricted-syntax -- as above\n schemas: [NO_ERRORS_SCHEMA],\n })(WrapperComponent);\n}\n\n/**\n * Mount a component, or a template string, onto a fresh fake Fabric and wait for the first commit.\n *\n * The same `mount()` an app boots with, so what the queries read is exactly what native would\n * have been sent. A string is compiled as a host component's template, with `imports` and\n * `componentProperties` for what it uses; a class is rendered as-is, with `inputs` set before its\n * first change detection so `input.required()` works.\n */\nexport async function render<T>(\n component: Type<T> | string,\n options: RenderOptions<T> = {},\n): Promise<RenderResult<T>> {\n const { inputs, on, providers, imports: _, componentProperties, ...engine } = options;\n const isTemplate = typeof component === 'string';\n const type = isTemplate ? await hostFor(component, options) : component;\n const fabric = createFakeFabric();\n if (!isTemplate) refuseUnknownInputs(type, inputs);\n const app = mount(1, type, fabric, {\n ...engine,\n providers,\n inputs: isTemplate ? {} : inputs,\n // As a template would bind them, so an output a host directive forwards is heard, by the name\n // it is forwarded under, and a name that is no output is refused by Angular, by name.\n bindings: Object.entries(on ?? {}).map(([name, handler]) =>\n outputBinding(name, handler as (value: unknown) => void),\n ),\n });\n const self: Mounted = {\n fabric,\n engine: app.engine,\n applicationRef: app.applicationRef,\n componentRef: app.componentRef,\n };\n mounted.push(self);\n\n const componentRef = app.componentRef as ComponentRef<T>;\n const instance = componentRef.instance as T & Record<string, unknown>;\n\n const detectChanges = async (): Promise<void> => {\n // The component's own view, not the host's: `componentRef.changeDetectorRef` marks the host\n // and upwards, which leaves an OnPush component with a changed field exactly as it was.\n componentRef.injector.get(ChangeDetectorRef).markForCheck();\n app.applicationRef.tick();\n await settle();\n };\n const rerender: RenderResult<T>['rerender'] = async (changes = {}) => {\n refuseUnknownInputs(type, changes.inputs);\n for (const [name, value] of Object.entries(changes.inputs ?? {})) {\n componentRef.setInput(name, value);\n }\n Object.assign(instance, changes.componentProperties);\n await detectChanges();\n };\n\n if (!isTemplate && componentProperties) await rerender({ componentProperties });\n else await settle();\n\n return {\n ...queriesFor(\n () => fabric.committed,\n () => self,\n ),\n fabric,\n componentRef,\n instance,\n fixture: { componentRef, componentInstance: instance, detectChanges },\n rerender,\n detectChanges,\n debug: (node) => console.log(tree(node ? [currentOf(node) ?? node] : fabric.committed)),\n unmount: () => unmount(self),\n };\n}\n\nfunction unmount(target: Mounted): void {\n const at = mounted.indexOf(target);\n if (at === -1) return;\n mounted.splice(at, 1);\n target.applicationRef.destroy();\n}\n\n/**\n * Unmount everything rendered so far, and destroy every app `injectService` created. Registered\n * as an `afterEach` where one is global.\n */\nexport function cleanup(): void {\n for (const target of [...mounted]) unmount(target);\n destroyServiceApps();\n}\n\n/** Queries over whatever was rendered most recently, as Testing Library's `screen` is. */\nexport const screen: BoundQueries & { debug(node?: FakeFabricNode): void } = {\n ...queriesFor(() => latest().fabric.committed, latest),\n debug: (node) => console.log(tree(node ? [currentOf(node) ?? node] : latest().fabric.committed)),\n};\n\n/** Queries scoped to one node and what is under it, in whichever commit is current. */\nexport function within(node: FakeFabricNode): BoundQueries {\n const owner = ownerOf(node);\n return queriesFor(\n () => {\n const current = currentOf(node);\n return current ? [current] : [];\n },\n () => owner,\n );\n}\n\ntype Present = FakeFabricNode | readonly FakeFabricNode[] | null | undefined;\n\n/** Wait for a node, or whatever a callback finds, to leave the committed tree. */\nexport async function waitForElementToBeRemoved(\n target: FakeFabricNode | readonly FakeFabricNode[] | (() => Present),\n options?: WaitForOptions,\n): Promise<void> {\n const present = (): boolean => {\n let found: Present;\n try {\n found = typeof target === 'function' ? target() : target;\n } catch {\n return false;\n }\n const nodes = found ? ([] as FakeFabricNode[]).concat(found) : [];\n return nodes.some((node) => currentOf(node) !== null);\n };\n if (!present()) {\n throw new Error(\n 'waitForElementToBeRemoved needs the node to be there when it starts; it already is not.',\n );\n }\n await waitFor(() => {\n if (present())\n throw new Error(`Still rendered after the timeout.\\n\\n${tree(latest().fabric.committed)}`);\n }, options);\n}\n\n// Testing Library's own convention: clean up after every test when the runner exposes `afterEach`\n// globally (Vitest with `globals: true`, Jest). Otherwise each `render()` stands alone.\nconst globalAfterEach = (globalThis as { afterEach?: (fn: () => void) => void }).afterEach;\nif (typeof globalAfterEach === 'function') globalAfterEach(cleanup);\n"]}
@@ -10,6 +10,12 @@ export interface FakeFabricNode {
10
10
  props: Record<string, unknown>;
11
11
  children: FakeFabricNode[];
12
12
  instanceHandle: unknown;
13
+ /**
14
+ * The node this one is under, as of the last query: null at the top. Set by the queries as they
15
+ * walk the committed tree. A commit shares the nodes it did not change with the one before, so
16
+ * on a node held across commits a later query may set it again, to the parent in that commit.
17
+ */
18
+ readonly parent?: FakeFabricNode | null;
13
19
  }
14
20
  export interface FakeFabric extends FabricUIManager {
15
21
  /** Root child set from the most recent `completeRoot`. */
@@ -125,10 +125,12 @@ export function createFakeFabric() {
125
125
  ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
126
126
  : value;
127
127
  const walk = (nodes, depth, withProps) => nodes.flatMap((n) => {
128
- const text = typeof n.props['text'] === 'string' ? ` "${n.props['text']}"` : '';
129
- const props = withProps && Object.keys(n.props).length && !text
130
- ? ' ' + JSON.stringify(n.props, sorted)
131
- : '';
128
+ // The text is printed for itself, so the payload beside it is every other prop: a text
129
+ // field's placeholder and keyboard, where a `RawText` has nothing more to say.
130
+ const { text: said, ...rest } = n.props;
131
+ const text = typeof said === 'string' ? ` "${said}"` : '';
132
+ const shown = text ? rest : n.props;
133
+ const props = withProps && Object.keys(shown).length ? ' ' + JSON.stringify(shown, sorted) : '';
132
134
  return [
133
135
  ' '.repeat(depth) + n.viewName + text + props,
134
136
  ...walk(n.children, depth + 1, withProps),