-
-
Notifications
You must be signed in to change notification settings - Fork 173
Expand file tree
/
Copy pathtestUtils.ts
More file actions
142 lines (120 loc) · 3.93 KB
/
Copy pathtestUtils.ts
File metadata and controls
142 lines (120 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import { renderHook } from "@testing-library/react-native";
import { useAnimatedRef } from "react-native-reanimated";
import { sv } from "../../../../__fixtures__/sv";
import type { useChatKeyboard } from "..";
import type { SharedValue } from "react-native-reanimated";
import type Reanimated from "react-native-reanimated";
export type KeyboardEvent = { height: number; duration?: number };
export type Handlers = {
onStart: (e: KeyboardEvent) => void;
onMove: (e: KeyboardEvent) => void;
onInteractive: (e: KeyboardEvent) => void;
onEnd: (e: KeyboardEvent) => void;
};
export const mockOffset = { value: 0 };
export const mockLayout = { value: { width: 390, height: 800 } };
export const mockSize = { value: { width: 390, height: 2000 } };
export const KEYBOARD = 300;
export const mockScrollTo = jest.fn();
type Reaction = {
producer: () => unknown;
effect: (current: unknown, previous: unknown | null) => void;
previous: unknown;
};
const reactions: Reaction[] = [];
/**
* Linear interpolate mock matching Reanimated's `interpolate` signature.
*
* @param value - The input value to interpolate.
* @param input - Input range `[min, max]`.
* @param output - Output range `[min, max]`.
* @returns The interpolated value.
* @example mockInterpolate(150, [0, 300], [0, 250]); // 125
*/
export function mockInterpolate(
value: number,
input: number[],
output: number[],
): number {
"worklet";
if (input[1] === 0) {
return 0;
}
const progress = (value - input[0]) / (input[1] - input[0]);
return output[0] + progress * (output[1] - output[0]);
}
/** Reset mock scroll state to defaults. */
export function reset() {
mockOffset.value = 0;
mockLayout.value = { width: 390, height: 800 };
mockSize.value = { width: 390, height: 2000 };
}
/**
* Common beforeEach: reset modules, inject trackable scrollTo, reset state.
*
* @example setupBeforeEach()
*/
export function setupBeforeEach() {
jest.resetModules();
reactions.length = 0;
jest.doMock("react-native-reanimated", () => ({
...require("react-native-reanimated/mock"),
scrollTo: mockScrollTo,
interpolate: mockInterpolate,
useAnimatedReaction: (
producer: () => unknown,
effect: (current: unknown, previous: unknown | null) => void,
) => {
reactions.push({
producer,
effect,
previous: producer(),
});
},
}));
reset();
mockScrollTo.mockClear();
}
/** Run registered Reanimated reactions after mutating mocked shared values. */
export function flushAnimatedReactions() {
for (const reaction of reactions) {
const current = reaction.producer();
reaction.effect(current, reaction.previous);
reaction.previous = current;
}
}
type RenderOptions = Omit<
Parameters<typeof useChatKeyboard>[1],
"freeze" | "offset" | "blankSpace" | "extraContentPadding"
> & {
freeze?: boolean | SharedValue<boolean>;
offset?: number;
blankSpace?: SharedValue<number>;
extraContentPadding?: SharedValue<number>;
};
/**
* Create a render function that loads the hook from the given module path.
*
* @param modulePath - Relative path to the hook module (e.g. `"../index.ios"` or `"../index"`).
* @returns A render function bound to that module.
* @example const render = createRender("../index.ios");
*/
export function createRender(modulePath: string) {
return function render(options: RenderOptions) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mod = require(modulePath) as {
useChatKeyboard: typeof useChatKeyboard;
};
return renderHook(() => {
const ref = useAnimatedRef<Reanimated.ScrollView>();
const freeze = options.freeze ?? false;
return mod.useChatKeyboard(ref, {
...options,
freeze: typeof freeze === "boolean" ? sv(freeze) : freeze,
offset: options.offset ?? 0,
blankSpace: options.blankSpace ?? sv(0),
extraContentPadding: options.extraContentPadding ?? sv(0),
});
});
};
}