ferns-ui 1.7.0 → 1.8.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/Common.d.ts +42 -0
- package/dist/DateTimeField.test.js +5 -9
- package/dist/DateTimeField.test.js.map +1 -1
- package/dist/Icon.js +2 -1
- package/dist/Icon.js.map +1 -1
- package/dist/Modal.js +21 -6
- package/dist/Modal.js.map +1 -1
- package/dist/SelectBadge.d.ts +3 -0
- package/dist/SelectBadge.js +180 -0
- package/dist/SelectBadge.js.map +1 -0
- package/dist/TextArea.test.d.ts +1 -0
- package/dist/TextArea.test.js +146 -0
- package/dist/TextArea.test.js.map +1 -0
- package/dist/TextField.test.d.ts +1 -0
- package/dist/TextField.test.js +251 -0
- package/dist/TextField.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/test-utils.d.ts +97 -0
- package/dist/test-utils.js +23 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/useStoredState.d.ts +1 -1
- package/dist/useStoredState.js +19 -4
- package/dist/useStoredState.js.map +1 -1
- package/dist/useStoredState.test.d.ts +1 -0
- package/dist/useStoredState.test.js +93 -0
- package/dist/useStoredState.test.js.map +1 -0
- package/package.json +1 -1
- package/src/Common.ts +43 -0
- package/src/DateTimeField.test.tsx +5 -10
- package/src/Icon.tsx +1 -1
- package/src/Modal.tsx +24 -6
- package/src/SelectBadge.tsx +279 -0
- package/src/TextArea.test.tsx +271 -0
- package/src/TextField.test.tsx +442 -0
- package/src/__snapshots__/TextArea.test.tsx.snap +424 -0
- package/src/__snapshots__/TextField.test.tsx.snap +481 -0
- package/src/index.tsx +1 -0
- package/src/test-utils.tsx +26 -0
- package/src/useStoredState.test.tsx +134 -0
- package/src/useStoredState.ts +21 -5
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`TextField snapshots should match snapshot when disabled 1`] = `
|
|
4
|
+
<View
|
|
5
|
+
style={
|
|
6
|
+
{
|
|
7
|
+
"flexDirection": "column",
|
|
8
|
+
"width": "100%",
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
>
|
|
12
|
+
<Text
|
|
13
|
+
style={
|
|
14
|
+
{
|
|
15
|
+
"color": "#1C1C1C",
|
|
16
|
+
"fontSize": 14,
|
|
17
|
+
"fontWeight": 600,
|
|
18
|
+
"lineHeight": 22.4,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
>
|
|
22
|
+
Disabled Field
|
|
23
|
+
</Text>
|
|
24
|
+
<View
|
|
25
|
+
style={
|
|
26
|
+
{
|
|
27
|
+
"alignItems": "center",
|
|
28
|
+
"backgroundColor": "#D9D9D9",
|
|
29
|
+
"borderColor": "#4E4E4E",
|
|
30
|
+
"borderRadius": 4,
|
|
31
|
+
"borderWidth": 1,
|
|
32
|
+
"flexDirection": "row",
|
|
33
|
+
"overflow": "hidden",
|
|
34
|
+
"paddingHorizontal": 12,
|
|
35
|
+
"paddingVertical": 8,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
<TextInput
|
|
40
|
+
accessibilityHint="Enter text here"
|
|
41
|
+
accessibilityState={
|
|
42
|
+
{
|
|
43
|
+
"disabled": true,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
aria-label="Text input field"
|
|
47
|
+
autoCapitalize="sentences"
|
|
48
|
+
autoCorrect={true}
|
|
49
|
+
blurOnSubmit={true}
|
|
50
|
+
keyboardType="default"
|
|
51
|
+
numberOfLines={1}
|
|
52
|
+
onBlur={[Function]}
|
|
53
|
+
onChangeText={[MockFunction]}
|
|
54
|
+
onContentSizeChange={[Function]}
|
|
55
|
+
onFocus={[Function]}
|
|
56
|
+
onSubmitEditing={[Function]}
|
|
57
|
+
placeholderTextColor="#686868"
|
|
58
|
+
readOnly={true}
|
|
59
|
+
secureTextEntry={false}
|
|
60
|
+
style={
|
|
61
|
+
{
|
|
62
|
+
"color": "#1C1C1C",
|
|
63
|
+
"flex": 1,
|
|
64
|
+
"fontFamily": "text",
|
|
65
|
+
"fontSize": 16,
|
|
66
|
+
"gap": 10,
|
|
67
|
+
"height": 20,
|
|
68
|
+
"paddingVertical": 0,
|
|
69
|
+
"width": "100%",
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
textContentType="none"
|
|
73
|
+
underlineColorAndroid="transparent"
|
|
74
|
+
value="disabled value"
|
|
75
|
+
/>
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
exports[`TextField snapshots should match snapshot with all props 1`] = `
|
|
81
|
+
<View
|
|
82
|
+
style={
|
|
83
|
+
{
|
|
84
|
+
"flexDirection": "column",
|
|
85
|
+
"width": "100%",
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
>
|
|
89
|
+
<Text
|
|
90
|
+
style={
|
|
91
|
+
{
|
|
92
|
+
"color": "#1C1C1C",
|
|
93
|
+
"fontSize": 14,
|
|
94
|
+
"fontWeight": 600,
|
|
95
|
+
"lineHeight": 22.4,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
>
|
|
99
|
+
Test Title
|
|
100
|
+
</Text>
|
|
101
|
+
<View
|
|
102
|
+
style={
|
|
103
|
+
{
|
|
104
|
+
"alignItems": "center",
|
|
105
|
+
"flexDirection": "row",
|
|
106
|
+
"marginVertical": 2,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
>
|
|
110
|
+
<View
|
|
111
|
+
style={
|
|
112
|
+
{
|
|
113
|
+
"marginLeft": 4,
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
>
|
|
117
|
+
<Text
|
|
118
|
+
style={
|
|
119
|
+
{
|
|
120
|
+
"color": "#BD1111",
|
|
121
|
+
"fontSize": 12,
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
>
|
|
125
|
+
Error text
|
|
126
|
+
</Text>
|
|
127
|
+
</View>
|
|
128
|
+
</View>
|
|
129
|
+
<View
|
|
130
|
+
style={
|
|
131
|
+
{
|
|
132
|
+
"alignItems": "center",
|
|
133
|
+
"backgroundColor": "#FFFFFF",
|
|
134
|
+
"borderColor": "#D33232",
|
|
135
|
+
"borderRadius": 4,
|
|
136
|
+
"borderWidth": 1,
|
|
137
|
+
"flexDirection": "row",
|
|
138
|
+
"overflow": "hidden",
|
|
139
|
+
"paddingHorizontal": 12,
|
|
140
|
+
"paddingVertical": 8,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
>
|
|
144
|
+
<TextInput
|
|
145
|
+
accessibilityHint="Enter text here"
|
|
146
|
+
accessibilityState={
|
|
147
|
+
{
|
|
148
|
+
"disabled": false,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
aria-label="Text input field"
|
|
152
|
+
autoCapitalize="sentences"
|
|
153
|
+
autoCorrect={true}
|
|
154
|
+
blurOnSubmit={true}
|
|
155
|
+
keyboardType="default"
|
|
156
|
+
multiline={false}
|
|
157
|
+
numberOfLines={1}
|
|
158
|
+
onBlur={[Function]}
|
|
159
|
+
onChangeText={[MockFunction]}
|
|
160
|
+
onContentSizeChange={[Function]}
|
|
161
|
+
onFocus={[Function]}
|
|
162
|
+
onSubmitEditing={[Function]}
|
|
163
|
+
placeholder="Enter text"
|
|
164
|
+
placeholderTextColor="#686868"
|
|
165
|
+
readOnly={false}
|
|
166
|
+
secureTextEntry={false}
|
|
167
|
+
style={
|
|
168
|
+
{
|
|
169
|
+
"color": "#1C1C1C",
|
|
170
|
+
"flex": 1,
|
|
171
|
+
"fontFamily": "text",
|
|
172
|
+
"fontSize": 16,
|
|
173
|
+
"gap": 10,
|
|
174
|
+
"height": 20,
|
|
175
|
+
"paddingVertical": 0,
|
|
176
|
+
"width": "100%",
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
textContentType="none"
|
|
180
|
+
underlineColorAndroid="transparent"
|
|
181
|
+
value="test value"
|
|
182
|
+
/>
|
|
183
|
+
<View
|
|
184
|
+
accessibilityState={
|
|
185
|
+
{
|
|
186
|
+
"busy": undefined,
|
|
187
|
+
"checked": undefined,
|
|
188
|
+
"disabled": undefined,
|
|
189
|
+
"expanded": undefined,
|
|
190
|
+
"selected": undefined,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
accessibilityValue={
|
|
194
|
+
{
|
|
195
|
+
"max": undefined,
|
|
196
|
+
"min": undefined,
|
|
197
|
+
"now": undefined,
|
|
198
|
+
"text": undefined,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
accessible={true}
|
|
202
|
+
aria-role="button"
|
|
203
|
+
collapsable={false}
|
|
204
|
+
focusable={true}
|
|
205
|
+
onBlur={[Function]}
|
|
206
|
+
onClick={[Function]}
|
|
207
|
+
onFocus={[Function]}
|
|
208
|
+
onResponderGrant={[Function]}
|
|
209
|
+
onResponderMove={[Function]}
|
|
210
|
+
onResponderRelease={[Function]}
|
|
211
|
+
onResponderTerminate={[Function]}
|
|
212
|
+
onResponderTerminationRequest={[Function]}
|
|
213
|
+
onStartShouldSetResponder={[Function]}
|
|
214
|
+
/>
|
|
215
|
+
</View>
|
|
216
|
+
<View
|
|
217
|
+
style={
|
|
218
|
+
{
|
|
219
|
+
"marginTop": 2,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
>
|
|
223
|
+
<Text
|
|
224
|
+
style={
|
|
225
|
+
{
|
|
226
|
+
"color": "#1C1C1C",
|
|
227
|
+
"fontSize": 12,
|
|
228
|
+
"lineHeight": 16,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
>
|
|
232
|
+
Helper text
|
|
233
|
+
</Text>
|
|
234
|
+
</View>
|
|
235
|
+
</View>
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
exports[`TextField snapshots should match snapshot with default props 1`] = `
|
|
239
|
+
<View
|
|
240
|
+
style={
|
|
241
|
+
{
|
|
242
|
+
"flexDirection": "column",
|
|
243
|
+
"width": "100%",
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
>
|
|
247
|
+
<View
|
|
248
|
+
style={
|
|
249
|
+
{
|
|
250
|
+
"alignItems": "center",
|
|
251
|
+
"backgroundColor": "#FFFFFF",
|
|
252
|
+
"borderColor": "#9A9A9A",
|
|
253
|
+
"borderRadius": 4,
|
|
254
|
+
"borderWidth": 1,
|
|
255
|
+
"flexDirection": "row",
|
|
256
|
+
"overflow": "hidden",
|
|
257
|
+
"paddingHorizontal": 12,
|
|
258
|
+
"paddingVertical": 8,
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
>
|
|
262
|
+
<TextInput
|
|
263
|
+
accessibilityHint="Enter text here"
|
|
264
|
+
accessibilityState={
|
|
265
|
+
{
|
|
266
|
+
"disabled": undefined,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
aria-label="Text input field"
|
|
270
|
+
autoCapitalize="sentences"
|
|
271
|
+
autoCorrect={true}
|
|
272
|
+
blurOnSubmit={true}
|
|
273
|
+
keyboardType="default"
|
|
274
|
+
numberOfLines={1}
|
|
275
|
+
onBlur={[Function]}
|
|
276
|
+
onChangeText={[MockFunction]}
|
|
277
|
+
onContentSizeChange={[Function]}
|
|
278
|
+
onFocus={[Function]}
|
|
279
|
+
onSubmitEditing={[Function]}
|
|
280
|
+
placeholderTextColor="#686868"
|
|
281
|
+
secureTextEntry={false}
|
|
282
|
+
style={
|
|
283
|
+
{
|
|
284
|
+
"color": "#1C1C1C",
|
|
285
|
+
"flex": 1,
|
|
286
|
+
"fontFamily": "text",
|
|
287
|
+
"fontSize": 16,
|
|
288
|
+
"gap": 10,
|
|
289
|
+
"height": 20,
|
|
290
|
+
"paddingVertical": 0,
|
|
291
|
+
"width": "100%",
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
textContentType="none"
|
|
295
|
+
underlineColorAndroid="transparent"
|
|
296
|
+
value="test value"
|
|
297
|
+
/>
|
|
298
|
+
</View>
|
|
299
|
+
</View>
|
|
300
|
+
`;
|
|
301
|
+
|
|
302
|
+
exports[`TextField snapshots should match snapshot with error state 1`] = `
|
|
303
|
+
<View
|
|
304
|
+
style={
|
|
305
|
+
{
|
|
306
|
+
"flexDirection": "column",
|
|
307
|
+
"width": "100%",
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
>
|
|
311
|
+
<Text
|
|
312
|
+
style={
|
|
313
|
+
{
|
|
314
|
+
"color": "#1C1C1C",
|
|
315
|
+
"fontSize": 14,
|
|
316
|
+
"fontWeight": 600,
|
|
317
|
+
"lineHeight": 22.4,
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
>
|
|
321
|
+
Error Field
|
|
322
|
+
</Text>
|
|
323
|
+
<View
|
|
324
|
+
style={
|
|
325
|
+
{
|
|
326
|
+
"alignItems": "center",
|
|
327
|
+
"flexDirection": "row",
|
|
328
|
+
"marginVertical": 2,
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
>
|
|
332
|
+
<View
|
|
333
|
+
style={
|
|
334
|
+
{
|
|
335
|
+
"marginLeft": 4,
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
>
|
|
339
|
+
<Text
|
|
340
|
+
style={
|
|
341
|
+
{
|
|
342
|
+
"color": "#BD1111",
|
|
343
|
+
"fontSize": 12,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
>
|
|
347
|
+
This field is required
|
|
348
|
+
</Text>
|
|
349
|
+
</View>
|
|
350
|
+
</View>
|
|
351
|
+
<View
|
|
352
|
+
style={
|
|
353
|
+
{
|
|
354
|
+
"alignItems": "center",
|
|
355
|
+
"backgroundColor": "#FFFFFF",
|
|
356
|
+
"borderColor": "#D33232",
|
|
357
|
+
"borderRadius": 4,
|
|
358
|
+
"borderWidth": 1,
|
|
359
|
+
"flexDirection": "row",
|
|
360
|
+
"overflow": "hidden",
|
|
361
|
+
"paddingHorizontal": 12,
|
|
362
|
+
"paddingVertical": 8,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
>
|
|
366
|
+
<TextInput
|
|
367
|
+
accessibilityHint="Enter text here"
|
|
368
|
+
accessibilityState={
|
|
369
|
+
{
|
|
370
|
+
"disabled": undefined,
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
aria-label="Text input field"
|
|
374
|
+
autoCapitalize="sentences"
|
|
375
|
+
autoCorrect={true}
|
|
376
|
+
blurOnSubmit={true}
|
|
377
|
+
keyboardType="default"
|
|
378
|
+
numberOfLines={1}
|
|
379
|
+
onBlur={[Function]}
|
|
380
|
+
onChangeText={[MockFunction]}
|
|
381
|
+
onContentSizeChange={[Function]}
|
|
382
|
+
onFocus={[Function]}
|
|
383
|
+
onSubmitEditing={[Function]}
|
|
384
|
+
placeholderTextColor="#686868"
|
|
385
|
+
secureTextEntry={false}
|
|
386
|
+
style={
|
|
387
|
+
{
|
|
388
|
+
"color": "#1C1C1C",
|
|
389
|
+
"flex": 1,
|
|
390
|
+
"fontFamily": "text",
|
|
391
|
+
"fontSize": 16,
|
|
392
|
+
"gap": 10,
|
|
393
|
+
"height": 20,
|
|
394
|
+
"paddingVertical": 0,
|
|
395
|
+
"width": "100%",
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
textContentType="none"
|
|
399
|
+
underlineColorAndroid="transparent"
|
|
400
|
+
value=""
|
|
401
|
+
/>
|
|
402
|
+
</View>
|
|
403
|
+
</View>
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
exports[`TextField snapshots should match snapshot with multiline 1`] = `
|
|
407
|
+
<View
|
|
408
|
+
style={
|
|
409
|
+
{
|
|
410
|
+
"flexDirection": "column",
|
|
411
|
+
"width": "100%",
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
>
|
|
415
|
+
<Text
|
|
416
|
+
style={
|
|
417
|
+
{
|
|
418
|
+
"color": "#1C1C1C",
|
|
419
|
+
"fontSize": 14,
|
|
420
|
+
"fontWeight": 600,
|
|
421
|
+
"lineHeight": 22.4,
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
>
|
|
425
|
+
Multiline Field
|
|
426
|
+
</Text>
|
|
427
|
+
<View
|
|
428
|
+
style={
|
|
429
|
+
{
|
|
430
|
+
"alignItems": "center",
|
|
431
|
+
"backgroundColor": "#FFFFFF",
|
|
432
|
+
"borderColor": "#9A9A9A",
|
|
433
|
+
"borderRadius": 4,
|
|
434
|
+
"borderWidth": 1,
|
|
435
|
+
"flexDirection": "row",
|
|
436
|
+
"overflow": "hidden",
|
|
437
|
+
"paddingHorizontal": 12,
|
|
438
|
+
"paddingVertical": 8,
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
>
|
|
442
|
+
<TextInput
|
|
443
|
+
accessibilityHint="Enter text here"
|
|
444
|
+
accessibilityState={
|
|
445
|
+
{
|
|
446
|
+
"disabled": undefined,
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
aria-label="Text input field"
|
|
450
|
+
autoCapitalize="sentences"
|
|
451
|
+
autoCorrect={true}
|
|
452
|
+
blurOnSubmit={true}
|
|
453
|
+
keyboardType="default"
|
|
454
|
+
multiline={true}
|
|
455
|
+
numberOfLines={3}
|
|
456
|
+
onBlur={[Function]}
|
|
457
|
+
onChangeText={[MockFunction]}
|
|
458
|
+
onContentSizeChange={[Function]}
|
|
459
|
+
onFocus={[Function]}
|
|
460
|
+
onSubmitEditing={[Function]}
|
|
461
|
+
placeholderTextColor="#686868"
|
|
462
|
+
secureTextEntry={false}
|
|
463
|
+
style={
|
|
464
|
+
{
|
|
465
|
+
"color": "#1C1C1C",
|
|
466
|
+
"flex": 1,
|
|
467
|
+
"fontFamily": "text",
|
|
468
|
+
"fontSize": 16,
|
|
469
|
+
"gap": 10,
|
|
470
|
+
"height": 120,
|
|
471
|
+
"paddingVertical": 0,
|
|
472
|
+
"width": "100%",
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
textContentType="none"
|
|
476
|
+
underlineColorAndroid="transparent"
|
|
477
|
+
value="line 1\\nline 2"
|
|
478
|
+
/>
|
|
479
|
+
</View>
|
|
480
|
+
</View>
|
|
481
|
+
`;
|
package/src/index.tsx
CHANGED
|
@@ -50,6 +50,7 @@ export * from "./Radio";
|
|
|
50
50
|
export * from "./RadioField";
|
|
51
51
|
export * from "./ScrollView";
|
|
52
52
|
export * from "./SegmentedControl";
|
|
53
|
+
export * from "./SelectBadge";
|
|
53
54
|
export * from "./SelectField";
|
|
54
55
|
export * from "./SideDrawer";
|
|
55
56
|
export * from "./Signature";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {render} from "@testing-library/react-native";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import {ThemeProvider} from "./Theme";
|
|
4
|
+
|
|
5
|
+
export const renderWithTheme = (ui: React.ReactElement) => {
|
|
6
|
+
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const createCommonMocks = () => ({
|
|
10
|
+
onChange: jest.fn(),
|
|
11
|
+
onFocus: jest.fn(),
|
|
12
|
+
onBlur: jest.fn(),
|
|
13
|
+
onEnter: jest.fn(),
|
|
14
|
+
onSubmitEditing: jest.fn(),
|
|
15
|
+
onIconClick: jest.fn(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const setupComponentTest = () => {
|
|
19
|
+
jest.useFakeTimers();
|
|
20
|
+
return createCommonMocks();
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const teardownComponentTest = () => {
|
|
24
|
+
jest.useRealTimers();
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {act, renderHook} from "@testing-library/react-native";
|
|
2
|
+
|
|
3
|
+
import {Unifier} from "./Unifier";
|
|
4
|
+
import {useStoredState} from "./useStoredState";
|
|
5
|
+
|
|
6
|
+
jest.mock("./Unifier", () => ({
|
|
7
|
+
Unifier: {
|
|
8
|
+
storage: {
|
|
9
|
+
getItem: jest.fn(),
|
|
10
|
+
setItem: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
describe("useStoredState", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should return initialValue and isLoading=true on initial render", async () => {
|
|
21
|
+
(Unifier.storage.getItem as jest.Mock).mockImplementation(
|
|
22
|
+
() => new Promise((resolve) => setTimeout(() => resolve("stored value"), 100))
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const {result} = renderHook(() => useStoredState("testKey", "initial value"));
|
|
26
|
+
|
|
27
|
+
expect(result.current[0]).toBe("initial value");
|
|
28
|
+
expect(result.current[2]).toBe(true);
|
|
29
|
+
|
|
30
|
+
await act(async () => {
|
|
31
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(result.current[0]).toBe("stored value");
|
|
35
|
+
expect(result.current[2]).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should update state and storage when setter is called", async () => {
|
|
39
|
+
(Unifier.storage.getItem as jest.Mock).mockResolvedValue("stored value");
|
|
40
|
+
(Unifier.storage.setItem as jest.Mock).mockResolvedValue(undefined);
|
|
41
|
+
|
|
42
|
+
const {result} = renderHook(() => useStoredState("testKey", "initial value"));
|
|
43
|
+
|
|
44
|
+
await act(async () => {
|
|
45
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
await act(async () => {
|
|
49
|
+
await result.current[1]("new value");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(result.current[0]).toBe("new value");
|
|
53
|
+
|
|
54
|
+
expect(Unifier.storage.setItem).toHaveBeenCalledWith("testKey", "new value");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should handle errors when reading from storage", async () => {
|
|
58
|
+
const originalConsoleError = console.error;
|
|
59
|
+
console.error = jest.fn();
|
|
60
|
+
|
|
61
|
+
(Unifier.storage.getItem as jest.Mock).mockRejectedValue(new Error("Storage error"));
|
|
62
|
+
|
|
63
|
+
const {result} = renderHook(() => useStoredState("testKey", "initial value"));
|
|
64
|
+
|
|
65
|
+
await act(async () => {
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
expect(result.current[0]).toBe("initial value");
|
|
70
|
+
expect(result.current[2]).toBe(false);
|
|
71
|
+
expect(console.error).toHaveBeenCalled();
|
|
72
|
+
|
|
73
|
+
console.error = originalConsoleError;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should handle errors when writing to storage", async () => {
|
|
77
|
+
const originalConsoleError = console.error;
|
|
78
|
+
console.error = jest.fn();
|
|
79
|
+
|
|
80
|
+
(Unifier.storage.getItem as jest.Mock).mockResolvedValue("stored value");
|
|
81
|
+
(Unifier.storage.setItem as jest.Mock).mockRejectedValue(new Error("Storage error"));
|
|
82
|
+
|
|
83
|
+
const {result} = renderHook(() => useStoredState("testKey", "initial value"));
|
|
84
|
+
|
|
85
|
+
await act(async () => {
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
await act(async () => {
|
|
90
|
+
await result.current[1]("new value");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(result.current[0]).toBe("stored value");
|
|
94
|
+
expect(console.error).toHaveBeenCalled();
|
|
95
|
+
|
|
96
|
+
console.error = originalConsoleError;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should handle undefined initialValue", async () => {
|
|
100
|
+
(Unifier.storage.getItem as jest.Mock).mockResolvedValue(null);
|
|
101
|
+
|
|
102
|
+
const {result} = renderHook(() => useStoredState("testKey"));
|
|
103
|
+
|
|
104
|
+
expect(result.current[0]).toBeUndefined();
|
|
105
|
+
expect(result.current[2]).toBe(true);
|
|
106
|
+
|
|
107
|
+
await act(async () => {
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
expect(result.current[0]).toBeNull();
|
|
112
|
+
expect(result.current[2]).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should not update state if component unmounts before storage resolves", async () => {
|
|
116
|
+
(Unifier.storage.getItem as jest.Mock).mockImplementation(
|
|
117
|
+
() => new Promise((resolve) => setTimeout(() => resolve("stored value"), 100))
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const {result, unmount} = renderHook(() => useStoredState("testKey", "initial value"));
|
|
121
|
+
|
|
122
|
+
expect(result.current[0]).toBe("initial value");
|
|
123
|
+
expect(result.current[2]).toBe(true);
|
|
124
|
+
|
|
125
|
+
unmount();
|
|
126
|
+
|
|
127
|
+
await act(async () => {
|
|
128
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
expect(result.current[0]).toBe("initial value");
|
|
132
|
+
expect(result.current[2]).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
});
|
package/src/useStoredState.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {useCallback, useEffect, useState} from "react";
|
|
1
|
+
import {useCallback, useEffect, useRef, useState} from "react";
|
|
2
2
|
|
|
3
3
|
import {Unifier} from "./Unifier";
|
|
4
4
|
|
|
5
5
|
export const useStoredState = <T>(
|
|
6
6
|
key: string,
|
|
7
7
|
initialValue?: T
|
|
8
|
-
): [T | undefined, (value: T | undefined) => Promise<void
|
|
8
|
+
): [T | undefined, (value: T | undefined) => Promise<void>, boolean] => {
|
|
9
9
|
const [state, setState] = useState<T | undefined>(initialValue);
|
|
10
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
11
|
+
const isMounted = useRef(true);
|
|
10
12
|
|
|
11
13
|
// Function to fetch data from AsyncStorage
|
|
12
14
|
const fetchData = useCallback(async (): Promise<T | undefined> => {
|
|
@@ -21,19 +23,33 @@ export const useStoredState = <T>(
|
|
|
21
23
|
// Fetch data when the component mounts
|
|
22
24
|
useEffect(() => {
|
|
23
25
|
void fetchData().then((value) => {
|
|
24
|
-
|
|
26
|
+
if (isMounted.current) {
|
|
27
|
+
setState(value);
|
|
28
|
+
setIsLoading(false);
|
|
29
|
+
}
|
|
30
|
+
}).catch((error) => {
|
|
31
|
+
console.error("Error fetching data:", error);
|
|
32
|
+
if (isMounted.current) {
|
|
33
|
+
setIsLoading(false);
|
|
34
|
+
}
|
|
25
35
|
});
|
|
36
|
+
|
|
37
|
+
return () => {
|
|
38
|
+
isMounted.current = false;
|
|
39
|
+
};
|
|
26
40
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
41
|
}, []);
|
|
28
42
|
|
|
29
43
|
const setAsyncStorageState = async (newValue: T | undefined): Promise<void> => {
|
|
30
44
|
try {
|
|
31
45
|
await Unifier.storage.setItem(key, newValue);
|
|
32
|
-
|
|
46
|
+
if (isMounted.current) {
|
|
47
|
+
setState(newValue);
|
|
48
|
+
}
|
|
33
49
|
} catch (error) {
|
|
34
50
|
console.error("Error writing data to AsyncStorage:", error);
|
|
35
51
|
}
|
|
36
52
|
};
|
|
37
53
|
|
|
38
|
-
return [state, setAsyncStorageState];
|
|
54
|
+
return [state, setAsyncStorageState, isLoading];
|
|
39
55
|
};
|