-
Notifications
You must be signed in to change notification settings - Fork 0
/
create.tsx
138 lines (119 loc) · 3.72 KB
/
create.tsx
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
import mapValues from "lodash/mapValues";
import type { ComponentType } from "react";
import { type AccessorsType, createAccessors } from "react-shallow-search";
import { createRenderer } from "react-test-renderer/shallow";
import { getMapHookKeyToLocalIndex } from "./getMapHookKeyToLocalIndex";
import { mockHookValues } from "./mockHookValues";
import type { EngineType, OptionsType } from "./types";
/**
* Creates engine for unit-testing of react component
* @param Component target component
* @param defaultProps stubs for required props
* @param options named accessors for rendered components and their props and callbacks
* @returns function that renders components and initializes accessors
*/
export function create<
Props,
Queries extends Record<
string,
// biome-ignore lint/suspicious/noExplicitAny: supports any component
keyof JSX.IntrinsicElements | ComponentType<any>
>,
Callbacks extends Record<string, [keyof Queries & string, string]>,
Properties extends Record<string, [keyof Queries, string]>,
Hooks extends Record<
string,
// biome-ignore lint/suspicious/noExplicitAny: should extend Function
(...args: any) => any
>,
>(
Component: ComponentType<Props>,
defaultProps: Props,
{
queries,
callbacks,
properties,
hooks,
hookOrder,
hookDefaultValues = {},
mockFunctionValue,
getMockArguments,
}: OptionsType<Queries, Callbacks, Properties, Hooks>,
) {
/**
* function that renders components and initializes accessors
* @param props props of target component
* @param hookValues values of hooks
* @returns engine for unit-testing
*/
const render = (
props: Partial<Props>,
hookValues: Partial<{
[Key in keyof Hooks]: ReturnType<Hooks[Key]>;
}> = {},
): EngineType<Queries, Callbacks, Properties, Hooks> => {
const mapHookKeyToLocalIndex =
hooks && hookOrder ? getMapHookKeyToLocalIndex(hooks, hookOrder) : null;
mockHookValues(
hooks,
hookOrder,
hookDefaultValues,
hookValues,
mockFunctionValue,
);
const renderer = createRenderer();
renderer.render(<Component {...defaultProps} {...props} />);
const root = renderer.getRenderOutput();
const accessors = mapValues(queries, (accessorsParams) => {
if (Array.isArray(accessorsParams)) {
return createAccessors(root, accessorsParams[0], accessorsParams[1]);
}
return createAccessors(root, accessorsParams);
}) as {
[Key in keyof Queries]: AccessorsType<Queries[Key]>;
};
const getCallback = <Key extends keyof Callbacks & string>(
callbackKey: Key,
) => {
if (!callbacks) {
throw new Error("`callbacks` option is not setted");
}
const [accessorKey, propName] = callbacks[callbackKey];
const props = accessors[accessorKey].getProps();
const callback = props[propName];
if (typeof callback !== "function") {
throw new Error(
`accessor "${accessorKey}", prop "${propName}" is not a function`,
);
}
return callback;
};
const getProperty = <Key extends keyof Properties & string>(
propertyKey: Key,
) => {
if (!properties) {
throw new Error("`properties` option is not setted");
}
const [accessorKey, propName] = properties[propertyKey];
const props = accessors[accessorKey].getProps();
return props[propName];
};
const getHookArguments = <Key extends keyof Hooks>(hookKey: Key) => {
if (!hooks || !mapHookKeyToLocalIndex || !getMockArguments) {
throw new Error(
"Required parameters to initialize hooks: `hooks`, `hookOrder`, `mockFunctionValue`, `getMockArguments`",
);
}
return getMockArguments(hooks[hookKey], mapHookKeyToLocalIndex[hookKey]);
};
return {
root,
checkIsRendered: () => Boolean(root),
accessors,
getCallback,
getHookArguments,
getProperty,
};
};
return render;
}