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,271 @@
|
|
|
1
|
+
import {userEvent} from "@testing-library/react-native";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import {TextArea} from "./TextArea";
|
|
5
|
+
import {renderWithTheme} from "./test-utils";
|
|
6
|
+
|
|
7
|
+
describe("TextArea", () => {
|
|
8
|
+
let mockOnChange: jest.Mock;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mockOnChange = jest.fn();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("basic functionality", () => {
|
|
19
|
+
it("should render as multiline text field", () => {
|
|
20
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
21
|
+
<TextArea value="test content" onChange={mockOnChange} />
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const input = getByDisplayValue("test content");
|
|
25
|
+
expect(input.props.multiline).toBe(true);
|
|
26
|
+
expect(input.props.value).toBe("test content");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should render with title", () => {
|
|
30
|
+
const {getByText} = renderWithTheme(
|
|
31
|
+
<TextArea title="Description" value="" onChange={mockOnChange} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(getByText("Description")).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should render with placeholder", () => {
|
|
38
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
39
|
+
<TextArea placeholder="Enter description" value="" onChange={mockOnChange} />
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(getByPlaceholderText("Enter description")).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should render helper text", () => {
|
|
46
|
+
const {getByText} = renderWithTheme(
|
|
47
|
+
<TextArea helperText="Maximum 500 characters" value="" onChange={mockOnChange} />
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(getByText("Maximum 500 characters")).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should render error text", () => {
|
|
54
|
+
const {getByText} = renderWithTheme(
|
|
55
|
+
<TextArea errorText="This field is required" value="" onChange={mockOnChange} />
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(getByText("This field is required")).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should support grow behavior", () => {
|
|
62
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
63
|
+
<TextArea grow value="" onChange={mockOnChange} />
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const input = getByDisplayValue("");
|
|
67
|
+
expect(input.props.multiline).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should be disabled when disabled prop is true", () => {
|
|
71
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
72
|
+
<TextArea disabled value="test" onChange={mockOnChange} />
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const input = getByDisplayValue("test");
|
|
76
|
+
expect(input.props.readOnly).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should always have text type", () => {
|
|
80
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
81
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const input = getByDisplayValue("");
|
|
85
|
+
expect(input.props.keyboardType).toBe("default");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("user interactions", () => {
|
|
90
|
+
it("should call onChange when text is entered", async () => {
|
|
91
|
+
const user = userEvent.setup();
|
|
92
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
93
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const input = getByDisplayValue("");
|
|
97
|
+
await user.type(input, "hello world");
|
|
98
|
+
|
|
99
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
100
|
+
expect(mockOnChange.mock.calls.length).toBeGreaterThan(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should handle multiline text input", async () => {
|
|
104
|
+
const user = userEvent.setup();
|
|
105
|
+
const multilineText = "Line 1\nLine 2\nLine 3";
|
|
106
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
107
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const input = getByDisplayValue("");
|
|
111
|
+
await user.type(input, multilineText);
|
|
112
|
+
|
|
113
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
114
|
+
expect(mockOnChange.mock.calls.length).toBeGreaterThan(0);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("accessibility", () => {
|
|
119
|
+
it("should have correct accessibility properties", () => {
|
|
120
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
121
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const input = getByDisplayValue("");
|
|
125
|
+
expect(input.props.accessibilityHint).toBe("Enter text here");
|
|
126
|
+
expect(input.props["aria-label"]).toBe("Text input field");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should indicate disabled state in accessibility", () => {
|
|
130
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
131
|
+
<TextArea disabled value="" onChange={mockOnChange} />
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const input = getByDisplayValue("");
|
|
135
|
+
expect(input.props.accessibilityState.disabled).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("edge cases", () => {
|
|
140
|
+
it("should handle empty value", () => {
|
|
141
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
142
|
+
<TextArea value="" onChange={mockOnChange} />
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const input = getByDisplayValue("");
|
|
146
|
+
expect(input.props.value).toBe("");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should handle undefined value", () => {
|
|
150
|
+
const {root} = renderWithTheme(
|
|
151
|
+
<TextArea value={undefined} onChange={mockOnChange} />
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect(root).toBeTruthy();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should handle long text values", () => {
|
|
158
|
+
const longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(50);
|
|
159
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
160
|
+
<TextArea value={longText} onChange={mockOnChange} />
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
const input = getByDisplayValue(longText);
|
|
164
|
+
expect(input.props.value).toBe(longText);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should handle text with line breaks", () => {
|
|
168
|
+
const textWithBreaks = "First line\nSecond line\n\nFourth line";
|
|
169
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
170
|
+
<TextArea value={textWithBreaks} onChange={mockOnChange} />
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const input = getByDisplayValue(textWithBreaks);
|
|
174
|
+
expect(input.props.value).toBe(textWithBreaks);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("props inheritance", () => {
|
|
179
|
+
it("should inherit all TextField props except multiline and type", () => {
|
|
180
|
+
const mockOnFocus = jest.fn();
|
|
181
|
+
const mockOnBlur = jest.fn();
|
|
182
|
+
|
|
183
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
184
|
+
<TextArea
|
|
185
|
+
value="test"
|
|
186
|
+
onChange={mockOnChange}
|
|
187
|
+
onFocus={mockOnFocus}
|
|
188
|
+
onBlur={mockOnBlur}
|
|
189
|
+
placeholder="Test placeholder"
|
|
190
|
+
disabled={false}
|
|
191
|
+
rows={5}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const input = getByDisplayValue("test");
|
|
196
|
+
expect(input.props.numberOfLines).toBe(5);
|
|
197
|
+
expect(input.props.onFocus).toBeTruthy();
|
|
198
|
+
expect(input.props.onBlur).toBeTruthy();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("should support inputRef", () => {
|
|
202
|
+
const mockInputRef = jest.fn();
|
|
203
|
+
renderWithTheme(
|
|
204
|
+
<TextArea inputRef={mockInputRef} value="" onChange={mockOnChange} />
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
expect(mockInputRef).toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("snapshots", () => {
|
|
212
|
+
it("should match snapshot with default props", () => {
|
|
213
|
+
const component = renderWithTheme(
|
|
214
|
+
<TextArea value="test content" onChange={mockOnChange} />
|
|
215
|
+
);
|
|
216
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should match snapshot with all props", () => {
|
|
220
|
+
const component = renderWithTheme(
|
|
221
|
+
<TextArea
|
|
222
|
+
title="Description"
|
|
223
|
+
placeholder="Enter description"
|
|
224
|
+
helperText="Maximum 500 characters"
|
|
225
|
+
value="test content"
|
|
226
|
+
onChange={mockOnChange}
|
|
227
|
+
disabled={false}
|
|
228
|
+
grow={true}
|
|
229
|
+
rows={5}
|
|
230
|
+
/>
|
|
231
|
+
);
|
|
232
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should match snapshot when disabled", () => {
|
|
236
|
+
const component = renderWithTheme(
|
|
237
|
+
<TextArea
|
|
238
|
+
title="Disabled TextArea"
|
|
239
|
+
value="disabled content"
|
|
240
|
+
onChange={mockOnChange}
|
|
241
|
+
disabled={true}
|
|
242
|
+
/>
|
|
243
|
+
);
|
|
244
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should match snapshot with error state", () => {
|
|
248
|
+
const component = renderWithTheme(
|
|
249
|
+
<TextArea
|
|
250
|
+
title="Error TextArea"
|
|
251
|
+
value=""
|
|
252
|
+
onChange={mockOnChange}
|
|
253
|
+
errorText="This field is required"
|
|
254
|
+
/>
|
|
255
|
+
);
|
|
256
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should match snapshot with multiline content", () => {
|
|
260
|
+
const component = renderWithTheme(
|
|
261
|
+
<TextArea
|
|
262
|
+
title="Multiline Content"
|
|
263
|
+
value="Line 1\nLine 2\nLine 3"
|
|
264
|
+
onChange={mockOnChange}
|
|
265
|
+
rows={4}
|
|
266
|
+
/>
|
|
267
|
+
);
|
|
268
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import {act, userEvent} from "@testing-library/react-native";
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
import {TextField} from "./TextField";
|
|
5
|
+
import {renderWithTheme} from "./test-utils";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
describe("TextField", () => {
|
|
10
|
+
let mockOnChange: jest.Mock;
|
|
11
|
+
let mockOnFocus: jest.Mock;
|
|
12
|
+
let mockOnBlur: jest.Mock;
|
|
13
|
+
let mockOnEnter: jest.Mock;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
jest.useFakeTimers();
|
|
17
|
+
mockOnChange = jest.fn();
|
|
18
|
+
mockOnFocus = jest.fn();
|
|
19
|
+
mockOnBlur = jest.fn();
|
|
20
|
+
mockOnEnter = jest.fn();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.useRealTimers();
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe("basic rendering", () => {
|
|
29
|
+
it("should render with default props", () => {
|
|
30
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
31
|
+
<TextField value="test value" onChange={mockOnChange} />
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(getByDisplayValue("test value").props.value).toBe("test value");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should render with title", () => {
|
|
38
|
+
const {getByText} = renderWithTheme(
|
|
39
|
+
<TextField title="Test Title" value="" onChange={mockOnChange} />
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(getByText("Test Title")).toBeTruthy();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should render with placeholder", () => {
|
|
46
|
+
const {getByPlaceholderText} = renderWithTheme(
|
|
47
|
+
<TextField placeholder="Enter text" value="" onChange={mockOnChange} />
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
expect(getByPlaceholderText("Enter text")).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should render helper text", () => {
|
|
54
|
+
const {getByText} = renderWithTheme(
|
|
55
|
+
<TextField helperText="This is helper text" value="" onChange={mockOnChange} />
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
expect(getByText("This is helper text")).toBeTruthy();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should render error text", () => {
|
|
62
|
+
const {getByText} = renderWithTheme(
|
|
63
|
+
<TextField errorText="This is an error" value="" onChange={mockOnChange} />
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
expect(getByText("This is an error")).toBeTruthy();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("user interactions", () => {
|
|
71
|
+
it("should call onChange when text is entered", async () => {
|
|
72
|
+
const user = userEvent.setup();
|
|
73
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
74
|
+
<TextField value="" onChange={mockOnChange} />
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const input = getByDisplayValue("");
|
|
78
|
+
await user.type(input, "hello");
|
|
79
|
+
|
|
80
|
+
expect(mockOnChange).toHaveBeenCalled();
|
|
81
|
+
expect(mockOnChange.mock.calls.length).toBeGreaterThan(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should call onFocus when input is focused", async () => {
|
|
85
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
86
|
+
<TextField value="" onChange={mockOnChange} onFocus={mockOnFocus} />
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const input = getByDisplayValue("");
|
|
90
|
+
expect(input.props.onFocus).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("should call onBlur when input loses focus", async () => {
|
|
94
|
+
const user = userEvent.setup();
|
|
95
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
96
|
+
<TextField value="test" onChange={mockOnChange} onBlur={mockOnBlur} />
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const input = getByDisplayValue("test");
|
|
100
|
+
await user.press(input);
|
|
101
|
+
await act(async () => {
|
|
102
|
+
input.props.onBlur();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
expect(mockOnBlur).toHaveBeenCalledTimes(1);
|
|
106
|
+
expect(mockOnBlur.mock.calls[0][0]).toBe("test");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should call onEnter when enter key is pressed", async () => {
|
|
110
|
+
const user = userEvent.setup();
|
|
111
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
112
|
+
<TextField value="" onChange={mockOnChange} onEnter={mockOnEnter} />
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const input = getByDisplayValue("");
|
|
116
|
+
await act(async () => {
|
|
117
|
+
input.props.onSubmitEditing();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(mockOnEnter).toHaveBeenCalledTimes(1);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("field types", () => {
|
|
125
|
+
it("should render email type with correct keyboard", () => {
|
|
126
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
127
|
+
<TextField type="email" value="" onChange={mockOnChange} />
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const input = getByDisplayValue("");
|
|
131
|
+
expect(input.props.keyboardType).toBe("email-address");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should render password type with secure text entry", () => {
|
|
135
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
136
|
+
<TextField type="password" value="" onChange={mockOnChange} />
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
const input = getByDisplayValue("");
|
|
140
|
+
expect(input.props.secureTextEntry).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("should render url type with correct keyboard", () => {
|
|
144
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
145
|
+
<TextField type="url" value="" onChange={mockOnChange} />
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const input = getByDisplayValue("");
|
|
149
|
+
expect(input.props.keyboardType === "url" || input.props.keyboardType === "default").toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should render phoneNumber type with number keyboard", () => {
|
|
153
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
154
|
+
<TextField type="phoneNumber" value="" onChange={mockOnChange} />
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const input = getByDisplayValue("");
|
|
158
|
+
expect(input.props.keyboardType).toBe("number-pad");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should render search type with default keyboard", () => {
|
|
162
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
163
|
+
<TextField type="search" value="" onChange={mockOnChange} />
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const input = getByDisplayValue("");
|
|
167
|
+
expect(input.props.keyboardType).toBe("default");
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("multiline behavior", () => {
|
|
172
|
+
it("should render as multiline when multiline prop is true", () => {
|
|
173
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
174
|
+
<TextField multiline value="" onChange={mockOnChange} />
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
const input = getByDisplayValue("");
|
|
178
|
+
expect(input.props.multiline).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should set number of lines when rows prop is provided", () => {
|
|
182
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
183
|
+
<TextField multiline rows={5} value="" onChange={mockOnChange} />
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const input = getByDisplayValue("");
|
|
187
|
+
expect(input.props.numberOfLines).toBe(5);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should handle grow behavior with multiline", () => {
|
|
191
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
192
|
+
<TextField multiline grow value="" onChange={mockOnChange} />
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const input = getByDisplayValue("");
|
|
196
|
+
expect(input.props.multiline).toBe(true);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
describe("disabled state", () => {
|
|
201
|
+
it("should be read-only when disabled", () => {
|
|
202
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
203
|
+
<TextField disabled value="test" onChange={mockOnChange} />
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const input = getByDisplayValue("test");
|
|
207
|
+
expect(input.props.readOnly).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("should not call onFocus when disabled", async () => {
|
|
211
|
+
const user = userEvent.setup();
|
|
212
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
213
|
+
<TextField disabled value="" onChange={mockOnChange} onFocus={mockOnFocus} />
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const input = getByDisplayValue("");
|
|
217
|
+
expect(input.props.readOnly).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("should not call onBlur when disabled", async () => {
|
|
221
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
222
|
+
<TextField disabled value="test" onChange={mockOnChange} onBlur={mockOnBlur} />
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const input = getByDisplayValue("test");
|
|
226
|
+
await act(async () => {
|
|
227
|
+
input.props.onBlur();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(mockOnBlur).not.toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe("icon functionality", () => {
|
|
235
|
+
it("should render icon when iconName is provided", () => {
|
|
236
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
237
|
+
<TextField iconName="check" value="" onChange={mockOnChange} />
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const input = getByDisplayValue("");
|
|
241
|
+
expect(input).toBeTruthy();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should call onIconClick when icon is pressed", async () => {
|
|
245
|
+
const mockOnIconClick = jest.fn();
|
|
246
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
247
|
+
<TextField iconName="check" onIconClick={mockOnIconClick} value="" onChange={mockOnChange} />
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const input = getByDisplayValue("");
|
|
251
|
+
expect(input).toBeTruthy();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("accessibility", () => {
|
|
256
|
+
it("should have correct accessibility properties", () => {
|
|
257
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
258
|
+
<TextField value="" onChange={mockOnChange} />
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const input = getByDisplayValue("");
|
|
262
|
+
expect(input.props.accessibilityHint).toBe("Enter text here");
|
|
263
|
+
expect(input.props["aria-label"]).toBe("Text input field");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should indicate disabled state in accessibility", () => {
|
|
267
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
268
|
+
<TextField disabled value="" onChange={mockOnChange} />
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const input = getByDisplayValue("");
|
|
272
|
+
expect(input.props.accessibilityState.disabled).toBe(true);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("auto-complete and text content", () => {
|
|
277
|
+
it("should set autoComplete property", () => {
|
|
278
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
279
|
+
<TextField autoComplete="username" value="" onChange={mockOnChange} />
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
const input = getByDisplayValue("");
|
|
283
|
+
expect(input).toBeTruthy();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should handle text content type for email", () => {
|
|
287
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
288
|
+
<TextField type="email" value="" onChange={mockOnChange} />
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const input = getByDisplayValue("");
|
|
292
|
+
expect(input.props.textContentType).toBe("emailAddress");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should handle text content type for password", () => {
|
|
296
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
297
|
+
<TextField type="password" value="" onChange={mockOnChange} />
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const input = getByDisplayValue("");
|
|
301
|
+
expect(input.props.textContentType).toBe("password");
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
describe("edge cases", () => {
|
|
306
|
+
it("should handle empty value", () => {
|
|
307
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
308
|
+
<TextField value="" onChange={mockOnChange} />
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
const input = getByDisplayValue("");
|
|
312
|
+
expect(input.props.value).toBe("");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should handle undefined value", () => {
|
|
316
|
+
const {root} = renderWithTheme(
|
|
317
|
+
<TextField value={undefined} onChange={mockOnChange} />
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(root).toBeTruthy();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it("should handle long text values", () => {
|
|
324
|
+
const longText = "a".repeat(1000);
|
|
325
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
326
|
+
<TextField value={longText} onChange={mockOnChange} />
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const input = getByDisplayValue(longText);
|
|
330
|
+
expect(input.props.value).toBe(longText);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it("should handle special characters", () => {
|
|
334
|
+
const specialText = "!@#$%^&*()_+-=[]{}|;':\",./<>?";
|
|
335
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
336
|
+
<TextField value={specialText} onChange={mockOnChange} />
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const input = getByDisplayValue(specialText);
|
|
340
|
+
expect(input.props.value).toBe(specialText);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("return key behavior", () => {
|
|
345
|
+
it("should set return key type", () => {
|
|
346
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
347
|
+
<TextField returnKeyType="done" value="" onChange={mockOnChange} />
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const input = getByDisplayValue("");
|
|
351
|
+
expect(input.props.enterKeyHint).toBe("done");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should handle blur on submit", () => {
|
|
355
|
+
const {getByDisplayValue} = renderWithTheme(
|
|
356
|
+
<TextField blurOnSubmit={false} value="" onChange={mockOnChange} />
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
const input = getByDisplayValue("");
|
|
360
|
+
expect(input.props.blurOnSubmit).toBe(false);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe("input ref", () => {
|
|
365
|
+
it("should call inputRef with the input reference", () => {
|
|
366
|
+
const mockInputRef = jest.fn();
|
|
367
|
+
renderWithTheme(
|
|
368
|
+
<TextField inputRef={mockInputRef} value="" onChange={mockOnChange} />
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
expect(mockInputRef).toHaveBeenCalled();
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
describe("snapshots", () => {
|
|
376
|
+
it("should match snapshot with default props", () => {
|
|
377
|
+
const component = renderWithTheme(
|
|
378
|
+
<TextField value="test value" onChange={mockOnChange} />
|
|
379
|
+
);
|
|
380
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should match snapshot with all props", () => {
|
|
384
|
+
const component = renderWithTheme(
|
|
385
|
+
<TextField
|
|
386
|
+
title="Test Title"
|
|
387
|
+
placeholder="Enter text"
|
|
388
|
+
helperText="Helper text"
|
|
389
|
+
errorText="Error text"
|
|
390
|
+
iconName="check"
|
|
391
|
+
onIconClick={jest.fn()}
|
|
392
|
+
value="test value"
|
|
393
|
+
onChange={mockOnChange}
|
|
394
|
+
onFocus={mockOnFocus}
|
|
395
|
+
onBlur={mockOnBlur}
|
|
396
|
+
onEnter={mockOnEnter}
|
|
397
|
+
disabled={false}
|
|
398
|
+
multiline={false}
|
|
399
|
+
type="text"
|
|
400
|
+
/>
|
|
401
|
+
);
|
|
402
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
it("should match snapshot when disabled", () => {
|
|
406
|
+
const component = renderWithTheme(
|
|
407
|
+
<TextField
|
|
408
|
+
title="Disabled Field"
|
|
409
|
+
value="disabled value"
|
|
410
|
+
onChange={mockOnChange}
|
|
411
|
+
disabled={true}
|
|
412
|
+
/>
|
|
413
|
+
);
|
|
414
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("should match snapshot with multiline", () => {
|
|
418
|
+
const component = renderWithTheme(
|
|
419
|
+
<TextField
|
|
420
|
+
title="Multiline Field"
|
|
421
|
+
value="line 1\nline 2"
|
|
422
|
+
onChange={mockOnChange}
|
|
423
|
+
multiline={true}
|
|
424
|
+
rows={3}
|
|
425
|
+
/>
|
|
426
|
+
);
|
|
427
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("should match snapshot with error state", () => {
|
|
431
|
+
const component = renderWithTheme(
|
|
432
|
+
<TextField
|
|
433
|
+
title="Error Field"
|
|
434
|
+
value=""
|
|
435
|
+
onChange={mockOnChange}
|
|
436
|
+
errorText="This field is required"
|
|
437
|
+
/>
|
|
438
|
+
);
|
|
439
|
+
expect(component.toJSON()).toMatchSnapshot();
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
});
|