@khanacademy/wonder-blocks-form 3.1.5 → 3.1.7
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/CHANGELOG.md +21 -0
- package/dist/es/index.js +8 -8
- package/package.json +7 -7
- package/src/components/__tests__/field-heading.test.js +35 -40
- package/src/components/__tests__/labeled-text-field.test.js +87 -97
- package/src/components/__tests__/text-field.test.js +69 -74
- package/src/components/labeled-text-field.js +5 -11
- package/src/components/text-field.js +5 -12
- package/dist/index.js +0 -1737
- package/dist/index.js.flow +0 -2
- package/docs.md +0 -5
|
@@ -1,35 +1,27 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import {
|
|
4
|
-
import "
|
|
3
|
+
import {fireEvent, render, screen} from "@testing-library/react";
|
|
4
|
+
import userEvent from "@testing-library/user-event";
|
|
5
5
|
|
|
6
6
|
import TextField from "../text-field.js";
|
|
7
7
|
|
|
8
|
-
const wait = (delay: number = 0) =>
|
|
9
|
-
new Promise((resolve, reject) => {
|
|
10
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
11
|
-
return setTimeout(resolve, delay);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
8
|
describe("TextField", () => {
|
|
15
9
|
it("textfield is focused", () => {
|
|
16
10
|
// Arrange
|
|
17
|
-
|
|
18
|
-
<TextField id="tf-1" value="" onChange={() => {}} />,
|
|
19
|
-
);
|
|
11
|
+
render(<TextField id="tf-1" value="" onChange={() => {}} />);
|
|
20
12
|
|
|
21
13
|
// Act
|
|
22
|
-
|
|
14
|
+
userEvent.tab();
|
|
23
15
|
|
|
24
16
|
// Assert
|
|
25
|
-
expect(
|
|
17
|
+
expect(screen.getByRole("textbox")).toHaveFocus();
|
|
26
18
|
});
|
|
27
19
|
|
|
28
20
|
it("onFocus is called after textfield is focused", () => {
|
|
29
21
|
// Arrange
|
|
30
22
|
const handleOnFocus = jest.fn(() => {});
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
render(
|
|
33
25
|
<TextField
|
|
34
26
|
id={"tf-1"}
|
|
35
27
|
value="TextIsLongerThan8"
|
|
@@ -39,7 +31,7 @@ describe("TextField", () => {
|
|
|
39
31
|
);
|
|
40
32
|
|
|
41
33
|
// Act
|
|
42
|
-
|
|
34
|
+
userEvent.tab();
|
|
43
35
|
|
|
44
36
|
// Assert
|
|
45
37
|
expect(handleOnFocus).toHaveBeenCalled();
|
|
@@ -47,24 +39,24 @@ describe("TextField", () => {
|
|
|
47
39
|
|
|
48
40
|
it("textfield is blurred", async () => {
|
|
49
41
|
// Arrange
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
render(<TextField id="tf-1" value="" onChange={() => {}} />);
|
|
43
|
+
|
|
44
|
+
// focus
|
|
45
|
+
userEvent.tab();
|
|
53
46
|
|
|
54
47
|
// Act
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
wrapper.simulate("blur");
|
|
48
|
+
// blur
|
|
49
|
+
userEvent.tab();
|
|
58
50
|
|
|
59
51
|
// Assert
|
|
60
|
-
expect(
|
|
52
|
+
expect(screen.getByRole("textbox")).not.toHaveFocus();
|
|
61
53
|
});
|
|
62
54
|
|
|
63
55
|
it("onBlur is called after textfield is blurred", async () => {
|
|
64
56
|
// Arrange
|
|
65
57
|
const handleOnBlur = jest.fn(() => {});
|
|
66
58
|
|
|
67
|
-
|
|
59
|
+
render(
|
|
68
60
|
<TextField
|
|
69
61
|
id={"tf-1"}
|
|
70
62
|
value="TextIsLongerThan8"
|
|
@@ -73,10 +65,12 @@ describe("TextField", () => {
|
|
|
73
65
|
/>,
|
|
74
66
|
);
|
|
75
67
|
|
|
68
|
+
// focus
|
|
69
|
+
userEvent.tab();
|
|
70
|
+
|
|
76
71
|
// Act
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
wrapper.simulate("blur");
|
|
72
|
+
// blur
|
|
73
|
+
userEvent.tab();
|
|
80
74
|
|
|
81
75
|
// Assert
|
|
82
76
|
expect(handleOnBlur).toHaveBeenCalled();
|
|
@@ -87,13 +81,11 @@ describe("TextField", () => {
|
|
|
87
81
|
const id: string = "tf-1";
|
|
88
82
|
|
|
89
83
|
// Act
|
|
90
|
-
|
|
91
|
-
<TextField id={id} value="" onChange={() => {}} />,
|
|
92
|
-
);
|
|
84
|
+
render(<TextField id={id} value="" onChange={() => {}} />);
|
|
93
85
|
|
|
94
86
|
// Assert
|
|
95
|
-
const input =
|
|
96
|
-
expect(input).
|
|
87
|
+
const input = screen.getByRole("textbox");
|
|
88
|
+
expect(input).toHaveAttribute("id", id);
|
|
97
89
|
});
|
|
98
90
|
|
|
99
91
|
it("type prop is passed to the input element", () => {
|
|
@@ -101,13 +93,14 @@ describe("TextField", () => {
|
|
|
101
93
|
const type = "number";
|
|
102
94
|
|
|
103
95
|
// Act
|
|
104
|
-
|
|
96
|
+
render(
|
|
105
97
|
<TextField id={"tf-1"} type={type} value="" onChange={() => {}} />,
|
|
106
98
|
);
|
|
107
99
|
|
|
108
100
|
// Assert
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
// NOTE: The implicit role for input[type=number] is "spinbutton".
|
|
102
|
+
const input = screen.getByRole("spinbutton");
|
|
103
|
+
expect(input).toHaveAttribute("type", type);
|
|
111
104
|
});
|
|
112
105
|
|
|
113
106
|
it("value prop is passed to the input element", () => {
|
|
@@ -115,18 +108,16 @@ describe("TextField", () => {
|
|
|
115
108
|
const value = "Text";
|
|
116
109
|
|
|
117
110
|
// Act
|
|
118
|
-
|
|
119
|
-
<TextField id={"tf-1"} value={value} onChange={() => {}} />,
|
|
120
|
-
);
|
|
111
|
+
render(<TextField id={"tf-1"} value={value} onChange={() => {}} />);
|
|
121
112
|
|
|
122
113
|
// Assert
|
|
123
|
-
const input =
|
|
124
|
-
expect(input).
|
|
114
|
+
const input = screen.getByDisplayValue(value);
|
|
115
|
+
expect(input).toBeInTheDocument();
|
|
125
116
|
});
|
|
126
117
|
|
|
127
118
|
it("disabled prop disables the input element", () => {
|
|
128
119
|
// Arrange
|
|
129
|
-
|
|
120
|
+
render(
|
|
130
121
|
<TextField
|
|
131
122
|
id="tf-1"
|
|
132
123
|
value=""
|
|
@@ -134,7 +125,7 @@ describe("TextField", () => {
|
|
|
134
125
|
disabled={true}
|
|
135
126
|
/>,
|
|
136
127
|
);
|
|
137
|
-
const input =
|
|
128
|
+
const input = screen.getByRole("textbox");
|
|
138
129
|
|
|
139
130
|
// Act
|
|
140
131
|
|
|
@@ -146,13 +137,17 @@ describe("TextField", () => {
|
|
|
146
137
|
// Arrange
|
|
147
138
|
const handleOnChange = jest.fn();
|
|
148
139
|
|
|
149
|
-
|
|
140
|
+
render(
|
|
150
141
|
<TextField id={"tf-1"} value="Text" onChange={handleOnChange} />,
|
|
151
142
|
);
|
|
152
143
|
|
|
153
144
|
// Act
|
|
154
145
|
const newValue = "Test2";
|
|
155
|
-
|
|
146
|
+
const input = screen.getByRole("textbox");
|
|
147
|
+
// @see https://testing-library.com/docs/react-testing-library/faq
|
|
148
|
+
// How do I test input onChange handlers?
|
|
149
|
+
// eslint-disable-next-line testing-library/prefer-user-event
|
|
150
|
+
fireEvent.change(input, {target: {value: newValue}});
|
|
156
151
|
|
|
157
152
|
// Assert
|
|
158
153
|
expect(handleOnChange).toHaveBeenCalledWith(newValue);
|
|
@@ -162,7 +157,7 @@ describe("TextField", () => {
|
|
|
162
157
|
// Arrange
|
|
163
158
|
const handleValidate = jest.fn((value: string): ?string => {});
|
|
164
159
|
|
|
165
|
-
|
|
160
|
+
render(
|
|
166
161
|
<TextField
|
|
167
162
|
id={"tf-1"}
|
|
168
163
|
value="Text"
|
|
@@ -173,7 +168,8 @@ describe("TextField", () => {
|
|
|
173
168
|
|
|
174
169
|
// Act
|
|
175
170
|
const newValue = "Text2";
|
|
176
|
-
|
|
171
|
+
// Select all text and replace it with the new value.
|
|
172
|
+
userEvent.type(screen.getByRole("textbox"), `{selectall}${newValue}`);
|
|
177
173
|
|
|
178
174
|
// Assert
|
|
179
175
|
expect(handleValidate).toHaveBeenCalledWith(newValue);
|
|
@@ -187,7 +183,7 @@ describe("TextField", () => {
|
|
|
187
183
|
}
|
|
188
184
|
});
|
|
189
185
|
|
|
190
|
-
|
|
186
|
+
render(
|
|
191
187
|
<TextField
|
|
192
188
|
id={"tf-1"}
|
|
193
189
|
value="TextIsLong"
|
|
@@ -198,7 +194,8 @@ describe("TextField", () => {
|
|
|
198
194
|
|
|
199
195
|
// Act
|
|
200
196
|
const newValue = "TextIsLongerThan8";
|
|
201
|
-
|
|
197
|
+
// Select all text and replace it with the new value.
|
|
198
|
+
userEvent.type(screen.getByRole("textbox"), `{selectall}${newValue}`);
|
|
202
199
|
|
|
203
200
|
// Assert
|
|
204
201
|
expect(handleValidate).toHaveReturnedWith(undefined);
|
|
@@ -213,7 +210,7 @@ describe("TextField", () => {
|
|
|
213
210
|
}
|
|
214
211
|
});
|
|
215
212
|
|
|
216
|
-
|
|
213
|
+
render(
|
|
217
214
|
<TextField
|
|
218
215
|
id={"tf-1"}
|
|
219
216
|
value="TextIsLongerThan8"
|
|
@@ -224,7 +221,8 @@ describe("TextField", () => {
|
|
|
224
221
|
|
|
225
222
|
// Act
|
|
226
223
|
const newValue = "Text";
|
|
227
|
-
|
|
224
|
+
// Select all text and replace it with the new value.
|
|
225
|
+
userEvent.type(screen.getByRole("textbox"), `{selectall}${newValue}`);
|
|
228
226
|
|
|
229
227
|
// Assert
|
|
230
228
|
expect(handleValidate).toHaveReturnedWith(errorMessage);
|
|
@@ -240,7 +238,7 @@ describe("TextField", () => {
|
|
|
240
238
|
}
|
|
241
239
|
});
|
|
242
240
|
|
|
243
|
-
|
|
241
|
+
render(
|
|
244
242
|
<TextField
|
|
245
243
|
id={"tf-1"}
|
|
246
244
|
value="TextIsLongerThan8"
|
|
@@ -252,7 +250,8 @@ describe("TextField", () => {
|
|
|
252
250
|
|
|
253
251
|
// Act
|
|
254
252
|
const newValue = "Text";
|
|
255
|
-
|
|
253
|
+
// Select all text and replace it with the new value.
|
|
254
|
+
userEvent.type(screen.getByRole("textbox"), `{selectall}${newValue}`);
|
|
256
255
|
|
|
257
256
|
// Assert
|
|
258
257
|
expect(handleValidate).toHaveBeenCalledWith(errorMessage);
|
|
@@ -269,7 +268,7 @@ describe("TextField", () => {
|
|
|
269
268
|
});
|
|
270
269
|
|
|
271
270
|
// Act
|
|
272
|
-
|
|
271
|
+
render(
|
|
273
272
|
<TextField
|
|
274
273
|
id={"tf-1"}
|
|
275
274
|
value="Short"
|
|
@@ -291,7 +290,7 @@ describe("TextField", () => {
|
|
|
291
290
|
},
|
|
292
291
|
);
|
|
293
292
|
|
|
294
|
-
|
|
293
|
+
render(
|
|
295
294
|
<TextField
|
|
296
295
|
id={"tf-1"}
|
|
297
296
|
value="TextIsLongerThan8"
|
|
@@ -301,12 +300,10 @@ describe("TextField", () => {
|
|
|
301
300
|
);
|
|
302
301
|
|
|
303
302
|
// Act
|
|
304
|
-
|
|
305
|
-
const input = wrapper.find("input");
|
|
306
|
-
input.simulate("keyDown", {key: key});
|
|
303
|
+
userEvent.type(screen.getByRole("textbox"), "{enter}");
|
|
307
304
|
|
|
308
305
|
// Assert
|
|
309
|
-
expect(handleOnKeyDown).toHaveReturnedWith(
|
|
306
|
+
expect(handleOnKeyDown).toHaveReturnedWith("Enter");
|
|
310
307
|
});
|
|
311
308
|
|
|
312
309
|
it("placeholder prop is passed to the input element", () => {
|
|
@@ -314,7 +311,7 @@ describe("TextField", () => {
|
|
|
314
311
|
const placeholder = "Placeholder";
|
|
315
312
|
|
|
316
313
|
// Act
|
|
317
|
-
|
|
314
|
+
render(
|
|
318
315
|
<TextField
|
|
319
316
|
id={"tf-1"}
|
|
320
317
|
value="Text"
|
|
@@ -324,16 +321,14 @@ describe("TextField", () => {
|
|
|
324
321
|
);
|
|
325
322
|
|
|
326
323
|
// Assert
|
|
327
|
-
const input =
|
|
328
|
-
expect(input).
|
|
329
|
-
`[placeholder="${placeholder}"]`,
|
|
330
|
-
);
|
|
324
|
+
const input = screen.getByPlaceholderText(placeholder);
|
|
325
|
+
expect(input).toBeInTheDocument();
|
|
331
326
|
});
|
|
332
327
|
|
|
333
328
|
it("testId is passed to the input element", () => {
|
|
334
329
|
// Arrange
|
|
335
330
|
const testId = "some-test-id";
|
|
336
|
-
|
|
331
|
+
render(
|
|
337
332
|
<TextField
|
|
338
333
|
id={"tf-1"}
|
|
339
334
|
value="Text"
|
|
@@ -345,14 +340,14 @@ describe("TextField", () => {
|
|
|
345
340
|
// Act
|
|
346
341
|
|
|
347
342
|
// Assert
|
|
348
|
-
const input =
|
|
349
|
-
expect(input).
|
|
343
|
+
const input = screen.getByRole("textbox");
|
|
344
|
+
expect(input).toHaveAttribute("data-test-id", testId);
|
|
350
345
|
});
|
|
351
346
|
|
|
352
347
|
it("aria props are passed to the input element", () => {
|
|
353
348
|
// Arrange
|
|
354
349
|
const ariaLabel = "example-text-field";
|
|
355
|
-
|
|
350
|
+
render(
|
|
356
351
|
<TextField
|
|
357
352
|
id={"tf-1"}
|
|
358
353
|
value="Text"
|
|
@@ -364,15 +359,15 @@ describe("TextField", () => {
|
|
|
364
359
|
// Act
|
|
365
360
|
|
|
366
361
|
// Assert
|
|
367
|
-
const input =
|
|
368
|
-
expect(input).
|
|
362
|
+
const input = screen.getByRole("textbox");
|
|
363
|
+
expect(input).toHaveAttribute("aria-label", ariaLabel);
|
|
369
364
|
});
|
|
370
365
|
|
|
371
366
|
it("readOnly prop is passed to the input element", async () => {
|
|
372
367
|
// Arrange
|
|
373
368
|
|
|
374
369
|
// Act
|
|
375
|
-
|
|
370
|
+
render(
|
|
376
371
|
<TextField
|
|
377
372
|
id={"tf-1"}
|
|
378
373
|
value={"Text"}
|
|
@@ -382,8 +377,8 @@ describe("TextField", () => {
|
|
|
382
377
|
);
|
|
383
378
|
|
|
384
379
|
// Assert
|
|
385
|
-
const input =
|
|
386
|
-
expect(input).
|
|
380
|
+
const input = screen.getByRole("textbox");
|
|
381
|
+
expect(input).toHaveAttribute("readOnly");
|
|
387
382
|
});
|
|
388
383
|
|
|
389
384
|
it("autoComplete prop is passed to the input element", async () => {
|
|
@@ -391,7 +386,7 @@ describe("TextField", () => {
|
|
|
391
386
|
const autoComplete = "name";
|
|
392
387
|
|
|
393
388
|
// Act
|
|
394
|
-
|
|
389
|
+
render(
|
|
395
390
|
<TextField
|
|
396
391
|
id={"tf-1"}
|
|
397
392
|
value={"Text"}
|
|
@@ -401,7 +396,7 @@ describe("TextField", () => {
|
|
|
401
396
|
);
|
|
402
397
|
|
|
403
398
|
// Assert
|
|
404
|
-
const input =
|
|
405
|
-
expect(input).
|
|
399
|
+
const input = screen.getByRole("textbox");
|
|
400
|
+
expect(input).toHaveAttribute("autoComplete", autoComplete);
|
|
406
401
|
});
|
|
407
402
|
});
|
|
@@ -163,15 +163,11 @@ type State = {|
|
|
|
163
163
|
focused: boolean,
|
|
164
164
|
|};
|
|
165
165
|
|
|
166
|
-
// TODO(WB-1081): Change class name back to LabeledTextField after Styleguidist is gone.
|
|
167
166
|
/**
|
|
168
167
|
* A LabeledTextField is an element used to accept a single line of text
|
|
169
168
|
* from the user paired with a label, description, and error field elements.
|
|
170
169
|
*/
|
|
171
|
-
class
|
|
172
|
-
PropsWithForwardRef,
|
|
173
|
-
State,
|
|
174
|
-
> {
|
|
170
|
+
class LabeledTextField extends React.Component<PropsWithForwardRef, State> {
|
|
175
171
|
static defaultProps: DefaultProps = {
|
|
176
172
|
type: "text",
|
|
177
173
|
disabled: false,
|
|
@@ -288,7 +284,7 @@ class LabeledTextFieldInternal extends React.Component<
|
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
type ExportProps = $Diff<
|
|
291
|
-
React.ElementConfig<typeof
|
|
287
|
+
React.ElementConfig<typeof LabeledTextField>,
|
|
292
288
|
WithForwardRef,
|
|
293
289
|
>;
|
|
294
290
|
|
|
@@ -312,9 +308,7 @@ type ExportProps = $Diff<
|
|
|
312
308
|
* />
|
|
313
309
|
* ```
|
|
314
310
|
*/
|
|
315
|
-
const LabeledTextField: React.AbstractComponent<ExportProps, HTMLInputElement> =
|
|
316
|
-
React.forwardRef<ExportProps, HTMLInputElement>((props, ref) => (
|
|
317
|
-
<LabeledTextFieldInternal {...props} forwardedRef={ref} />
|
|
318
|
-
));
|
|
319
311
|
|
|
320
|
-
export default
|
|
312
|
+
export default (React.forwardRef<ExportProps, HTMLInputElement>(
|
|
313
|
+
(props, ref) => <LabeledTextField {...props} forwardedRef={ref} />,
|
|
314
|
+
): React.AbstractComponent<ExportProps, HTMLInputElement>);
|
|
@@ -146,11 +146,10 @@ type State = {|
|
|
|
146
146
|
focused: boolean,
|
|
147
147
|
|};
|
|
148
148
|
|
|
149
|
-
// TODO(WB-1081): Change class name back to TextField after Styleguidist is gone.
|
|
150
149
|
/**
|
|
151
150
|
* A TextField is an element used to accept a single line of text from the user.
|
|
152
151
|
*/
|
|
153
|
-
class
|
|
152
|
+
class TextField extends React.Component<PropsWithForwardRef, State> {
|
|
154
153
|
static defaultProps: DefaultProps = {
|
|
155
154
|
type: "text",
|
|
156
155
|
disabled: false,
|
|
@@ -343,10 +342,7 @@ const styles = StyleSheet.create({
|
|
|
343
342
|
},
|
|
344
343
|
});
|
|
345
344
|
|
|
346
|
-
type ExportProps = $Diff<
|
|
347
|
-
React.ElementConfig<typeof TextFieldInternal>,
|
|
348
|
-
WithForwardRef,
|
|
349
|
-
>;
|
|
345
|
+
type ExportProps = $Diff<React.ElementConfig<typeof TextField>, WithForwardRef>;
|
|
350
346
|
|
|
351
347
|
/**
|
|
352
348
|
* A TextField is an element used to accept a single line of text from the user.
|
|
@@ -365,9 +361,6 @@ type ExportProps = $Diff<
|
|
|
365
361
|
* />
|
|
366
362
|
* ```
|
|
367
363
|
*/
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
));
|
|
372
|
-
|
|
373
|
-
export default TextField;
|
|
364
|
+
export default (React.forwardRef<ExportProps, HTMLInputElement>(
|
|
365
|
+
(props, ref) => <TextField {...props} forwardedRef={ref} />,
|
|
366
|
+
): React.AbstractComponent<ExportProps, HTMLInputElement>);
|