@ng-native/testing 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +67 -1
- package/dist/events.d.ts +45 -0
- package/dist/events.js +144 -0
- package/dist/events.js.map +1 -0
- package/dist/gesture-handler.d.ts +39 -0
- package/dist/gesture-handler.js +42 -0
- package/dist/gesture-handler.js.map +1 -0
- package/dist/gesture-of.d.ts +8 -0
- package/dist/gesture-of.js +29 -0
- package/dist/gesture-of.js.map +1 -0
- package/dist/gestures.d.ts +16 -0
- package/dist/gestures.js +46 -0
- package/dist/gestures.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/queries.d.ts +73 -0
- package/dist/queries.js +148 -0
- package/dist/queries.js.map +1 -0
- package/dist/reanimated-library.d.ts +62 -0
- package/dist/reanimated-library.js +95 -0
- package/dist/reanimated-library.js.map +1 -0
- package/dist/reanimated.d.ts +30 -0
- package/dist/reanimated.js +80 -0
- package/dist/reanimated.js.map +1 -0
- package/dist/render.d.ts +92 -0
- package/dist/render.js +182 -0
- package/dist/render.js.map +1 -0
- package/dist/test-utils.d.ts +66 -0
- package/dist/test-utils.js +162 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/wait-for.d.ts +25 -0
- package/dist/wait-for.js +21 -0
- package/dist/wait-for.js.map +1 -0
- package/dist/worklets-library.d.ts +10 -0
- package/dist/worklets-library.js +23 -0
- package/dist/worklets-library.js.map +1 -0
- package/package.json +86 -3
- package/runner/compile.mjs +82 -0
- package/runner/loader.mjs +73 -0
- package/runner/register.mjs +17 -0
- package/runner/setup.mjs +10 -0
- package/runner/vitest.d.mts +12 -0
- package/runner/vitest.mjs +142 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ashley Hunter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,69 @@
|
|
|
1
1
|
# @ng-native/testing
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Runs an Angular Native component test in plain Node, in milliseconds, with no simulator and no
|
|
4
|
+
device: Angular Testing Library's `render()` and `screen`, and React Native Testing Library's
|
|
5
|
+
`fireEvent` and `userEvent`, over a fake of the native side.
|
|
6
|
+
|
|
7
|
+
Alpha: APIs may change before 1.0.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
Most apps start from `npx create-expo-app@latest my-app --template @ng-native/template`, which
|
|
12
|
+
already has this set up. Otherwise:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install --save-dev @ng-native/testing vitest
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Example
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
// vitest.config.mts
|
|
22
|
+
import { ngNative } from '@ng-native/testing/vitest';
|
|
23
|
+
import { defineConfig } from 'vitest/config';
|
|
24
|
+
|
|
25
|
+
export default defineConfig({
|
|
26
|
+
plugins: [ngNative()],
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// app.test.ts
|
|
32
|
+
import { render, screen, userEvent } from '@ng-native/testing';
|
|
33
|
+
import { expect, test } from 'vitest';
|
|
34
|
+
import { App } from './app.ts';
|
|
35
|
+
|
|
36
|
+
test('counts taps', async () => {
|
|
37
|
+
await render(App);
|
|
38
|
+
|
|
39
|
+
await userEvent.setup().press(screen.getByRole('button', { name: 'Tapped 0 times' }));
|
|
40
|
+
|
|
41
|
+
expect(screen.getByText('Tapped 1 times')).toBeTruthy();
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The renderer, styling engine, responder system, and every component and primitive under test are
|
|
46
|
+
the real ones - only `nativeFabricUIManager` is faked, so a query reads what native would actually
|
|
47
|
+
have been sent.
|
|
48
|
+
|
|
49
|
+
## What's in the package
|
|
50
|
+
|
|
51
|
+
- `.` - `render()`, `screen`, `fireEvent`, `userEvent`, `cleanup()`, `createFakeFabric()`.
|
|
52
|
+
- `./vitest` - `ngNative()`, the Vitest plugin that compiles Angular for tests the way Metro
|
|
53
|
+
compiles it for the app.
|
|
54
|
+
- `./register` - a Node module hook for `node --import @ng-native/testing/register --test`, for
|
|
55
|
+
projects that would rather use `node:test` than Vitest.
|
|
56
|
+
- `./loader` - the underlying loader `./register` installs.
|
|
57
|
+
|
|
58
|
+
## Docs
|
|
59
|
+
|
|
60
|
+
- [Testing](https://ng-native.com/packages/testing)
|
|
61
|
+
- [Setup](https://ng-native.com/packages/testing/setup),
|
|
62
|
+
[writing a test](https://ng-native.com/packages/testing/writing-a-test) and the
|
|
63
|
+
[API reference](https://ng-native.com/packages/testing/api)
|
|
64
|
+
- [Root README](https://github.com/ng-native/ng-native/blob/main/README.md) and
|
|
65
|
+
[ARCHITECTURE.md](https://github.com/ng-native/ng-native/blob/main/ARCHITECTURE.md)
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FakeFabricNode } from './test-utils.ts';
|
|
2
|
+
/** A payload as a handler would see it (`{ nativeEvent }`), or the native event itself. */
|
|
3
|
+
export type EventPayload = Record<string, unknown>;
|
|
4
|
+
declare function press(node: FakeFabricNode): Promise<void>;
|
|
5
|
+
declare const named: {
|
|
6
|
+
press: typeof press;
|
|
7
|
+
changeText(node: FakeFabricNode, text: string): Promise<void>;
|
|
8
|
+
scroll(node: FakeFabricNode, payload?: EventPayload): Promise<void>;
|
|
9
|
+
focus(node: FakeFabricNode): Promise<void>;
|
|
10
|
+
blur(node: FakeFabricNode): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
type FireEvent = ((node: FakeFabricNode, name: string, payload?: unknown) => Promise<void>) & typeof named;
|
|
13
|
+
/**
|
|
14
|
+
* `fireEvent(node, 'press')`, `fireEvent(node, 'changeText', 'Ada')`, or any other event by name -
|
|
15
|
+
* `fireEvent(node, 'submitEditing', { text })` emits `topSubmitEditing`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const fireEvent: FireEvent;
|
|
18
|
+
/** Options for `userEvent.type`, named as React Native Testing Library names them. */
|
|
19
|
+
export interface TypeOptions {
|
|
20
|
+
/** Leave the field focused afterwards. By default typing ends with the field blurring. */
|
|
21
|
+
skipBlur?: boolean;
|
|
22
|
+
/** Press return at the end: `topSubmitEditing`, before any blur. */
|
|
23
|
+
submitEditing?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Interactions as a person performs them: several events, a task apart, in the order the platform
|
|
27
|
+
* sends them. Slower than `fireEvent`, and closer to what a device does.
|
|
28
|
+
*/
|
|
29
|
+
export interface UserEvent {
|
|
30
|
+
/** A finger down and up, a task apart, through the responder. */
|
|
31
|
+
press(node: FakeFabricNode): Promise<void>;
|
|
32
|
+
/** A finger held for `duration` ms (500, the long-press delay, by default). */
|
|
33
|
+
longPress(node: FakeFabricNode, options?: {
|
|
34
|
+
duration?: number;
|
|
35
|
+
}): Promise<void>;
|
|
36
|
+
/** Focus the field, then one change per character, each with the next `eventCount`. */
|
|
37
|
+
type(node: FakeFabricNode, text: string, options?: TypeOptions): Promise<void>;
|
|
38
|
+
/** Focus the field, empty it, and blur. */
|
|
39
|
+
clear(node: FakeFabricNode): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/** RNTL's `userEvent`: `userEvent.setup()` for an instance, or the same calls directly. */
|
|
42
|
+
export declare const userEvent: UserEvent & {
|
|
43
|
+
setup(): UserEvent;
|
|
44
|
+
};
|
|
45
|
+
export {};
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `fireEvent` and `userEvent`, as React Native Testing Library names them, emitting the Fabric
|
|
3
|
+
* events the native side would.
|
|
4
|
+
*
|
|
5
|
+
* Each goes through `fabric.emit`, which is the handler the renderer registered with Fabric, so an
|
|
6
|
+
* event takes the real route: the engine's responder negotiation for a touch, its bubbling for the
|
|
7
|
+
* rest, then the component's listener. Nothing here calls a handler directly, and a disabled
|
|
8
|
+
* pressable ignores a press here for the same reason it does on a phone.
|
|
9
|
+
*
|
|
10
|
+
* Both are async where RNTL's `fireEvent` is not. Change detection is zoneless and commits on a
|
|
11
|
+
* later task, so an event has to be awaited before the tree it changed can be read.
|
|
12
|
+
*/
|
|
13
|
+
import { currentOf, ownerOf, settle } from "./render.js";
|
|
14
|
+
import { flatten, isTextInput } from "./queries.js";
|
|
15
|
+
const nativeOf = (payload) => (payload && 'nativeEvent' in payload ? payload['nativeEvent'] : payload) ?? {};
|
|
16
|
+
function emit(node, type, nativeEvent = {}) {
|
|
17
|
+
ownerOf(node).fabric.emit(node, type, { target: node.reactTag, ...nativeEvent });
|
|
18
|
+
}
|
|
19
|
+
/** `press` -> `topPress`: the naming the renderer uses for `(touchEnd)` and every other event. */
|
|
20
|
+
const topLevel = (name) => name.startsWith('top') ? name : `top${name[0].toUpperCase()}${name.slice(1)}`;
|
|
21
|
+
/** One finger, as Fabric reports it. The press machine reads `pageX`/`pageY`. */
|
|
22
|
+
function touch(node, phase) {
|
|
23
|
+
const point = {
|
|
24
|
+
identifier: 0,
|
|
25
|
+
target: node.reactTag,
|
|
26
|
+
pageX: 0,
|
|
27
|
+
pageY: 0,
|
|
28
|
+
locationX: 0,
|
|
29
|
+
locationY: 0,
|
|
30
|
+
timestamp: Date.now(),
|
|
31
|
+
};
|
|
32
|
+
return { ...point, touches: phase === 'start' ? [point] : [], changedTouches: [point] };
|
|
33
|
+
}
|
|
34
|
+
/** The navigation bar's search field, which reports text as `topChangeText` with no count. */
|
|
35
|
+
const isSearchBar = (node) => node.viewName === 'RNSSearchBar';
|
|
36
|
+
/** The text field at or under a node, so a query for its wrapper still reaches the input. */
|
|
37
|
+
function fieldOf(node) {
|
|
38
|
+
const current = currentOf(node) ?? node;
|
|
39
|
+
const field = flatten([current]).find((n) => isTextInput(n) || isSearchBar(n));
|
|
40
|
+
if (!field)
|
|
41
|
+
throw new Error(`No TextInput or search bar at or under this ${node.viewName}.`);
|
|
42
|
+
return field;
|
|
43
|
+
}
|
|
44
|
+
/** The count native would send next: one past the last one JS echoed back. */
|
|
45
|
+
const nextCount = (field) => Number((currentOf(field) ?? field).props['mostRecentEventCount'] ?? 0) + 1;
|
|
46
|
+
function changeText(field, text) {
|
|
47
|
+
if (isSearchBar(field))
|
|
48
|
+
emit(field, 'topChangeText', { text });
|
|
49
|
+
else
|
|
50
|
+
emit(field, 'topChange', { text, eventCount: nextCount(field) });
|
|
51
|
+
}
|
|
52
|
+
async function press(node) {
|
|
53
|
+
emit(node, 'topTouchStart', touch(node, 'start'));
|
|
54
|
+
emit(node, 'topTouchEnd', touch(node, 'end'));
|
|
55
|
+
await settle();
|
|
56
|
+
}
|
|
57
|
+
const named = {
|
|
58
|
+
press,
|
|
59
|
+
async changeText(node, text) {
|
|
60
|
+
changeText(fieldOf(node), text);
|
|
61
|
+
await settle();
|
|
62
|
+
},
|
|
63
|
+
async scroll(node, payload) {
|
|
64
|
+
emit(node, 'topScroll', nativeOf(payload));
|
|
65
|
+
await settle();
|
|
66
|
+
},
|
|
67
|
+
async focus(node) {
|
|
68
|
+
emit(node, 'topFocus');
|
|
69
|
+
await settle();
|
|
70
|
+
},
|
|
71
|
+
async blur(node) {
|
|
72
|
+
emit(node, 'topBlur');
|
|
73
|
+
await settle();
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* `fireEvent(node, 'press')`, `fireEvent(node, 'changeText', 'Ada')`, or any other event by name -
|
|
78
|
+
* `fireEvent(node, 'submitEditing', { text })` emits `topSubmitEditing`.
|
|
79
|
+
*/
|
|
80
|
+
export const fireEvent = Object.assign(async (node, name, payload) => {
|
|
81
|
+
if (name === 'changeText')
|
|
82
|
+
return named.changeText(node, String(payload));
|
|
83
|
+
if (name === 'press' || name === 'scroll' || name === 'focus' || name === 'blur') {
|
|
84
|
+
return named[name](node, payload);
|
|
85
|
+
}
|
|
86
|
+
emit(node, topLevel(name), nativeOf(payload));
|
|
87
|
+
await settle();
|
|
88
|
+
}, named);
|
|
89
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
90
|
+
async function hold(node, duration) {
|
|
91
|
+
emit(node, 'topTouchStart', touch(node, 'start'));
|
|
92
|
+
await settle();
|
|
93
|
+
if (duration)
|
|
94
|
+
await sleep(duration);
|
|
95
|
+
emit(node, 'topTouchEnd', touch(node, 'end'));
|
|
96
|
+
await settle();
|
|
97
|
+
}
|
|
98
|
+
async function endEditing(field, text) {
|
|
99
|
+
emit(field, 'topEndEditing', { text });
|
|
100
|
+
emit(field, 'topBlur');
|
|
101
|
+
await settle();
|
|
102
|
+
}
|
|
103
|
+
const user = {
|
|
104
|
+
press: (node) => hold(node, 0),
|
|
105
|
+
longPress: (node, { duration = 500 } = {}) => hold(node, duration),
|
|
106
|
+
async type(node, text, options = {}) {
|
|
107
|
+
const field = fieldOf(node);
|
|
108
|
+
if (field.props['editable'] === false)
|
|
109
|
+
return;
|
|
110
|
+
emit(field, 'topFocus');
|
|
111
|
+
await settle();
|
|
112
|
+
const maxLength = field.props['maxLength'];
|
|
113
|
+
const limit = typeof maxLength === 'number' ? maxLength : Infinity;
|
|
114
|
+
let value = String((currentOf(field) ?? field).props['text'] ?? '');
|
|
115
|
+
for (const key of text) {
|
|
116
|
+
// `maxLength` is enforced natively: the field never calls back with text past it, so a key
|
|
117
|
+
// pressed once the limit is reached is swallowed the same way it would be on a device.
|
|
118
|
+
if (value.length >= limit)
|
|
119
|
+
continue;
|
|
120
|
+
value += key;
|
|
121
|
+
emit(field, 'topKeyPress', { key });
|
|
122
|
+
changeText(field, value);
|
|
123
|
+
await settle();
|
|
124
|
+
}
|
|
125
|
+
if (options.submitEditing)
|
|
126
|
+
emit(field, 'topSubmitEditing', { text: value });
|
|
127
|
+
if (!options.skipBlur)
|
|
128
|
+
await endEditing(field, value);
|
|
129
|
+
else
|
|
130
|
+
await settle();
|
|
131
|
+
},
|
|
132
|
+
async clear(node) {
|
|
133
|
+
const field = fieldOf(node);
|
|
134
|
+
if (field.props['editable'] === false)
|
|
135
|
+
return;
|
|
136
|
+
emit(field, 'topFocus');
|
|
137
|
+
changeText(field, '');
|
|
138
|
+
await settle();
|
|
139
|
+
await endEditing(field, '');
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
/** RNTL's `userEvent`: `userEvent.setup()` for an instance, or the same calls directly. */
|
|
143
|
+
export const userEvent = { ...user, setup: () => user };
|
|
144
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `fireEvent` and `userEvent`, as React Native Testing Library names them, emitting the Fabric\n * events the native side would.\n *\n * Each goes through `fabric.emit`, which is the handler the renderer registered with Fabric, so an\n * event takes the real route: the engine's responder negotiation for a touch, its bubbling for the\n * rest, then the component's listener. Nothing here calls a handler directly, and a disabled\n * pressable ignores a press here for the same reason it does on a phone.\n *\n * Both are async where RNTL's `fireEvent` is not. Change detection is zoneless and commits on a\n * later task, so an event has to be awaited before the tree it changed can be read.\n */\nimport { currentOf, ownerOf, settle } from './render.ts';\nimport { flatten, isTextInput } from './queries.ts';\nimport type { FakeFabricNode } from './test-utils.ts';\n\n/** A payload as a handler would see it (`{ nativeEvent }`), or the native event itself. */\nexport type EventPayload = Record<string, unknown>;\n\nconst nativeOf = (payload: EventPayload | undefined): Record<string, unknown> =>\n ((payload && 'nativeEvent' in payload ? payload['nativeEvent'] : payload) as Record<\n string,\n unknown\n >) ?? {};\n\nfunction emit(node: FakeFabricNode, type: string, nativeEvent: Record<string, unknown> = {}): void {\n ownerOf(node).fabric.emit(node, type, { target: node.reactTag, ...nativeEvent });\n}\n\n/** `press` -> `topPress`: the naming the renderer uses for `(touchEnd)` and every other event. */\nconst topLevel = (name: string): string =>\n name.startsWith('top') ? name : `top${name[0]!.toUpperCase()}${name.slice(1)}`;\n\n/** One finger, as Fabric reports it. The press machine reads `pageX`/`pageY`. */\nfunction touch(node: FakeFabricNode, phase: 'start' | 'end'): Record<string, unknown> {\n const point = {\n identifier: 0,\n target: node.reactTag,\n pageX: 0,\n pageY: 0,\n locationX: 0,\n locationY: 0,\n timestamp: Date.now(),\n };\n return { ...point, touches: phase === 'start' ? [point] : [], changedTouches: [point] };\n}\n\n/** The navigation bar's search field, which reports text as `topChangeText` with no count. */\nconst isSearchBar = (node: FakeFabricNode): boolean => node.viewName === 'RNSSearchBar';\n\n/** The text field at or under a node, so a query for its wrapper still reaches the input. */\nfunction fieldOf(node: FakeFabricNode): FakeFabricNode {\n const current = currentOf(node) ?? node;\n const field = flatten([current]).find((n) => isTextInput(n) || isSearchBar(n));\n if (!field) throw new Error(`No TextInput or search bar at or under this ${node.viewName}.`);\n return field;\n}\n\n/** The count native would send next: one past the last one JS echoed back. */\nconst nextCount = (field: FakeFabricNode): number =>\n Number((currentOf(field) ?? field).props['mostRecentEventCount'] ?? 0) + 1;\n\nfunction changeText(field: FakeFabricNode, text: string): void {\n if (isSearchBar(field)) emit(field, 'topChangeText', { text });\n else emit(field, 'topChange', { text, eventCount: nextCount(field) });\n}\n\nasync function press(node: FakeFabricNode): Promise<void> {\n emit(node, 'topTouchStart', touch(node, 'start'));\n emit(node, 'topTouchEnd', touch(node, 'end'));\n await settle();\n}\n\nconst named = {\n press,\n async changeText(node: FakeFabricNode, text: string): Promise<void> {\n changeText(fieldOf(node), text);\n await settle();\n },\n async scroll(node: FakeFabricNode, payload?: EventPayload): Promise<void> {\n emit(node, 'topScroll', nativeOf(payload));\n await settle();\n },\n async focus(node: FakeFabricNode): Promise<void> {\n emit(node, 'topFocus');\n await settle();\n },\n async blur(node: FakeFabricNode): Promise<void> {\n emit(node, 'topBlur');\n await settle();\n },\n};\n\ntype FireEvent = ((node: FakeFabricNode, name: string, payload?: unknown) => Promise<void>) &\n typeof named;\n\n/**\n * `fireEvent(node, 'press')`, `fireEvent(node, 'changeText', 'Ada')`, or any other event by name -\n * `fireEvent(node, 'submitEditing', { text })` emits `topSubmitEditing`.\n */\nexport const fireEvent: FireEvent = Object.assign(\n async (node: FakeFabricNode, name: string, payload?: unknown): Promise<void> => {\n if (name === 'changeText') return named.changeText(node, String(payload));\n if (name === 'press' || name === 'scroll' || name === 'focus' || name === 'blur') {\n return named[name](node, payload as EventPayload);\n }\n emit(node, topLevel(name), nativeOf(payload as EventPayload));\n await settle();\n },\n named,\n);\n\n/** Options for `userEvent.type`, named as React Native Testing Library names them. */\nexport interface TypeOptions {\n /** Leave the field focused afterwards. By default typing ends with the field blurring. */\n skipBlur?: boolean;\n /** Press return at the end: `topSubmitEditing`, before any blur. */\n submitEditing?: boolean;\n}\n\n/**\n * Interactions as a person performs them: several events, a task apart, in the order the platform\n * sends them. Slower than `fireEvent`, and closer to what a device does.\n */\nexport interface UserEvent {\n /** A finger down and up, a task apart, through the responder. */\n press(node: FakeFabricNode): Promise<void>;\n /** A finger held for `duration` ms (500, the long-press delay, by default). */\n longPress(node: FakeFabricNode, options?: { duration?: number }): Promise<void>;\n /** Focus the field, then one change per character, each with the next `eventCount`. */\n type(node: FakeFabricNode, text: string, options?: TypeOptions): Promise<void>;\n /** Focus the field, empty it, and blur. */\n clear(node: FakeFabricNode): Promise<void>;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nasync function hold(node: FakeFabricNode, duration: number): Promise<void> {\n emit(node, 'topTouchStart', touch(node, 'start'));\n await settle();\n if (duration) await sleep(duration);\n emit(node, 'topTouchEnd', touch(node, 'end'));\n await settle();\n}\n\nasync function endEditing(field: FakeFabricNode, text: string): Promise<void> {\n emit(field, 'topEndEditing', { text });\n emit(field, 'topBlur');\n await settle();\n}\n\nconst user: UserEvent = {\n press: (node) => hold(node, 0),\n longPress: (node, { duration = 500 } = {}) => hold(node, duration),\n async type(node, text, options = {}) {\n const field = fieldOf(node);\n if (field.props['editable'] === false) return;\n emit(field, 'topFocus');\n await settle();\n const maxLength = field.props['maxLength'];\n const limit = typeof maxLength === 'number' ? maxLength : Infinity;\n let value = String((currentOf(field) ?? field).props['text'] ?? '');\n for (const key of text) {\n // `maxLength` is enforced natively: the field never calls back with text past it, so a key\n // pressed once the limit is reached is swallowed the same way it would be on a device.\n if (value.length >= limit) continue;\n value += key;\n emit(field, 'topKeyPress', { key });\n changeText(field, value);\n await settle();\n }\n if (options.submitEditing) emit(field, 'topSubmitEditing', { text: value });\n if (!options.skipBlur) await endEditing(field, value);\n else await settle();\n },\n async clear(node) {\n const field = fieldOf(node);\n if (field.props['editable'] === false) return;\n emit(field, 'topFocus');\n changeText(field, '');\n await settle();\n await endEditing(field, '');\n },\n};\n\n/** RNTL's `userEvent`: `userEvent.setup()` for an instance, or the same calls directly. */\nexport const userEvent: UserEvent & { setup(): UserEvent } = { ...user, setup: () => user };\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a test gets for `react-native-gesture-handler`, whose source Node cannot load.
|
|
3
|
+
*
|
|
4
|
+
* `Gesture.Pan()`, `Gesture.Tap()`, `Gesture.Simultaneous(...)` and every other builder returns a
|
|
5
|
+
* gesture that takes any configuration call, `.minDistance(10).onUpdate(...)`, and returns itself,
|
|
6
|
+
* so a component builds its gestures in a test exactly as it does on a device. The callbacks it was
|
|
7
|
+
* given are kept on `callbacks`, by name, for a test that wants to call one. `State` and
|
|
8
|
+
* `Directions` are the library's own numbers.
|
|
9
|
+
*/
|
|
10
|
+
/** The configuration and callback calls gestures take, as far as a test types them. */
|
|
11
|
+
type GestureMethod = 'onBegin' | 'onStart' | 'onUpdate' | 'onChange' | 'onEnd' | 'onFinalize' | 'onTouchesDown' | 'onTouchesMove' | 'onTouchesUp' | 'onTouchesCancelled' | 'enabled' | 'runOnJS' | 'minDistance' | 'minPointers' | 'maxPointers' | 'numberOfTaps' | 'minDuration' | 'maxDuration' | 'direction' | 'activeOffsetX' | 'activeOffsetY' | 'failOffsetX' | 'failOffsetY' | 'hitSlop' | 'shouldCancelWhenOutside' | 'manualActivation' | 'simultaneousWithExternalGesture' | 'requireExternalGestureToFail' | 'blocksExternalGesture' | 'withRef' | 'withTestId';
|
|
12
|
+
/**
|
|
13
|
+
* A gesture built in a test: every configuration call returns it, and callbacks are kept. `kind`
|
|
14
|
+
* is the builder that made it, `Pan` or `Race`, and `gestures` what a composed one was made of.
|
|
15
|
+
*/
|
|
16
|
+
export type TestGesture = {
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
readonly gestures: readonly TestGesture[];
|
|
19
|
+
readonly callbacks: Record<string, (...args: never[]) => unknown>;
|
|
20
|
+
} & {
|
|
21
|
+
readonly [M in GestureMethod]: (...args: readonly unknown[]) => TestGesture;
|
|
22
|
+
};
|
|
23
|
+
type Builder = 'Pan' | 'Tap' | 'LongPress' | 'Pinch' | 'Rotation' | 'Fling' | 'Hover' | 'Manual' | 'Native' | 'ForceTouch' | 'Race' | 'Simultaneous' | 'Exclusive';
|
|
24
|
+
export declare const Gesture: Readonly<Record<Builder, (...gestures: readonly unknown[]) => TestGesture>>;
|
|
25
|
+
export declare const State: {
|
|
26
|
+
readonly UNDETERMINED: 0;
|
|
27
|
+
readonly FAILED: 1;
|
|
28
|
+
readonly BEGAN: 2;
|
|
29
|
+
readonly CANCELLED: 3;
|
|
30
|
+
readonly ACTIVE: 4;
|
|
31
|
+
readonly END: 5;
|
|
32
|
+
};
|
|
33
|
+
export declare const Directions: {
|
|
34
|
+
readonly RIGHT: 1;
|
|
35
|
+
readonly LEFT: 2;
|
|
36
|
+
readonly UP: 4;
|
|
37
|
+
readonly DOWN: 8;
|
|
38
|
+
};
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a test gets for `react-native-gesture-handler`, whose source Node cannot load.
|
|
3
|
+
*
|
|
4
|
+
* `Gesture.Pan()`, `Gesture.Tap()`, `Gesture.Simultaneous(...)` and every other builder returns a
|
|
5
|
+
* gesture that takes any configuration call, `.minDistance(10).onUpdate(...)`, and returns itself,
|
|
6
|
+
* so a component builds its gestures in a test exactly as it does on a device. The callbacks it was
|
|
7
|
+
* given are kept on `callbacks`, by name, for a test that wants to call one. `State` and
|
|
8
|
+
* `Directions` are the library's own numbers.
|
|
9
|
+
*/
|
|
10
|
+
function gesture(kind, gestures) {
|
|
11
|
+
const callbacks = {};
|
|
12
|
+
const fields = { kind, gestures, callbacks };
|
|
13
|
+
const self = new Proxy(fields, {
|
|
14
|
+
get(target, key) {
|
|
15
|
+
if (typeof key === 'string' && key in fields)
|
|
16
|
+
return fields[key];
|
|
17
|
+
// Not a thenable, and nothing to say to a symbol such as `Symbol.toPrimitive`.
|
|
18
|
+
if (typeof key === 'symbol' || key === 'then')
|
|
19
|
+
return undefined;
|
|
20
|
+
return (...args) => {
|
|
21
|
+
if (key.startsWith('on') && typeof args[0] === 'function') {
|
|
22
|
+
callbacks[key] = args[0];
|
|
23
|
+
}
|
|
24
|
+
return self;
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
return self;
|
|
29
|
+
}
|
|
30
|
+
export const Gesture = new Proxy({}, {
|
|
31
|
+
get: (_target, kind) => (...gestures) => gesture(String(kind), gestures),
|
|
32
|
+
});
|
|
33
|
+
export const State = {
|
|
34
|
+
UNDETERMINED: 0,
|
|
35
|
+
FAILED: 1,
|
|
36
|
+
BEGAN: 2,
|
|
37
|
+
CANCELLED: 3,
|
|
38
|
+
ACTIVE: 4,
|
|
39
|
+
END: 5,
|
|
40
|
+
};
|
|
41
|
+
export const Directions = { RIGHT: 1, LEFT: 2, UP: 4, DOWN: 8 };
|
|
42
|
+
//# sourceMappingURL=gesture-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gesture-handler.js","sourceRoot":"","sources":["../src/gesture-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6DH,SAAS,OAAO,CAAC,IAAY,EAAE,QAAgC;IAC7D,MAAM,SAAS,GAAkD,EAAE,CAAC;IACpE,MAAM,MAAM,GAA4B,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtE,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,MAAqB,EAAE;QAC5C,GAAG,CAAC,MAAM,EAAE,GAAG;YACb,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,IAAI,MAAM;gBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,+EAA+E;YAC/E,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,MAAM;gBAAE,OAAO,SAAS,CAAC;YAChE,OAAO,CAAC,GAAG,IAAe,EAAE,EAAE;gBAC5B,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;oBAC1D,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAkC,CAAC;gBAC5D,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,KAAK,CAC9B,EAAE,EACF;IACE,GAAG,EACD,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAClB,CAAC,GAAG,QAAmB,EAAE,EAAE,CACzB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAyB,CAAC;CACrD,CAC6E,CAAC;AAEjF,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,CAAC;IACR,SAAS,EAAE,CAAC;IACZ,MAAM,EAAE,CAAC;IACT,GAAG,EAAE,CAAC;CACE,CAAC;AAEX,MAAM,CAAC,MAAM,UAAU,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAW,CAAC","sourcesContent":["/**\n * What a test gets for `react-native-gesture-handler`, whose source Node cannot load.\n *\n * `Gesture.Pan()`, `Gesture.Tap()`, `Gesture.Simultaneous(...)` and every other builder returns a\n * gesture that takes any configuration call, `.minDistance(10).onUpdate(...)`, and returns itself,\n * so a component builds its gestures in a test exactly as it does on a device. The callbacks it was\n * given are kept on `callbacks`, by name, for a test that wants to call one. `State` and\n * `Directions` are the library's own numbers.\n */\n\n/** The configuration and callback calls gestures take, as far as a test types them. */\ntype GestureMethod =\n | 'onBegin'\n | 'onStart'\n | 'onUpdate'\n | 'onChange'\n | 'onEnd'\n | 'onFinalize'\n | 'onTouchesDown'\n | 'onTouchesMove'\n | 'onTouchesUp'\n | 'onTouchesCancelled'\n | 'enabled'\n | 'runOnJS'\n | 'minDistance'\n | 'minPointers'\n | 'maxPointers'\n | 'numberOfTaps'\n | 'minDuration'\n | 'maxDuration'\n | 'direction'\n | 'activeOffsetX'\n | 'activeOffsetY'\n | 'failOffsetX'\n | 'failOffsetY'\n | 'hitSlop'\n | 'shouldCancelWhenOutside'\n | 'manualActivation'\n | 'simultaneousWithExternalGesture'\n | 'requireExternalGestureToFail'\n | 'blocksExternalGesture'\n | 'withRef'\n | 'withTestId';\n\n/**\n * A gesture built in a test: every configuration call returns it, and callbacks are kept. `kind`\n * is the builder that made it, `Pan` or `Race`, and `gestures` what a composed one was made of.\n */\nexport type TestGesture = {\n readonly kind: string;\n readonly gestures: readonly TestGesture[];\n readonly callbacks: Record<string, (...args: never[]) => unknown>;\n} & { readonly [M in GestureMethod]: (...args: readonly unknown[]) => TestGesture };\n\ntype Builder =\n | 'Pan'\n | 'Tap'\n | 'LongPress'\n | 'Pinch'\n | 'Rotation'\n | 'Fling'\n | 'Hover'\n | 'Manual'\n | 'Native'\n | 'ForceTouch'\n | 'Race'\n | 'Simultaneous'\n | 'Exclusive';\n\nfunction gesture(kind: string, gestures: readonly TestGesture[]): TestGesture {\n const callbacks: Record<string, (...args: never[]) => unknown> = {};\n const fields: Record<string, unknown> = { kind, gestures, callbacks };\n const self = new Proxy(fields as TestGesture, {\n get(target, key) {\n if (typeof key === 'string' && key in fields) return fields[key];\n // Not a thenable, and nothing to say to a symbol such as `Symbol.toPrimitive`.\n if (typeof key === 'symbol' || key === 'then') return undefined;\n return (...args: unknown[]) => {\n if (key.startsWith('on') && typeof args[0] === 'function') {\n callbacks[key] = args[0] as (...args: never[]) => unknown;\n }\n return self;\n };\n },\n });\n return self;\n}\n\nexport const Gesture = new Proxy(\n {},\n {\n get:\n (_target, kind) =>\n (...gestures: unknown[]) =>\n gesture(String(kind), gestures as TestGesture[]),\n },\n) as Readonly<Record<Builder, (...gestures: readonly unknown[]) => TestGesture>>;\n\nexport const State = {\n UNDETERMINED: 0,\n FAILED: 1,\n BEGAN: 2,\n CANCELLED: 3,\n ACTIVE: 4,\n END: 5,\n} as const;\n\nexport const Directions = { RIGHT: 1, LEFT: 2, UP: 4, DOWN: 8 } as const;\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TestGesture } from './gesture-handler.ts';
|
|
2
|
+
import type { FakeFabricNode } from './test-utils.ts';
|
|
3
|
+
/**
|
|
4
|
+
* The gesture attached to a view with `[gesture]`, or, given a kind, the gesture of that kind in
|
|
5
|
+
* it, however deeply composed: `gestureOf(row, 'Pan').callbacks['onEnd']!(event)`. What a test
|
|
6
|
+
* calls in place of a finger, since there is no native recogniser to drive.
|
|
7
|
+
*/
|
|
8
|
+
export declare function gestureOf(node: FakeFabricNode, kind?: string): TestGesture;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Where the `[gesture]` stand-in keeps the view's gesture. */
|
|
2
|
+
const GESTURE = Symbol.for('ng-native.test-gesture');
|
|
3
|
+
/**
|
|
4
|
+
* The gesture attached to a view with `[gesture]`, or, given a kind, the gesture of that kind in
|
|
5
|
+
* it, however deeply composed: `gestureOf(row, 'Pan').callbacks['onEnd']!(event)`. What a test
|
|
6
|
+
* calls in place of a finger, since there is no native recogniser to drive.
|
|
7
|
+
*/
|
|
8
|
+
export function gestureOf(node, kind) {
|
|
9
|
+
const attached = node.instanceHandle?.[GESTURE];
|
|
10
|
+
if (!attached)
|
|
11
|
+
throw new Error(`gestureOf: the ${node.viewName} has no gesture attached.`);
|
|
12
|
+
if (kind === undefined)
|
|
13
|
+
return attached;
|
|
14
|
+
const found = find(attached, kind);
|
|
15
|
+
if (!found)
|
|
16
|
+
throw new Error(`gestureOf: the ${node.viewName}'s gesture has no ${kind} gesture.`);
|
|
17
|
+
return found;
|
|
18
|
+
}
|
|
19
|
+
function find(gesture, kind) {
|
|
20
|
+
if (gesture.kind === kind)
|
|
21
|
+
return gesture;
|
|
22
|
+
for (const inner of gesture.gestures) {
|
|
23
|
+
const found = find(inner, kind);
|
|
24
|
+
if (found)
|
|
25
|
+
return found;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=gesture-of.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gesture-of.js","sourceRoot":"","sources":["../src/gesture-of.ts"],"names":[],"mappings":"AAGA,+DAA+D;AAC/D,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,IAAoB,EAAE,IAAa;IAC3D,MAAM,QAAQ,GAAI,IAAI,CAAC,cAAiE,EAAE,CACxF,OAAO,CACR,CAAC;IACF,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,CAAC,QAAQ,2BAA2B,CAAC,CAAC;IAC3F,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,CAAC,QAAQ,qBAAqB,IAAI,WAAW,CAAC,CAAC;IACjG,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,IAAI,CAAC,OAAoB,EAAE,IAAY;IAC9C,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAChC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import type { TestGesture } from './gesture-handler.ts';\nimport type { FakeFabricNode } from './test-utils.ts';\n\n/** Where the `[gesture]` stand-in keeps the view's gesture. */\nconst GESTURE = Symbol.for('ng-native.test-gesture');\n\n/**\n * The gesture attached to a view with `[gesture]`, or, given a kind, the gesture of that kind in\n * it, however deeply composed: `gestureOf(row, 'Pan').callbacks['onEnd']!(event)`. What a test\n * calls in place of a finger, since there is no native recogniser to drive.\n */\nexport function gestureOf(node: FakeFabricNode, kind?: string): TestGesture {\n const attached = (node.instanceHandle as Record<symbol, TestGesture | undefined> | null)?.[\n GESTURE\n ];\n if (!attached) throw new Error(`gestureOf: the ${node.viewName} has no gesture attached.`);\n if (kind === undefined) return attached;\n const found = find(attached, kind);\n if (!found) throw new Error(`gestureOf: the ${node.viewName}'s gesture has no ${kind} gesture.`);\n return found;\n}\n\nfunction find(gesture: TestGesture, kind: string): TestGesture | undefined {\n if (gesture.kind === kind) return gesture;\n for (const inner of gesture.gestures) {\n const found = find(inner, kind);\n if (found) return found;\n }\n return undefined;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
export type GestureSpec = object;
|
|
3
|
+
export interface GestureTarget {
|
|
4
|
+
readonly tag: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class NativeGesture {
|
|
7
|
+
readonly gesture: import("@angular/core").InputSignal<object>;
|
|
8
|
+
constructor();
|
|
9
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NativeGesture, never>;
|
|
10
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NativeGesture, "[gesture]", never, { "gesture": { "alias": "gesture"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
11
|
+
}
|
|
12
|
+
export declare class GestureRoot {
|
|
13
|
+
constructor();
|
|
14
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<GestureRoot, never>;
|
|
15
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<GestureRoot, "gesture-root", never, {}, {}, never, ["*"], true, never>;
|
|
16
|
+
}
|
package/dist/gestures.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a test gets for `@ng-native/components/gestures`.
|
|
3
|
+
*
|
|
4
|
+
* The real entry point reaches react-native-gesture-handler's and Reanimated's source, which is
|
|
5
|
+
* React Native's Flow and TypeScript that Node cannot load, so a test of any component with a
|
|
6
|
+
* `<gesture-root>` or a `[gesture]` in it failed to import before it ran. `ngNative()` resolves
|
|
7
|
+
* the entry point here instead: the same selectors and the same `gesture` input, with no native
|
|
8
|
+
* library behind them. `<gesture-root>` renders its content in a plain view, as it does on iOS,
|
|
9
|
+
* and a `[gesture]` keeps its view from being collapsed, as the real one does; nothing is
|
|
10
|
+
* attached, because there is no native side for a recogniser to live in.
|
|
11
|
+
*
|
|
12
|
+
* A test of what a gesture does calls its callbacks, found with `gestureOf`, or the component method
|
|
13
|
+
* they call, directly.
|
|
14
|
+
*/
|
|
15
|
+
import { Component, Directive, ElementRef, inject, input } from '@angular/core';
|
|
16
|
+
import { claimHost, registerViewName } from '@ng-native/fabric';
|
|
17
|
+
import * as i0 from "@angular/core";
|
|
18
|
+
/** Where the view's gesture is kept for `gestureOf`: on the engine's node, as the real one is. */
|
|
19
|
+
const GESTURE = Symbol.for('ng-native.test-gesture');
|
|
20
|
+
export class NativeGesture {
|
|
21
|
+
gesture = input.required(/* @ts-ignore */
|
|
22
|
+
...(ngDevMode ? [{ debugName: "gesture" }] : /* istanbul ignore next */ []));
|
|
23
|
+
constructor() {
|
|
24
|
+
const node = inject(ElementRef).nativeElement;
|
|
25
|
+
Object.defineProperty(node, GESTURE, { get: () => this.gesture(), configurable: true });
|
|
26
|
+
}
|
|
27
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeGesture, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
28
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: NativeGesture, isStandalone: true, selector: "[gesture]", inputs: { gesture: { classPropertyName: "gesture", publicName: "gesture", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "collapsable": "false" } }, ngImport: i0 });
|
|
29
|
+
}
|
|
30
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeGesture, decorators: [{
|
|
31
|
+
type: Directive,
|
|
32
|
+
args: [{ selector: '[gesture]', host: { '[collapsable]': 'false' } }]
|
|
33
|
+
}], ctorParameters: () => [], propDecorators: { gesture: [{ type: i0.Input, args: [{ isSignal: true, alias: "gesture", required: true }] }] } });
|
|
34
|
+
export class GestureRoot {
|
|
35
|
+
constructor() {
|
|
36
|
+
registerViewName('gesture-root', 'RCTView', { flex: 1 });
|
|
37
|
+
claimHost(inject(ElementRef).nativeElement);
|
|
38
|
+
}
|
|
39
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: GestureRoot, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
40
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.2.0", type: GestureRoot, isStandalone: true, selector: "gesture-root", ngImport: i0, template: '<ng-content />', isInline: true });
|
|
41
|
+
}
|
|
42
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: GestureRoot, decorators: [{
|
|
43
|
+
type: Component,
|
|
44
|
+
args: [{ selector: 'gesture-root', template: '<ng-content />' }]
|
|
45
|
+
}], ctorParameters: () => [] });
|
|
46
|
+
//# sourceMappingURL=gestures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gestures.js","sourceRoot":"","sources":["../src/gestures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAiB,MAAM,mBAAmB,CAAC;;AAQ/E,kGAAkG;AAClG,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;AAGrD,MAAM,OAAO,aAAa;IACf,OAAO,GAAG,KAAK,CAAC,QAAQ;gFAAe,CAAC;IAEjD;QACE,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAuB,CAAC;QACxD,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1F,CAAC;uGANU,aAAa;2FAAb,aAAa;;2FAAb,aAAa;kBADzB,SAAS;mBAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE;;AAWxE,MAAM,OAAO,WAAW;IACtB;QACE,gBAAgB,CAAC,cAAc,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QACzD,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC,CAAC;IAC1D,CAAC;uGAJU,WAAW;2FAAX,WAAW,wEADyB,gBAAgB;;2FACpD,WAAW;kBADvB,SAAS;mBAAC,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE","sourcesContent":["/**\n * What a test gets for `@ng-native/components/gestures`.\n *\n * The real entry point reaches react-native-gesture-handler's and Reanimated's source, which is\n * React Native's Flow and TypeScript that Node cannot load, so a test of any component with a\n * `<gesture-root>` or a `[gesture]` in it failed to import before it ran. `ngNative()` resolves\n * the entry point here instead: the same selectors and the same `gesture` input, with no native\n * library behind them. `<gesture-root>` renders its content in a plain view, as it does on iOS,\n * and a `[gesture]` keeps its view from being collapsed, as the real one does; nothing is\n * attached, because there is no native side for a recogniser to live in.\n *\n * A test of what a gesture does calls its callbacks, found with `gestureOf`, or the component method\n * they call, directly.\n */\nimport { Component, Directive, ElementRef, inject, input } from '@angular/core';\nimport { claimHost, registerViewName, type HostNode } from '@ng-native/fabric';\n\nexport type GestureSpec = object;\n\nexport interface GestureTarget {\n readonly tag: number;\n}\n\n/** Where the view's gesture is kept for `gestureOf`: on the engine's node, as the real one is. */\nconst GESTURE = Symbol.for('ng-native.test-gesture');\n\n@Directive({ selector: '[gesture]', host: { '[collapsable]': 'false' } })\nexport class NativeGesture {\n readonly gesture = input.required<GestureSpec>();\n\n constructor() {\n const node = inject(ElementRef).nativeElement as object;\n Object.defineProperty(node, GESTURE, { get: () => this.gesture(), configurable: true });\n }\n}\n\n@Component({ selector: 'gesture-root', template: '<ng-content />' })\nexport class GestureRoot {\n constructor() {\n registerViewName('gesture-root', 'RCTView', { flex: 1 });\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fake `nativeFabricUIManager`, and a Testing Library on top of it. Kept out of the engine so
|
|
3
|
+
* nothing in a shipped bundle can reach it, and out of `test/` so an app's own tests can use it.
|
|
4
|
+
*
|
|
5
|
+
* The runners live beside `src/` rather than in it: `@ng-native/testing/vitest` and
|
|
6
|
+
* `@ng-native/testing/register` are plain JavaScript, because they are loaded by Node before
|
|
7
|
+
* anything can compile TypeScript for it.
|
|
8
|
+
*/
|
|
9
|
+
export { createFakeFabric, type FakeFabric, type FakeFabricNode } from './test-utils.ts';
|
|
10
|
+
export { cleanup, render, screen, settle, waitForElementToBeRemoved, within, type RenderOptions, type RenderResult, } from './render.ts';
|
|
11
|
+
export type { BoundQueries, ByRoleOptions, Matcher, TextMatchOptions } from './queries.ts';
|
|
12
|
+
export { waitFor, type WaitForOptions } from './wait-for.ts';
|
|
13
|
+
export { gestureOf } from './gesture-of.ts';
|
|
14
|
+
export type { TestGesture } from './gesture-handler.ts';
|
|
15
|
+
export { fireEvent, userEvent, type EventPayload, type TypeOptions, type UserEvent, } from './events.ts';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fake `nativeFabricUIManager`, and a Testing Library on top of it. Kept out of the engine so
|
|
3
|
+
* nothing in a shipped bundle can reach it, and out of `test/` so an app's own tests can use it.
|
|
4
|
+
*
|
|
5
|
+
* The runners live beside `src/` rather than in it: `@ng-native/testing/vitest` and
|
|
6
|
+
* `@ng-native/testing/register` are plain JavaScript, because they are loaded by Node before
|
|
7
|
+
* anything can compile TypeScript for it.
|
|
8
|
+
*/
|
|
9
|
+
export { createFakeFabric } from "./test-utils.js";
|
|
10
|
+
export { cleanup, render, screen, settle, waitForElementToBeRemoved, within, } from "./render.js";
|
|
11
|
+
export { waitFor } from "./wait-for.js";
|
|
12
|
+
export { gestureOf } from "./gesture-of.js";
|
|
13
|
+
export { fireEvent, userEvent, } from "./events.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,gBAAgB,EAAwC,MAAM,iBAAiB,CAAC;AACzF,OAAO,EACL,OAAO,EACP,MAAM,EACN,MAAM,EACN,MAAM,EACN,yBAAyB,EACzB,MAAM,GAGP,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,OAAO,EAAuB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EACL,SAAS,EACT,SAAS,GAIV,MAAM,aAAa,CAAC","sourcesContent":["/**\n * The fake `nativeFabricUIManager`, and a Testing Library on top of it. Kept out of the engine so\n * nothing in a shipped bundle can reach it, and out of `test/` so an app's own tests can use it.\n *\n * The runners live beside `src/` rather than in it: `@ng-native/testing/vitest` and\n * `@ng-native/testing/register` are plain JavaScript, because they are loaded by Node before\n * anything can compile TypeScript for it.\n */\nexport { createFakeFabric, type FakeFabric, type FakeFabricNode } from './test-utils.ts';\nexport {\n cleanup,\n render,\n screen,\n settle,\n waitForElementToBeRemoved,\n within,\n type RenderOptions,\n type RenderResult,\n} from './render.ts';\nexport type { BoundQueries, ByRoleOptions, Matcher, TextMatchOptions } from './queries.ts';\nexport { waitFor, type WaitForOptions } from './wait-for.ts';\nexport { gestureOf } from './gesture-of.ts';\nexport type { TestGesture } from './gesture-handler.ts';\nexport {\n fireEvent,\n userEvent,\n type EventPayload,\n type TypeOptions,\n type UserEvent,\n} from './events.ts';\n"]}
|