@ng-native/testing 0.2.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.
- package/dist/compile-css.d.ts +17 -0
- package/dist/compile-css.js +14 -0
- package/dist/compile-css.js.map +1 -0
- package/dist/events.d.ts +9 -0
- package/dist/events.js +14 -0
- package/dist/events.js.map +1 -1
- package/dist/gesture-handler.d.ts +3 -39
- package/dist/gesture-handler.js +2 -41
- package/dist/gesture-handler.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/inject-service.d.ts +32 -0
- package/dist/inject-service.js +70 -0
- package/dist/inject-service.js.map +1 -0
- package/dist/memory-database.d.ts +23 -0
- package/dist/memory-database.js +60 -0
- package/dist/memory-database.js.map +1 -0
- package/dist/queries.d.ts +5 -0
- package/dist/queries.js +21 -3
- package/dist/queries.js.map +1 -1
- package/dist/reanimated-library.d.ts +5 -59
- package/dist/reanimated-library.js +5 -92
- package/dist/reanimated-library.js.map +1 -1
- package/dist/reanimated.d.ts +3 -0
- package/dist/reanimated.js +24 -7
- package/dist/reanimated.js.map +1 -1
- package/dist/render.d.ts +12 -5
- package/dist/render.js +68 -8
- package/dist/render.js.map +1 -1
- package/dist/test-utils.d.ts +6 -0
- package/dist/test-utils.js +81 -5
- package/dist/test-utils.js.map +1 -1
- package/dist/worklets-library.d.ts +2 -10
- package/dist/worklets-library.js +2 -22
- package/dist/worklets-library.js.map +1 -1
- package/package.json +9 -8
- package/runner/compile.mjs +10 -1
- package/runner/register.mjs +14 -0
- package/runner/setup.mjs +13 -0
- package/runner/vitest.d.mts +6 -0
- package/runner/vitest.mjs +52 -16
package/dist/render.js.map
CHANGED
|
@@ -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 { EngineOptions } from '@ng-native/fabric';\nimport { mount } from '@ng-native/platform';\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 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\n/**\n * Let zoneless change detection run and commit.\n *\n * One macrotask is enough: Angular schedules its pass on `setTimeout(0)` (racing an animation\n * frame) when something changes, so a timeout queued after the change runs after the pass.\n */\nexport const settle = (): Promise<void> => new Promise((resolve) => setTimeout(resolve, 0));\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 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/** Unmount everything rendered so far. Registered as an `afterEach` where one is global. */\nexport function cleanup(): void {\n for (const target of [...mounted]) unmount(target);\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"]}
|
package/dist/test-utils.d.ts
CHANGED
|
@@ -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`. */
|
package/dist/test-utils.js
CHANGED
|
@@ -41,8 +41,74 @@ export function createFakeFabric() {
|
|
|
41
41
|
}
|
|
42
42
|
parents.set(child.reactTag, parent);
|
|
43
43
|
};
|
|
44
|
+
/*
|
|
45
|
+
* The views that have been unmounted, by tag. Fabric drops a view's event target once no
|
|
46
|
+
* committed tree holds it (`EventEmitter::setEnabled`) and never makes a new one, so a view
|
|
47
|
+
* committed again after leaving the tree dispatches every event with a null instance handle, as
|
|
48
|
+
* `emit` does here. A view that comes back has to be created again, as React does.
|
|
49
|
+
*/
|
|
50
|
+
const unmounted = new Set();
|
|
51
|
+
const unmount = (n) => {
|
|
52
|
+
unmounted.add(n.reactTag);
|
|
53
|
+
for (const child of n.children)
|
|
54
|
+
unmount(child);
|
|
55
|
+
};
|
|
56
|
+
// Walks only what changed, as `updateMountedFlag` does: a subtree committed as it was is skipped.
|
|
57
|
+
const diff = (before, after) => {
|
|
58
|
+
if (before === after)
|
|
59
|
+
return;
|
|
60
|
+
const next = new Map(after.map((n) => [n.reactTag, n]));
|
|
61
|
+
for (const was of before) {
|
|
62
|
+
const now = next.get(was.reactTag);
|
|
63
|
+
if (!now)
|
|
64
|
+
unmount(was);
|
|
65
|
+
else if (now !== was)
|
|
66
|
+
diff(was.children, now.children);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
/*
|
|
70
|
+
* iOS animates a presented modal out when it is committed with `visible: false`, then reports
|
|
71
|
+
* `topDismiss` once, and React Native's Modal.js keeps it in the tree until then. The fake
|
|
72
|
+
* reports it as soon as that commit is done, and lets one dismissal through per close, so a test
|
|
73
|
+
* that also sends it by hand does not see `(dismiss)` twice.
|
|
74
|
+
*/
|
|
75
|
+
const dismissing = new Set();
|
|
76
|
+
const dismissed = new Set();
|
|
77
|
+
const dismissAfterCommit = () => {
|
|
78
|
+
if (dismissing.size === 0 || !handler)
|
|
79
|
+
return;
|
|
80
|
+
const tags = [...dismissing];
|
|
81
|
+
dismissing.clear();
|
|
82
|
+
queueMicrotask(() => {
|
|
83
|
+
for (const tag of tags) {
|
|
84
|
+
const host = findByTag(committed, tag);
|
|
85
|
+
if (host && host.props['visible'] === false)
|
|
86
|
+
fake.emit(host, 'topDismiss');
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
const findByTag = (nodes, tag) => {
|
|
91
|
+
for (const n of nodes) {
|
|
92
|
+
const hit = n.reactTag === tag ? n : findByTag(n.children, tag);
|
|
93
|
+
if (hit)
|
|
94
|
+
return hit;
|
|
95
|
+
}
|
|
96
|
+
return undefined;
|
|
97
|
+
};
|
|
98
|
+
/** A modal host committed as closing, or that left the tree closing. */
|
|
99
|
+
const closing = (target) => (findByTag(committed, target.reactTag) ?? target).props['visible'] === false;
|
|
100
|
+
/** A modal host's props are changing: a close owes one dismissal, and a show resets it. */
|
|
101
|
+
const noteModal = (source, props) => {
|
|
102
|
+
if (source.viewName !== 'ModalHostView' || !props || !('visible' in props))
|
|
103
|
+
return;
|
|
104
|
+
if (props['visible'] !== false)
|
|
105
|
+
dismissed.delete(source.reactTag);
|
|
106
|
+
else if (source.props['visible'] !== false)
|
|
107
|
+
dismissing.add(source.reactTag);
|
|
108
|
+
};
|
|
44
109
|
const clone = (n, props, keepChildren = false) => {
|
|
45
110
|
const source = n;
|
|
111
|
+
noteModal(source, props);
|
|
46
112
|
return withHandle({
|
|
47
113
|
...source,
|
|
48
114
|
handle: ++handle,
|
|
@@ -59,10 +125,12 @@ export function createFakeFabric() {
|
|
|
59
125
|
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
60
126
|
: value;
|
|
61
127
|
const walk = (nodes, depth, withProps) => nodes.flatMap((n) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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) : '';
|
|
66
134
|
return [
|
|
67
135
|
' '.repeat(depth) + n.viewName + text + props,
|
|
68
136
|
...walk(n.children, depth + 1, withProps),
|
|
@@ -100,7 +168,9 @@ export function createFakeFabric() {
|
|
|
100
168
|
},
|
|
101
169
|
completeRoot(_rootTag, set) {
|
|
102
170
|
calls.completeRoot++;
|
|
171
|
+
diff(committed, set);
|
|
103
172
|
committed = set;
|
|
173
|
+
dismissAfterCommit();
|
|
104
174
|
},
|
|
105
175
|
registerEventHandler(fn) {
|
|
106
176
|
handler = fn;
|
|
@@ -153,7 +223,13 @@ export function createFakeFabric() {
|
|
|
153
223
|
emit(target, type, nativeEvent = {}) {
|
|
154
224
|
if (!handler)
|
|
155
225
|
throw new Error('no Fabric event handler registered');
|
|
156
|
-
|
|
226
|
+
if (type === 'topDismiss' && target.viewName === 'ModalHostView') {
|
|
227
|
+
if (dismissed.has(target.reactTag))
|
|
228
|
+
return;
|
|
229
|
+
if (closing(target))
|
|
230
|
+
dismissed.add(target.reactTag);
|
|
231
|
+
}
|
|
232
|
+
handler(unmounted.has(target.reactTag) ? null : target.instanceHandle, type, nativeEvent);
|
|
157
233
|
},
|
|
158
234
|
reset() {
|
|
159
235
|
calls.createNode = 0;
|
package/dist/test-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-utils.js","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Fake `global.nativeFabricUIManager`. Records every host call so tests can assert on\n * the committed tree without a simulator. This is what lets the whole seam be tested in Node.\n */\nimport type { FabricNode, FabricNodeSet, FabricUIManager } from '@ng-native/fabric';\n\nexport interface FakeFabricNode {\n handle: number;\n reactTag: number;\n viewName: string;\n props: Record<string, unknown>;\n children: FakeFabricNode[];\n instanceHandle: unknown;\n}\n\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\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 const clone = (n: FabricNode, props?: object, keepChildren = false): FabricNode => {\n const source = n as unknown as FakeFabricNode;\n return withHandle(\n {\n ...source,\n handle: ++handle,\n // Fabric merges raw props onto the source props rather than replacing them.\n props: props ? { ...source.props, ...props } : 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: { ...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 const text = typeof n.props['text'] === 'string' ? ` \"${n.props['text']}\"` : '';\n const props =\n withProps && Object.keys(n.props).length && !text\n ? ' ' + JSON.stringify(n.props, sorted)\n : '';\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 committed = set as FakeFabricNode[];\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 handler(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\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 noteModal(source, props as Record<string, unknown> | undefined);\n return withHandle(\n {\n ...source,\n handle: ++handle,\n // Fabric merges raw props onto the source props rather than replacing them.\n props: props ? { ...source.props, ...props } : 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: { ...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,10 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* runtime in a test, so scheduling a function on either runtime calls it at once.
|
|
4
|
-
*/
|
|
5
|
-
export declare function scheduleOnRN<A extends unknown[]>(fn: (...args: A) => unknown, ...args: A): void;
|
|
6
|
-
export declare function scheduleOnUI<A extends unknown[]>(fn: (...args: A) => unknown, ...args: A): void;
|
|
7
|
-
export declare function runOnUISync<A extends unknown[], R>(fn: (...args: A) => R, ...args: A): R;
|
|
8
|
-
export declare function runOnJS<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
|
|
9
|
-
export declare function runOnUI<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => R;
|
|
10
|
-
export declare function isWorkletFunction(_value: unknown): boolean;
|
|
1
|
+
/** What a test gets for `react-native-worklets`, whose source Node cannot load. */
|
|
2
|
+
export * from '@ng-native/components/stand-ins/worklets';
|
package/dist/worklets-library.js
CHANGED
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* runtime in a test, so scheduling a function on either runtime calls it at once.
|
|
4
|
-
*/
|
|
5
|
-
export function scheduleOnRN(fn, ...args) {
|
|
6
|
-
fn(...args);
|
|
7
|
-
}
|
|
8
|
-
export function scheduleOnUI(fn, ...args) {
|
|
9
|
-
fn(...args);
|
|
10
|
-
}
|
|
11
|
-
export function runOnUISync(fn, ...args) {
|
|
12
|
-
return fn(...args);
|
|
13
|
-
}
|
|
14
|
-
export function runOnJS(fn) {
|
|
15
|
-
return fn;
|
|
16
|
-
}
|
|
17
|
-
export function runOnUI(fn) {
|
|
18
|
-
return fn;
|
|
19
|
-
}
|
|
20
|
-
export function isWorkletFunction(_value) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
1
|
+
/** What a test gets for `react-native-worklets`, whose source Node cannot load. */
|
|
2
|
+
export * from '@ng-native/components/stand-ins/worklets';
|
|
23
3
|
//# sourceMappingURL=worklets-library.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worklets-library.js","sourceRoot":"","sources":["../src/worklets-library.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"worklets-library.js","sourceRoot":"","sources":["../src/worklets-library.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,cAAc,0CAA0C,CAAC","sourcesContent":["/** What a test gets for `react-native-worklets`, whose source Node cannot load. */\nexport * from '@ng-native/components/stand-ins/worklets';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ng-native/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Test Angular Native components in Node with no simulator: a fake Fabric, Testing Library queries and events, and Vitest and node:test integration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -34,15 +34,16 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@ng-native/
|
|
38
|
-
"@ng-native/
|
|
39
|
-
"@ng-native/platform": "0.
|
|
37
|
+
"@ng-native/fabric": "0.4.0",
|
|
38
|
+
"@ng-native/metro": "0.4.0",
|
|
39
|
+
"@ng-native/platform": "0.4.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@angular/compiler": "^22.0.0",
|
|
43
43
|
"@angular/core": "^22.0.0",
|
|
44
44
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
|
45
|
-
"vitest": "^4.0.8 || ^5.0.0"
|
|
45
|
+
"vitest": "^4.0.8 || ^5.0.0",
|
|
46
|
+
"@ng-native/components": "0.4.0"
|
|
46
47
|
},
|
|
47
48
|
"peerDependenciesMeta": {
|
|
48
49
|
"@angular/compiler": {
|
|
@@ -61,10 +62,10 @@
|
|
|
61
62
|
"@angular/core": "22.2.0",
|
|
62
63
|
"@angular/forms": "22.2.0",
|
|
63
64
|
"@angular/router": "22.2.0",
|
|
65
|
+
"@ng-native/components": "0.4.0",
|
|
66
|
+
"@ng-native/router": "0.4.0",
|
|
64
67
|
"vite": "^8.3.1",
|
|
65
|
-
"vitest": "^5.0.2"
|
|
66
|
-
"@ng-native/router": "0.2.0",
|
|
67
|
-
"@ng-native/components": "0.2.0"
|
|
68
|
+
"vitest": "^5.0.2"
|
|
68
69
|
},
|
|
69
70
|
"nx": {
|
|
70
71
|
"tags": [
|
package/runner/compile.mjs
CHANGED
|
@@ -63,7 +63,7 @@ export function compileAngular(source, file, options = {}) {
|
|
|
63
63
|
: result.code;
|
|
64
64
|
const directory = path.dirname(file);
|
|
65
65
|
return {
|
|
66
|
-
code,
|
|
66
|
+
code: stubFonts(code),
|
|
67
67
|
dependencies: result.dependencies.map((dependency) => path.resolve(directory, dependency)),
|
|
68
68
|
map: result.map,
|
|
69
69
|
};
|
|
@@ -78,5 +78,14 @@ export function compileAngular(source, file, options = {}) {
|
|
|
78
78
|
const ASSET =
|
|
79
79
|
/\brequire\(\s*(['"])([^'"]+\.(?:png|jpe?g|gif|webp|bmp|svg|ttf|otf|mp3|mp4|wav|m4a|mov|json5?))\1\s*\)/g;
|
|
80
80
|
/** @param {string} code */
|
|
81
|
+
/**
|
|
82
|
+
* The `require` the compiler writes for the file a stylesheet's `@font-face` names, which is in no
|
|
83
|
+
* source a caller could have stubbed and has no `require` to run with either. Matched by where it
|
|
84
|
+
* sits in a compiled sheet, so whatever the file is called, and nothing a package wrote itself.
|
|
85
|
+
*/
|
|
86
|
+
const FONT = /"source":require\(("(?:[^"\\]|\\.)*")\)/g;
|
|
87
|
+
/** @param {string} code */
|
|
88
|
+
const stubFonts = (code) => code.replace(FONT, (_, path) => `"source":({ testUri: ${path} })`);
|
|
89
|
+
|
|
81
90
|
export const stubAssets = (code) =>
|
|
82
91
|
code.replace(ASSET, (_, _quote, path) => `({ testUri: ${JSON.stringify(path)} })`);
|
package/runner/register.mjs
CHANGED
|
@@ -15,3 +15,17 @@ createRequire(import.meta.url)('@ng-native/metro/polyfills/animation-globals.js'
|
|
|
15
15
|
globalThis.__DEV__ ??= true;
|
|
16
16
|
|
|
17
17
|
register('./loader.mjs', import.meta.url);
|
|
18
|
+
|
|
19
|
+
// Tells `injectService` which test is running. See `setup.mjs`. Only under `node --test`: a hook
|
|
20
|
+
// starts the test runner, which a script run through this file is not for.
|
|
21
|
+
if (process.env.NODE_TEST_CONTEXT || process.execArgv.some((arg) => arg.startsWith('--test'))) {
|
|
22
|
+
const { afterEach, beforeEach } = await import('node:test');
|
|
23
|
+
const tests = (globalThis[Symbol.for('ng-native.testing.tests')] ??= { finished: 0, running: 0 });
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
tests.running++;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
tests.running--;
|
|
29
|
+
tests.finished++;
|
|
30
|
+
});
|
|
31
|
+
}
|
package/runner/setup.mjs
CHANGED
|
@@ -4,7 +4,20 @@
|
|
|
4
4
|
* matters and why `ngDevMode` is left alone.
|
|
5
5
|
*/
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
+
import { afterEach, beforeEach } from 'vitest';
|
|
7
8
|
|
|
8
9
|
createRequire(import.meta.url)('@ng-native/metro/polyfills/animation-globals.js');
|
|
9
10
|
// Metro's prelude defines this in every bundle, and app code reads it bare.
|
|
10
11
|
globalThis.__DEV__ ??= true;
|
|
12
|
+
|
|
13
|
+
// Tells `injectService` which test is running, so the app the calls in a test share is never
|
|
14
|
+
// another test's: how many tests have finished, and how many are running now. On the global, since
|
|
15
|
+
// this file and the package a test imports need not be one module instance.
|
|
16
|
+
const tests = (globalThis[Symbol.for('ng-native.testing.tests')] ??= { finished: 0, running: 0 });
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
tests.running++;
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
tests.running--;
|
|
22
|
+
tests.finished++;
|
|
23
|
+
});
|
package/runner/vitest.d.mts
CHANGED
|
@@ -10,3 +10,9 @@ export interface NgNativeOptions {
|
|
|
10
10
|
|
|
11
11
|
/** Compile Angular for Vitest: AOT for decorated sources, the linker for partial-compiled packages. */
|
|
12
12
|
export declare function ngNative(options?: NgNativeOptions): Plugin;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Where a stand-in module is: the source in the repository, the compiled file in the published
|
|
16
|
+
* package. `from` is the runner's own location.
|
|
17
|
+
*/
|
|
18
|
+
export declare function standIn(name: string, from?: string | URL): string;
|
package/runner/vitest.mjs
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* The environment is Vitest's default, `node`. There is no DOM to emulate: the renderer talks to
|
|
21
21
|
* a fake Fabric, and jsdom would only give Angular a `document` to misread.
|
|
22
22
|
*/
|
|
23
|
-
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
24
24
|
import path from 'node:path';
|
|
25
25
|
import { fileURLToPath } from 'node:url';
|
|
26
26
|
import { defaultClientConditions, defaultServerConditions } from 'vite';
|
|
@@ -35,19 +35,27 @@ const SETUP = fileURLToPath(new URL('./setup.mjs', import.meta.url));
|
|
|
35
35
|
* an app imports for `withTiming` and `scheduleOnRN`. See each stand-in for what it does and does
|
|
36
36
|
* not do.
|
|
37
37
|
*/
|
|
38
|
-
const STAND_INS =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
),
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
const STAND_INS = Object.fromEntries(
|
|
39
|
+
Object.entries({
|
|
40
|
+
'@ng-native/components/gestures': 'gestures',
|
|
41
|
+
'react-native-gesture-handler': 'gesture-handler',
|
|
42
|
+
'@ng-native/components/reanimated': 'reanimated',
|
|
43
|
+
'react-native-reanimated': 'reanimated-library',
|
|
44
|
+
'react-native-worklets': 'worklets-library',
|
|
45
|
+
}).map(([source, name]) => [source, standIn(name)]),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Where a stand-in is: the source beside this file in the repository, and the compiled file in
|
|
50
|
+
* the published package, which ships `dist` and no `src`.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} name
|
|
53
|
+
* @param {string | URL} [from] the runner's own directory
|
|
54
|
+
*/
|
|
55
|
+
export function standIn(name, from = import.meta.url) {
|
|
56
|
+
const source = fileURLToPath(new URL(`../src/${name}.ts`, from));
|
|
57
|
+
return existsSync(source) ? source : fileURLToPath(new URL(`../dist/${name}.js`, from));
|
|
58
|
+
}
|
|
51
59
|
|
|
52
60
|
/**
|
|
53
61
|
* ponytail: inlining is by package name, so a third-party Angular library other than these has
|
|
@@ -91,13 +99,14 @@ function withConditions(conditions) {
|
|
|
91
99
|
* a `require` regardless. A module that probes for one then requires React Native's Flow source.
|
|
92
100
|
* A `var` of the same name shadows the one Vitest passes in, and is harmless where there is none.
|
|
93
101
|
* On the first line, so every other line keeps its number. ES modules only: a CommonJS file needs
|
|
94
|
-
* the real thing.
|
|
102
|
+
* the real thing. A `.js` file is one when its package says `"type": "module"`, as every published
|
|
103
|
+
* `@ng-native/*` package does.
|
|
95
104
|
*
|
|
96
105
|
* @param {string} code
|
|
97
106
|
* @param {string} file
|
|
98
107
|
*/
|
|
99
108
|
function hideRequire(code, file) {
|
|
100
|
-
if (
|
|
109
|
+
if (!/\brequire\b/.test(code) || !isModule(file)) return code;
|
|
101
110
|
// A module that makes its own, with `createRequire`, already sees the one it means.
|
|
102
111
|
if (/\b(?:const|let|var|function|class)\s+require\b/.test(code)) return code;
|
|
103
112
|
return `var require = undefined; ${code}`;
|
|
@@ -112,6 +121,33 @@ function manifest(dir) {
|
|
|
112
121
|
}
|
|
113
122
|
}
|
|
114
123
|
|
|
124
|
+
/** @type {Map<string, boolean>} */
|
|
125
|
+
const moduleDirs = new Map();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether `.js` files in a directory are ES modules: the `type` of the nearest `package.json`, as
|
|
129
|
+
* Node decides it. A package's dist can hold a `package.json` of its own with only a `type` in it.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} dir
|
|
132
|
+
* @returns {boolean}
|
|
133
|
+
*/
|
|
134
|
+
function jsIsModule(dir) {
|
|
135
|
+
let found = moduleDirs.get(dir);
|
|
136
|
+
if (found === undefined) {
|
|
137
|
+
const own = manifest(dir);
|
|
138
|
+
const parent = path.dirname(dir);
|
|
139
|
+
found = own ? own.type === 'module' : parent !== dir && jsIsModule(parent);
|
|
140
|
+
moduleDirs.set(dir, found);
|
|
141
|
+
}
|
|
142
|
+
return found;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @param {string} file */
|
|
146
|
+
function isModule(file) {
|
|
147
|
+
if (/\.(ts|mts|mjs)$/.test(file)) return true;
|
|
148
|
+
return file.endsWith('.js') && jsIsModule(path.dirname(file));
|
|
149
|
+
}
|
|
150
|
+
|
|
115
151
|
/**
|
|
116
152
|
* The directory of the `name` package a resolved file is in, or nothing: installed, or a linked
|
|
117
153
|
* workspace package, whose real path has no `node_modules` in it.
|