@jobber/components-native 0.21.1 → 0.22.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.
Files changed (40) hide show
  1. package/dist/src/InputText/InputText.js +138 -0
  2. package/dist/src/InputText/InputText.style.js +20 -0
  3. package/dist/src/InputText/context/InputAccessoriesContext.js +17 -0
  4. package/dist/src/InputText/context/InputAccessoriesProvider.js +73 -0
  5. package/dist/src/InputText/context/InputAccessoriesProvider.style.js +21 -0
  6. package/dist/src/InputText/context/InputAccessory.style.js +16 -0
  7. package/dist/src/InputText/context/index.js +2 -0
  8. package/dist/src/InputText/context/types.js +1 -0
  9. package/dist/src/InputText/index.js +2 -0
  10. package/dist/src/hooks/index.js +1 -0
  11. package/dist/src/hooks/useFormController.js +38 -0
  12. package/dist/src/index.js +1 -0
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/types/src/InputText/InputText.d.ts +165 -0
  15. package/dist/types/src/InputText/InputText.style.d.ts +16 -0
  16. package/dist/types/src/InputText/context/InputAccessoriesContext.d.ts +4 -0
  17. package/dist/types/src/InputText/context/InputAccessoriesProvider.d.ts +4 -0
  18. package/dist/types/src/InputText/context/InputAccessoriesProvider.style.d.ts +17 -0
  19. package/dist/types/src/InputText/context/InputAccessory.style.d.ts +14 -0
  20. package/dist/types/src/InputText/context/index.d.ts +2 -0
  21. package/dist/types/src/InputText/context/types.d.ts +23 -0
  22. package/dist/types/src/InputText/index.d.ts +3 -0
  23. package/dist/types/src/hooks/index.d.ts +1 -0
  24. package/dist/types/src/hooks/useFormController.d.ts +12 -0
  25. package/dist/types/src/index.d.ts +1 -0
  26. package/package.json +4 -2
  27. package/src/InputText/InputText.style.ts +25 -0
  28. package/src/InputText/InputText.test.tsx +534 -0
  29. package/src/InputText/InputText.tsx +483 -0
  30. package/src/InputText/context/InputAccessoriesContext.ts +21 -0
  31. package/src/InputText/context/InputAccessoriesProvider.style.tsx +23 -0
  32. package/src/InputText/context/InputAccessoriesProvider.test.tsx +84 -0
  33. package/src/InputText/context/InputAccessoriesProvider.tsx +121 -0
  34. package/src/InputText/context/InputAccessory.style.ts +17 -0
  35. package/src/InputText/context/index.ts +2 -0
  36. package/src/InputText/context/types.ts +28 -0
  37. package/src/InputText/index.ts +3 -0
  38. package/src/hooks/index.ts +1 -0
  39. package/src/hooks/useFormController.ts +68 -0
  40. package/src/index.ts +1 -0
@@ -0,0 +1,534 @@
1
+ import React from "react";
2
+ import {
3
+ RenderAPI,
4
+ fireEvent,
5
+ render,
6
+ waitFor,
7
+ } from "@testing-library/react-native";
8
+ import { useIntl } from "react-intl";
9
+ import { Platform, TextStyle } from "react-native";
10
+ import { InputText, InputTextProps } from "./InputText";
11
+ import { InputAccessoriesProvider } from "./context";
12
+ import { messages as ClearMessages } from "../InputFieldWrapper/components/ClearAction/messages";
13
+ import {
14
+ Clearable,
15
+ InputFieldWrapperProps,
16
+ commonInputStyles,
17
+ } from "../InputFieldWrapper";
18
+
19
+ const MockInputFieldWrapper = jest.fn();
20
+ jest.mock("../InputFieldWrapper", () => ({
21
+ ...jest.requireActual("../InputFieldWrapper"),
22
+ InputFieldWrapper: function Mock(props: InputFieldWrapperProps) {
23
+ MockInputFieldWrapper(props);
24
+ return jest.requireActual("../InputFieldWrapper").InputFieldWrapper(props);
25
+ },
26
+ }));
27
+
28
+ const mockLabel = { label: "$" };
29
+
30
+ const android = "android";
31
+ const ios = "ios";
32
+
33
+ const platforms: Array<[typeof android | typeof ios, string]> = [
34
+ ["ios", "done"],
35
+ ["android", "next"],
36
+ ];
37
+
38
+ function renderInputText(props: InputTextProps): RenderAPI {
39
+ return render(<InputText {...props} />);
40
+ }
41
+
42
+ // eslint-disable-next-line max-statements
43
+ describe("InputText", () => {
44
+ describe("InputFieldWrapper gets the expected props", () => {
45
+ it("renders an invalid InputText", () => {
46
+ const props = { invalid: true };
47
+ renderInputText(props);
48
+ expect(MockInputFieldWrapper).toHaveBeenCalledWith(
49
+ expect.objectContaining(props),
50
+ );
51
+ });
52
+
53
+ it("renders an invalid InputText with non-empty string", () => {
54
+ const props = { invalid: "Always invalid" };
55
+ renderInputText(props);
56
+ expect(MockInputFieldWrapper).toHaveBeenCalledWith(
57
+ expect.objectContaining(props),
58
+ );
59
+ });
60
+
61
+ it("renders a valid InputText with empty string", () => {
62
+ const props = { invalid: "" };
63
+ renderInputText(props);
64
+ expect(MockInputFieldWrapper).toHaveBeenCalledWith(
65
+ expect.objectContaining(props),
66
+ );
67
+ });
68
+
69
+ it("renders a disabled InputText", () => {
70
+ const props = { disabled: true };
71
+ renderInputText(props);
72
+ expect(MockInputFieldWrapper).toHaveBeenCalledWith(
73
+ expect.objectContaining(props),
74
+ );
75
+ });
76
+
77
+ it("renders a InputText with placeholder", () => {
78
+ const props = { placeholder: "Foobar" };
79
+ renderInputText(props);
80
+ expect(MockInputFieldWrapper).toHaveBeenCalledWith(
81
+ expect.objectContaining(props),
82
+ );
83
+ });
84
+ });
85
+
86
+ it("renders a InputText with defaultValue", () => {
87
+ const defaultValue = "Default Foobar";
88
+ const { getByDisplayValue } = renderInputText({
89
+ defaultValue,
90
+ });
91
+ expect(getByDisplayValue(defaultValue)).toBeTruthy();
92
+ });
93
+
94
+ it("renders a InputText with autoCompleteType", () => {
95
+ const { getByTestId } = renderInputText({
96
+ testID: "InputText",
97
+ autoComplete: "name",
98
+ });
99
+ expect(getByTestId("InputText").props.autoComplete).toBe("name");
100
+ });
101
+
102
+ describe("autoCorrect prop", () => {
103
+ it("defaults the autoCorrect prop to undefined", () => {
104
+ const { getByTestId } = renderInputText({ testID: "Text" });
105
+
106
+ expect(getByTestId("Text").props.autoCorrect).toEqual(undefined);
107
+ });
108
+
109
+ describe("setting the autoCorrect field", () => {
110
+ it("allows setting the field when a value is passed in", () => {
111
+ const { getByTestId } = renderInputText({
112
+ testID: "Text",
113
+ autoCorrect: true,
114
+ });
115
+
116
+ expect(getByTestId("Text").props.autoCorrect).toBe(true);
117
+ });
118
+ });
119
+ });
120
+
121
+ describe("Suffix and Prefix", () => {
122
+ it("renders a prefix icon when specified", () => {
123
+ const { getByTestId } = renderInputText({ prefix: { icon: "invoice" } });
124
+ expect(getByTestId("invoice")).toBeDefined();
125
+ });
126
+
127
+ it("renders a suffix icon when specified", () => {
128
+ const { getByTestId } = renderInputText({ suffix: { icon: "invoice" } });
129
+ expect(getByTestId("invoice")).toBeDefined();
130
+ });
131
+ });
132
+
133
+ describe("when given a value", () => {
134
+ const value = "Foobar";
135
+
136
+ it("renders the InputText with the value", () => {
137
+ const { getByDisplayValue } = renderInputText({ value: value });
138
+ expect(getByDisplayValue(value)).toBeTruthy();
139
+ });
140
+
141
+ it("renders a prefix label", () => {
142
+ const { getByText } = renderInputText({
143
+ prefix: mockLabel,
144
+ value: value,
145
+ });
146
+ expect(getByText(mockLabel.label)).toBeTruthy();
147
+ });
148
+
149
+ it("renders a suffix label", () => {
150
+ const { getByText } = renderInputText({
151
+ suffix: mockLabel,
152
+ value: value,
153
+ });
154
+ expect(getByText(mockLabel.label)).toBeTruthy();
155
+ });
156
+ });
157
+
158
+ describe("when no value", () => {
159
+ it("does not render a prefix label", () => {
160
+ const { queryByText } = renderInputText({ prefix: mockLabel });
161
+ expect(queryByText(mockLabel.label)).toBeNull();
162
+ });
163
+
164
+ it("does not render a suffix label", () => {
165
+ const { queryByText } = renderInputText({ suffix: mockLabel });
166
+ expect(queryByText(mockLabel.label)).toBeNull();
167
+ });
168
+ });
169
+
170
+ describe("Callbacks", () => {
171
+ it("calls onChangeText callback on input", () => {
172
+ const changeCallback = jest.fn();
173
+ const a11yLabel = "Test InputText";
174
+ const { getByLabelText } = render(
175
+ <InputText
176
+ onChangeText={changeCallback}
177
+ value="Initial value"
178
+ accessibilityLabel={a11yLabel}
179
+ />,
180
+ );
181
+
182
+ fireEvent.changeText(getByLabelText(a11yLabel), "New value");
183
+ expect(changeCallback).toHaveBeenCalledWith("New value");
184
+ });
185
+
186
+ describe("onBlur", () => {
187
+ it("calls onBlur callback on input", () => {
188
+ const blurCallback = jest.fn();
189
+ const a11yLabel = "Test InputText";
190
+ const { getByLabelText } = render(
191
+ <InputText
192
+ onBlur={blurCallback}
193
+ value="Initial value"
194
+ accessibilityLabel={a11yLabel}
195
+ />,
196
+ );
197
+
198
+ fireEvent(getByLabelText(a11yLabel), "blur");
199
+ expect(blurCallback).toHaveBeenCalledTimes(1);
200
+ });
201
+
202
+ it("trims whitespace on blur", () => {
203
+ const onChangeHandler = jest.fn();
204
+ const a11yLabel = "Test InputText";
205
+ const whiteSpacesValue = " Hello World ";
206
+ const { getByLabelText } = render(
207
+ <InputText
208
+ value={whiteSpacesValue}
209
+ accessibilityLabel={a11yLabel}
210
+ onChangeText={onChangeHandler}
211
+ />,
212
+ );
213
+
214
+ fireEvent(getByLabelText(a11yLabel), "blur");
215
+ expect(onChangeHandler).toHaveBeenCalledWith("Hello World");
216
+ });
217
+ });
218
+
219
+ it("calls onFocus callback on focus", () => {
220
+ const focusCallback = jest.fn();
221
+ const a11yLabel = "Test InputText";
222
+ const { getByLabelText } = render(
223
+ <InputText onFocus={focusCallback} accessibilityLabel={a11yLabel} />,
224
+ );
225
+
226
+ fireEvent(getByLabelText(a11yLabel), "onFocus");
227
+ expect(focusCallback).toHaveBeenCalled();
228
+ });
229
+ });
230
+
231
+ describe("Validations", () => {
232
+ it("calls validations on blur", async () => {
233
+ const validationMock = jest.fn();
234
+ const a11yLabel = "Test InputText";
235
+ const { getByLabelText } = render(
236
+ <InputText
237
+ value="Initial value"
238
+ accessibilityLabel={a11yLabel}
239
+ validations={{ validate: validationMock }}
240
+ />,
241
+ );
242
+
243
+ await waitFor(() => {
244
+ fireEvent(getByLabelText(a11yLabel), "blur");
245
+ });
246
+
247
+ expect(validationMock).toHaveBeenCalled();
248
+ });
249
+
250
+ it("renders validation messages after blur", async () => {
251
+ const a11yLabel = "Test InputText";
252
+ const { getByLabelText, getByText } = render(
253
+ <InputText
254
+ accessibilityLabel={a11yLabel}
255
+ validations={{ required: { value: true, message: "Foobar" } }}
256
+ />,
257
+ );
258
+
259
+ await waitFor(() => {
260
+ fireEvent(getByLabelText(a11yLabel), "blur");
261
+ });
262
+
263
+ expect(
264
+ getByText("Foobar", { includeHiddenElements: true }),
265
+ ).toBeDefined();
266
+ });
267
+ });
268
+
269
+ describe("accessibilityLabel", () => {
270
+ it("uses accessibilityLabel if specified", () => {
271
+ const changeCallback = jest.fn();
272
+ const { getByLabelText } = render(
273
+ <InputText
274
+ onChangeText={changeCallback}
275
+ value=""
276
+ placeholder="placeholder"
277
+ accessibilityLabel="accessibilityLabel"
278
+ />,
279
+ );
280
+
281
+ expect(getByLabelText("accessibilityLabel")).toBeTruthy();
282
+ });
283
+
284
+ it("uses placeholder if unspecified", () => {
285
+ const changeCallback = jest.fn();
286
+ const { getByLabelText } = render(
287
+ <InputText
288
+ onChangeText={changeCallback}
289
+ value=""
290
+ placeholder="placeholder"
291
+ />,
292
+ );
293
+
294
+ expect(getByLabelText("placeholder")).toBeTruthy();
295
+ });
296
+ });
297
+
298
+ describe("clearable button", () => {
299
+ function setup({
300
+ clearable,
301
+ value,
302
+ accessibilityLabel,
303
+ onChangeText,
304
+ disabled,
305
+ }: {
306
+ clearable: Clearable;
307
+ value?: string;
308
+ accessibilityLabel?: string;
309
+ onChangeText?: () => void;
310
+ disabled?: boolean;
311
+ }): RenderAPI {
312
+ return render(
313
+ <InputText
314
+ clearable={clearable}
315
+ value={value}
316
+ placeholder="placeholder"
317
+ accessibilityLabel={accessibilityLabel}
318
+ onChangeText={onChangeText}
319
+ disabled={disabled}
320
+ />,
321
+ );
322
+ }
323
+
324
+ describe("clearable set to always", () => {
325
+ it("renders the clear button when there is a value", () => {
326
+ const { formatMessage } = useIntl();
327
+ const { getByLabelText } = setup({
328
+ clearable: "always",
329
+ value: "test value",
330
+ });
331
+
332
+ const clearButton = getByLabelText(
333
+ formatMessage(ClearMessages.clearTextLabel),
334
+ );
335
+ expect(clearButton).toBeDefined();
336
+ });
337
+
338
+ it("doesn't render the clear button if there is no value", () => {
339
+ const { formatMessage } = useIntl();
340
+ const { queryByLabelText } = setup({ clearable: "always", value: "" });
341
+
342
+ const clearButton = queryByLabelText(
343
+ formatMessage(ClearMessages.clearTextLabel),
344
+ );
345
+ expect(clearButton).toBeNull();
346
+ });
347
+
348
+ it("renders the clear button when there is a value and you are focused", async () => {
349
+ const { formatMessage } = useIntl();
350
+ const { getByLabelText } = setup({
351
+ clearable: "always",
352
+ value: "test value",
353
+ accessibilityLabel: "clearableTest",
354
+ });
355
+ await fireEvent(getByLabelText("clearableTest"), "focus");
356
+
357
+ const clearButton = getByLabelText(
358
+ formatMessage(ClearMessages.clearTextLabel),
359
+ );
360
+ expect(clearButton).toBeDefined();
361
+ });
362
+ });
363
+
364
+ describe("clearable set to when editing", () => {
365
+ it("renders the clear button when there is a value and it is being edited", async () => {
366
+ const { formatMessage } = useIntl();
367
+ const { getByLabelText } = setup({
368
+ clearable: "while-editing",
369
+ value: "Test Input",
370
+ accessibilityLabel: "clearableTest",
371
+ });
372
+ await fireEvent(getByLabelText("clearableTest"), "focus");
373
+
374
+ const clearButton = getByLabelText(
375
+ formatMessage(ClearMessages.clearTextLabel),
376
+ );
377
+ expect(clearButton).toBeDefined();
378
+ });
379
+
380
+ it("doesn't render the clear button if the user isn't editing", () => {
381
+ const { formatMessage } = useIntl();
382
+ const { queryByLabelText } = setup({
383
+ clearable: "while-editing",
384
+ value: "Test value",
385
+ });
386
+
387
+ const clearButton = queryByLabelText(
388
+ formatMessage(ClearMessages.clearTextLabel),
389
+ );
390
+ expect(clearButton).toBeNull();
391
+ });
392
+ });
393
+
394
+ describe("clearable set to never", () => {
395
+ it("shouldn't show the button when it is being edited", async () => {
396
+ const { formatMessage } = useIntl();
397
+ const { getByLabelText, queryByLabelText } = setup({
398
+ clearable: "never",
399
+ value: "Test Input",
400
+ accessibilityLabel: "clearableTest",
401
+ });
402
+ await fireEvent(getByLabelText("clearableTest"), "focus");
403
+
404
+ const clearButton = queryByLabelText(
405
+ formatMessage(ClearMessages.clearTextLabel),
406
+ );
407
+ expect(clearButton).toBeNull();
408
+ });
409
+
410
+ it("shouldn't show the clear button when the user is not editing", () => {
411
+ const { formatMessage } = useIntl();
412
+ const { queryByLabelText } = setup({
413
+ clearable: "never",
414
+ value: "Test Input",
415
+ accessibilityLabel: "clearableTest",
416
+ });
417
+
418
+ const clearButton = queryByLabelText(
419
+ formatMessage(ClearMessages.clearTextLabel),
420
+ );
421
+ expect(clearButton).toBeNull();
422
+ });
423
+ });
424
+
425
+ it("should clear the value when the clear button is pressed", () => {
426
+ const { formatMessage } = useIntl();
427
+ const onChangeHandler = jest.fn();
428
+ const { getByLabelText } = setup({
429
+ clearable: "always",
430
+ value: "Test Input",
431
+ onChangeText: onChangeHandler,
432
+ });
433
+
434
+ const clearButton = getByLabelText(
435
+ formatMessage(ClearMessages.clearTextLabel),
436
+ );
437
+ fireEvent(clearButton, "press");
438
+ expect(onChangeHandler).toHaveBeenCalledWith("");
439
+ });
440
+
441
+ it("shouldn't render the clear button if the input is disabled", () => {
442
+ const { formatMessage } = useIntl();
443
+ const { queryByLabelText } = setup({
444
+ clearable: "always",
445
+ value: "Test value",
446
+ accessibilityLabel: "clearableTest",
447
+ disabled: true,
448
+ });
449
+
450
+ const clearButton = queryByLabelText(
451
+ formatMessage(ClearMessages.clearTextLabel),
452
+ );
453
+ expect(clearButton).toBeNull();
454
+ });
455
+ });
456
+
457
+ describe.each(platforms)(
458
+ "%s returnKeyLabel",
459
+ (platform, expectedReturnKeyType) => {
460
+ it("doesn't set a returnKeyType for multiline inputs", () => {
461
+ Platform.OS = platform;
462
+
463
+ const { getByTestId } = renderInputText({
464
+ multiline: true,
465
+ testID: "multiline",
466
+ });
467
+ const rnTextInput = getByTestId("multiline");
468
+
469
+ expect(rnTextInput.props.returnKeyType).toBeUndefined();
470
+ });
471
+
472
+ it("sets the correct returnKeyType when in the InputAccessoryContext", () => {
473
+ Platform.OS = platform;
474
+
475
+ const { getByTestId } = render(
476
+ <InputAccessoriesProvider>
477
+ <InputText testID="returnKeyType" />
478
+ </InputAccessoriesProvider>,
479
+ );
480
+
481
+ const rnTextInput = getByTestId("returnKeyType");
482
+
483
+ expect(rnTextInput.props.returnKeyType).toBe(expectedReturnKeyType);
484
+ });
485
+ },
486
+ );
487
+
488
+ describe("Style", () => {
489
+ it("displays default text styles", () => {
490
+ const defaultValue = "Default Foobar";
491
+ const { getByDisplayValue } = renderInputText({ defaultValue });
492
+ const textInput = getByDisplayValue(defaultValue);
493
+ expect(textInput.props.style).toContainEqual({
494
+ ...commonInputStyles.input,
495
+ });
496
+ });
497
+
498
+ it("displays overriden text styles", () => {
499
+ const defaultValue = "Default Foobar";
500
+ const styleOverride = {
501
+ inputText: {
502
+ fontFamily: "inter-extrabold",
503
+ fontSize: 50,
504
+ lineHeight: 60,
505
+ letterSpacing: 10,
506
+ color: "purple",
507
+ },
508
+ };
509
+ const { getByDisplayValue } = renderInputText({
510
+ defaultValue,
511
+ styleOverride,
512
+ });
513
+ const textInput = getByDisplayValue(defaultValue);
514
+ const flattenedStyle = textInput.props.style.reduce(
515
+ (style: TextStyle, additionalStyles: TextStyle) => ({
516
+ ...style,
517
+ ...additionalStyles,
518
+ }),
519
+ {},
520
+ );
521
+ expect(styleOverride.inputText.fontFamily).toEqual(
522
+ flattenedStyle.fontFamily,
523
+ );
524
+ expect(styleOverride.inputText.fontSize).toEqual(flattenedStyle.fontSize);
525
+ expect(styleOverride.inputText.lineHeight).toEqual(
526
+ flattenedStyle.lineHeight,
527
+ );
528
+ expect(styleOverride.inputText.letterSpacing).toEqual(
529
+ flattenedStyle.letterSpacing,
530
+ );
531
+ expect(styleOverride.inputText.color).toEqual(flattenedStyle.color);
532
+ });
533
+ });
534
+ });