@atlaskit/form 12.5.4 → 12.6.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/CHANGELOG.md +7 -0
- package/codemods/__tests__/migrate-data-testid-to-testid-prop.test.tsx +607 -0
- package/codemods/migrate-data-testid-to-testid-prop.tsx +160 -0
- package/dist/cjs/form.js +9 -3
- package/dist/es2019/form.js +6 -2
- package/dist/esm/form.js +9 -3
- package/dist/types/form.d.ts +4 -0
- package/dist/types-ts4.5/form.d.ts +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @atlaskit/form
|
|
2
2
|
|
|
3
|
+
## 12.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`27fa43b33e35e`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/27fa43b33e35e) -
|
|
8
|
+
Add optional testId prop that applies a data-testid attribute to the underlying form element
|
|
9
|
+
|
|
3
10
|
## 12.5.4
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
jest.autoMockOff();
|
|
2
|
+
|
|
3
|
+
import transformer from '../migrate-data-testid-to-testid-prop';
|
|
4
|
+
|
|
5
|
+
const defineInlineTest = require('jscodeshift/dist/testUtils').defineInlineTest;
|
|
6
|
+
|
|
7
|
+
// Test: No transformation when no @atlaskit/form import
|
|
8
|
+
defineInlineTest(
|
|
9
|
+
{ default: transformer, parser: 'tsx' },
|
|
10
|
+
{},
|
|
11
|
+
`import React from 'react';
|
|
12
|
+
|
|
13
|
+
const MyComponent = () => (
|
|
14
|
+
<form data-testid="should-not-change">
|
|
15
|
+
<input type="text" />
|
|
16
|
+
</form>
|
|
17
|
+
);`,
|
|
18
|
+
`import React from 'react';
|
|
19
|
+
|
|
20
|
+
const MyComponent = () => (
|
|
21
|
+
<form data-testid="should-not-change">
|
|
22
|
+
<input type="text" />
|
|
23
|
+
</form>
|
|
24
|
+
);`,
|
|
25
|
+
'should not transform if @atlaskit/form import is not present',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Test: Basic transformation with default import as Form
|
|
29
|
+
defineInlineTest(
|
|
30
|
+
{ default: transformer, parser: 'tsx' },
|
|
31
|
+
{},
|
|
32
|
+
`import Form from '@atlaskit/form';
|
|
33
|
+
|
|
34
|
+
const MyComponent = () => (
|
|
35
|
+
<Form onSubmit={handleSubmit}>
|
|
36
|
+
{({ formProps }) => (
|
|
37
|
+
<form {...formProps} data-testid="my-form">
|
|
38
|
+
<input type="text" />
|
|
39
|
+
</form>
|
|
40
|
+
)}
|
|
41
|
+
</Form>
|
|
42
|
+
);`,
|
|
43
|
+
`import Form from '@atlaskit/form';
|
|
44
|
+
|
|
45
|
+
const MyComponent = () => (
|
|
46
|
+
<Form onSubmit={handleSubmit} testId="my-form">
|
|
47
|
+
{({ formProps }) => (
|
|
48
|
+
<form {...formProps}>
|
|
49
|
+
<input type="text" />
|
|
50
|
+
</form>
|
|
51
|
+
)}
|
|
52
|
+
</Form>
|
|
53
|
+
);`,
|
|
54
|
+
'should migrate string literal data-testid to testId prop with Form import',
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// Test: Basic transformation with renamed default import
|
|
58
|
+
defineInlineTest(
|
|
59
|
+
{ default: transformer, parser: 'tsx' },
|
|
60
|
+
{},
|
|
61
|
+
`import AkForm from '@atlaskit/form';
|
|
62
|
+
|
|
63
|
+
const MyComponent = () => (
|
|
64
|
+
<AkForm onSubmit={handleSubmit}>
|
|
65
|
+
{({ formProps }) => (
|
|
66
|
+
<form {...formProps} data-testid="my-form">
|
|
67
|
+
<input type="text" />
|
|
68
|
+
</form>
|
|
69
|
+
)}
|
|
70
|
+
</AkForm>
|
|
71
|
+
);`,
|
|
72
|
+
`import AkForm from '@atlaskit/form';
|
|
73
|
+
|
|
74
|
+
const MyComponent = () => (
|
|
75
|
+
<AkForm onSubmit={handleSubmit} testId="my-form">
|
|
76
|
+
{({ formProps }) => (
|
|
77
|
+
<form {...formProps}>
|
|
78
|
+
<input type="text" />
|
|
79
|
+
</form>
|
|
80
|
+
)}
|
|
81
|
+
</AkForm>
|
|
82
|
+
);`,
|
|
83
|
+
'should migrate with renamed default import (AkForm)',
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Test: Expression data-testid with variable
|
|
87
|
+
defineInlineTest(
|
|
88
|
+
{ default: transformer, parser: 'tsx' },
|
|
89
|
+
{},
|
|
90
|
+
`import Form from '@atlaskit/form';
|
|
91
|
+
|
|
92
|
+
const testId = 'dynamic-form';
|
|
93
|
+
const MyComponent = () => (
|
|
94
|
+
<Form onSubmit={handleSubmit}>
|
|
95
|
+
{({ formProps }) => (
|
|
96
|
+
<form {...formProps} data-testid={testId}>
|
|
97
|
+
<input type="text" />
|
|
98
|
+
</form>
|
|
99
|
+
)}
|
|
100
|
+
</Form>
|
|
101
|
+
);`,
|
|
102
|
+
`import Form from '@atlaskit/form';
|
|
103
|
+
|
|
104
|
+
const testId = 'dynamic-form';
|
|
105
|
+
const MyComponent = () => (
|
|
106
|
+
<Form onSubmit={handleSubmit} testId={testId}>
|
|
107
|
+
{({ formProps }) => (
|
|
108
|
+
<form {...formProps}>
|
|
109
|
+
<input type="text" />
|
|
110
|
+
</form>
|
|
111
|
+
)}
|
|
112
|
+
</Form>
|
|
113
|
+
);`,
|
|
114
|
+
'should migrate expression data-testid with variable',
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Test: Expression data-testid with function call
|
|
118
|
+
defineInlineTest(
|
|
119
|
+
{ default: transformer, parser: 'tsx' },
|
|
120
|
+
{},
|
|
121
|
+
`import Form from '@atlaskit/form';
|
|
122
|
+
|
|
123
|
+
const MyComponent = ({ id }) => (
|
|
124
|
+
<Form onSubmit={handleSubmit}>
|
|
125
|
+
{({ formProps }) => (
|
|
126
|
+
<form {...formProps} data-testid={getTestId(id)}>
|
|
127
|
+
<input type="text" />
|
|
128
|
+
</form>
|
|
129
|
+
)}
|
|
130
|
+
</Form>
|
|
131
|
+
);`,
|
|
132
|
+
`import Form from '@atlaskit/form';
|
|
133
|
+
|
|
134
|
+
const MyComponent = ({ id }) => (
|
|
135
|
+
<Form onSubmit={handleSubmit} testId={getTestId(id)}>
|
|
136
|
+
{({ formProps }) => (
|
|
137
|
+
<form {...formProps}>
|
|
138
|
+
<input type="text" />
|
|
139
|
+
</form>
|
|
140
|
+
)}
|
|
141
|
+
</Form>
|
|
142
|
+
);`,
|
|
143
|
+
'should migrate expression data-testid with function call',
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Test: Multiple attributes on form element
|
|
147
|
+
defineInlineTest(
|
|
148
|
+
{ default: transformer, parser: 'tsx' },
|
|
149
|
+
{},
|
|
150
|
+
`import Form from '@atlaskit/form';
|
|
151
|
+
|
|
152
|
+
const MyComponent = () => (
|
|
153
|
+
<Form onSubmit={handleSubmit}>
|
|
154
|
+
{({ formProps }) => (
|
|
155
|
+
<form noValidate {...formProps} id="my-form" data-testid="my-form" className="custom-form">
|
|
156
|
+
<input type="text" />
|
|
157
|
+
</form>
|
|
158
|
+
)}
|
|
159
|
+
</Form>
|
|
160
|
+
);`,
|
|
161
|
+
`import Form from '@atlaskit/form';
|
|
162
|
+
|
|
163
|
+
const MyComponent = () => (
|
|
164
|
+
<Form onSubmit={handleSubmit} testId="my-form">
|
|
165
|
+
{({ formProps }) => (
|
|
166
|
+
<form noValidate {...formProps} id="my-form" className="custom-form">
|
|
167
|
+
<input type="text" />
|
|
168
|
+
</form>
|
|
169
|
+
)}
|
|
170
|
+
</Form>
|
|
171
|
+
);`,
|
|
172
|
+
'should migrate data-testid while preserving other attributes',
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Test: Renamed formProps parameter
|
|
176
|
+
defineInlineTest(
|
|
177
|
+
{ default: transformer, parser: 'tsx' },
|
|
178
|
+
{},
|
|
179
|
+
`import Form from '@atlaskit/form';
|
|
180
|
+
|
|
181
|
+
const MyComponent = () => (
|
|
182
|
+
<Form onSubmit={handleSubmit}>
|
|
183
|
+
{({ formProps: customProps }) => (
|
|
184
|
+
<form {...customProps} data-testid="my-form">
|
|
185
|
+
<input type="text" />
|
|
186
|
+
</form>
|
|
187
|
+
)}
|
|
188
|
+
</Form>
|
|
189
|
+
);`,
|
|
190
|
+
`import Form from '@atlaskit/form';
|
|
191
|
+
|
|
192
|
+
const MyComponent = () => (
|
|
193
|
+
<Form onSubmit={handleSubmit} testId="my-form">
|
|
194
|
+
{({ formProps: customProps }) => (
|
|
195
|
+
<form {...customProps}>
|
|
196
|
+
<input type="text" />
|
|
197
|
+
</form>
|
|
198
|
+
)}
|
|
199
|
+
</Form>
|
|
200
|
+
);`,
|
|
201
|
+
'should work with renamed formProps parameter',
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
// Test: Multiple destructured parameters
|
|
205
|
+
defineInlineTest(
|
|
206
|
+
{ default: transformer, parser: 'tsx' },
|
|
207
|
+
{},
|
|
208
|
+
`import Form from '@atlaskit/form';
|
|
209
|
+
|
|
210
|
+
const MyComponent = () => (
|
|
211
|
+
<Form onSubmit={handleSubmit}>
|
|
212
|
+
{({ formProps, submitting, dirty }) => (
|
|
213
|
+
<form {...formProps} data-testid="my-form">
|
|
214
|
+
<input type="text" />
|
|
215
|
+
<button type="submit" disabled={submitting}>
|
|
216
|
+
{dirty ? 'Save Changes' : 'Submit'}
|
|
217
|
+
</button>
|
|
218
|
+
</form>
|
|
219
|
+
)}
|
|
220
|
+
</Form>
|
|
221
|
+
);`,
|
|
222
|
+
`import Form from '@atlaskit/form';
|
|
223
|
+
|
|
224
|
+
const MyComponent = () => (
|
|
225
|
+
<Form onSubmit={handleSubmit} testId="my-form">
|
|
226
|
+
{({ formProps, submitting, dirty }) => (
|
|
227
|
+
<form {...formProps}>
|
|
228
|
+
<input type="text" />
|
|
229
|
+
<button type="submit" disabled={submitting}>
|
|
230
|
+
{dirty ? 'Save Changes' : 'Submit'}
|
|
231
|
+
</button>
|
|
232
|
+
</form>
|
|
233
|
+
)}
|
|
234
|
+
</Form>
|
|
235
|
+
);`,
|
|
236
|
+
'should work with multiple destructured parameters',
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Test: Nested form within wrapper div
|
|
240
|
+
defineInlineTest(
|
|
241
|
+
{ default: transformer, parser: 'tsx' },
|
|
242
|
+
{},
|
|
243
|
+
`import Form from '@atlaskit/form';
|
|
244
|
+
|
|
245
|
+
const MyComponent = () => (
|
|
246
|
+
<Form onSubmit={handleSubmit}>
|
|
247
|
+
{({ formProps }) => (
|
|
248
|
+
<div className="form-wrapper">
|
|
249
|
+
<form {...formProps} data-testid="nested-form">
|
|
250
|
+
<input type="text" />
|
|
251
|
+
</form>
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
</Form>
|
|
255
|
+
);`,
|
|
256
|
+
`import Form from '@atlaskit/form';
|
|
257
|
+
|
|
258
|
+
const MyComponent = () => (
|
|
259
|
+
<Form onSubmit={handleSubmit} testId="nested-form">
|
|
260
|
+
{({ formProps }) => (
|
|
261
|
+
<div className="form-wrapper">
|
|
262
|
+
<form {...formProps}>
|
|
263
|
+
<input type="text" />
|
|
264
|
+
</form>
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
</Form>
|
|
268
|
+
);`,
|
|
269
|
+
'should find form element nested within other elements',
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Test: Form with named imports and default import
|
|
273
|
+
defineInlineTest(
|
|
274
|
+
{ default: transformer, parser: 'tsx' },
|
|
275
|
+
{},
|
|
276
|
+
`import Form, { Field, FormSection } from '@atlaskit/form';
|
|
277
|
+
|
|
278
|
+
const MyComponent = () => (
|
|
279
|
+
<Form onSubmit={handleSubmit}>
|
|
280
|
+
{({ formProps }) => (
|
|
281
|
+
<form {...formProps} data-testid="complete-form">
|
|
282
|
+
<FormSection>
|
|
283
|
+
<Field name="username">
|
|
284
|
+
{({ fieldProps }) => <input {...fieldProps} />}
|
|
285
|
+
</Field>
|
|
286
|
+
</FormSection>
|
|
287
|
+
</form>
|
|
288
|
+
)}
|
|
289
|
+
</Form>
|
|
290
|
+
);`,
|
|
291
|
+
`import Form, { Field, FormSection } from '@atlaskit/form';
|
|
292
|
+
|
|
293
|
+
const MyComponent = () => (
|
|
294
|
+
<Form onSubmit={handleSubmit} testId="complete-form">
|
|
295
|
+
{({ formProps }) => (
|
|
296
|
+
<form {...formProps}>
|
|
297
|
+
<FormSection>
|
|
298
|
+
<Field name="username">
|
|
299
|
+
{({ fieldProps }) => <input {...fieldProps} />}
|
|
300
|
+
</Field>
|
|
301
|
+
</FormSection>
|
|
302
|
+
</form>
|
|
303
|
+
)}
|
|
304
|
+
</Form>
|
|
305
|
+
);`,
|
|
306
|
+
'should work with mixed default and named imports',
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// Test: No transformation when testId already exists
|
|
310
|
+
defineInlineTest(
|
|
311
|
+
{ default: transformer, parser: 'tsx' },
|
|
312
|
+
{},
|
|
313
|
+
`import Form from '@atlaskit/form';
|
|
314
|
+
|
|
315
|
+
const MyComponent = () => (
|
|
316
|
+
<Form testId="existing" onSubmit={handleSubmit}>
|
|
317
|
+
{({ formProps }) => (
|
|
318
|
+
<form {...formProps} data-testid="should-not-change">
|
|
319
|
+
<input type="text" />
|
|
320
|
+
</form>
|
|
321
|
+
)}
|
|
322
|
+
</Form>
|
|
323
|
+
);`,
|
|
324
|
+
`import Form from '@atlaskit/form';
|
|
325
|
+
|
|
326
|
+
const MyComponent = () => (
|
|
327
|
+
<Form testId="existing" onSubmit={handleSubmit}>
|
|
328
|
+
{({ formProps }) => (
|
|
329
|
+
<form {...formProps} data-testid="should-not-change">
|
|
330
|
+
<input type="text" />
|
|
331
|
+
</form>
|
|
332
|
+
)}
|
|
333
|
+
</Form>
|
|
334
|
+
);`,
|
|
335
|
+
'should not migrate when testId prop already exists',
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Test: No transformation when no data-testid present
|
|
339
|
+
defineInlineTest(
|
|
340
|
+
{ default: transformer, parser: 'tsx' },
|
|
341
|
+
{},
|
|
342
|
+
`import Form from '@atlaskit/form';
|
|
343
|
+
|
|
344
|
+
const MyComponent = () => (
|
|
345
|
+
<Form onSubmit={handleSubmit}>
|
|
346
|
+
{({ formProps }) => (
|
|
347
|
+
<form {...formProps} id="my-form">
|
|
348
|
+
<input type="text" />
|
|
349
|
+
</form>
|
|
350
|
+
)}
|
|
351
|
+
</Form>
|
|
352
|
+
);`,
|
|
353
|
+
`import Form from '@atlaskit/form';
|
|
354
|
+
|
|
355
|
+
const MyComponent = () => (
|
|
356
|
+
<Form onSubmit={handleSubmit}>
|
|
357
|
+
{({ formProps }) => (
|
|
358
|
+
<form {...formProps} id="my-form">
|
|
359
|
+
<input type="text" />
|
|
360
|
+
</form>
|
|
361
|
+
)}
|
|
362
|
+
</Form>
|
|
363
|
+
);`,
|
|
364
|
+
'should not transform when no data-testid is present',
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Test: No transformation with JSX children (not function children)
|
|
368
|
+
defineInlineTest(
|
|
369
|
+
{ default: transformer, parser: 'tsx' },
|
|
370
|
+
{},
|
|
371
|
+
`import Form from '@atlaskit/form';
|
|
372
|
+
|
|
373
|
+
const MyComponent = () => (
|
|
374
|
+
<Form onSubmit={handleSubmit}>
|
|
375
|
+
<div>JSX children instead of function</div>
|
|
376
|
+
</Form>
|
|
377
|
+
);`,
|
|
378
|
+
`import Form from '@atlaskit/form';
|
|
379
|
+
|
|
380
|
+
const MyComponent = () => (
|
|
381
|
+
<Form onSubmit={handleSubmit}>
|
|
382
|
+
<div>JSX children instead of function</div>
|
|
383
|
+
</Form>
|
|
384
|
+
);`,
|
|
385
|
+
'should not transform JSX children (non-function children)',
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Test: No transformation without formProps parameter
|
|
389
|
+
defineInlineTest(
|
|
390
|
+
{ default: transformer, parser: 'tsx' },
|
|
391
|
+
{},
|
|
392
|
+
`import Form from '@atlaskit/form';
|
|
393
|
+
|
|
394
|
+
const MyComponent = () => (
|
|
395
|
+
<Form onSubmit={handleSubmit}>
|
|
396
|
+
{({ submitting }) => (
|
|
397
|
+
<form data-testid="no-formprops">
|
|
398
|
+
<input type="text" />
|
|
399
|
+
<button disabled={submitting}>Submit</button>
|
|
400
|
+
</form>
|
|
401
|
+
)}
|
|
402
|
+
</Form>
|
|
403
|
+
);`,
|
|
404
|
+
`import Form from '@atlaskit/form';
|
|
405
|
+
|
|
406
|
+
const MyComponent = () => (
|
|
407
|
+
<Form onSubmit={handleSubmit}>
|
|
408
|
+
{({ submitting }) => (
|
|
409
|
+
<form data-testid="no-formprops">
|
|
410
|
+
<input type="text" />
|
|
411
|
+
<button disabled={submitting}>Submit</button>
|
|
412
|
+
</form>
|
|
413
|
+
)}
|
|
414
|
+
</Form>
|
|
415
|
+
);`,
|
|
416
|
+
'should not transform when no formProps parameter exists',
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
// Test: No transformation when no form element found
|
|
420
|
+
defineInlineTest(
|
|
421
|
+
{ default: transformer, parser: 'tsx' },
|
|
422
|
+
{},
|
|
423
|
+
`import Form from '@atlaskit/form';
|
|
424
|
+
|
|
425
|
+
const MyComponent = () => (
|
|
426
|
+
<Form onSubmit={handleSubmit}>
|
|
427
|
+
{({ formProps }) => (
|
|
428
|
+
<div {...formProps} data-testid="not-a-form">
|
|
429
|
+
<input type="text" />
|
|
430
|
+
</div>
|
|
431
|
+
)}
|
|
432
|
+
</Form>
|
|
433
|
+
);`,
|
|
434
|
+
`import Form from '@atlaskit/form';
|
|
435
|
+
|
|
436
|
+
const MyComponent = () => (
|
|
437
|
+
<Form onSubmit={handleSubmit}>
|
|
438
|
+
{({ formProps }) => (
|
|
439
|
+
<div {...formProps} data-testid="not-a-form">
|
|
440
|
+
<input type="text" />
|
|
441
|
+
</div>
|
|
442
|
+
)}
|
|
443
|
+
</Form>
|
|
444
|
+
);`,
|
|
445
|
+
'should not transform when no HTML form element is found',
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// Test: No transformation with only named imports (no default)
|
|
449
|
+
defineInlineTest(
|
|
450
|
+
{ default: transformer, parser: 'tsx' },
|
|
451
|
+
{},
|
|
452
|
+
`import { Field, FormSection } from '@atlaskit/form';
|
|
453
|
+
|
|
454
|
+
const MyComponent = () => (
|
|
455
|
+
<div>
|
|
456
|
+
<Field name="test">
|
|
457
|
+
{({ fieldProps }) => <input {...fieldProps} />}
|
|
458
|
+
</Field>
|
|
459
|
+
</div>
|
|
460
|
+
);`,
|
|
461
|
+
`import { Field, FormSection } from '@atlaskit/form';
|
|
462
|
+
|
|
463
|
+
const MyComponent = () => (
|
|
464
|
+
<div>
|
|
465
|
+
<Field name="test">
|
|
466
|
+
{({ fieldProps }) => <input {...fieldProps} />}
|
|
467
|
+
</Field>
|
|
468
|
+
</div>
|
|
469
|
+
);`,
|
|
470
|
+
'should not transform when only named imports exist (no default Form import)',
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
// Test: Multiple forms in same component - only transform the one with data-testid
|
|
474
|
+
defineInlineTest(
|
|
475
|
+
{ default: transformer, parser: 'tsx' },
|
|
476
|
+
{},
|
|
477
|
+
`import Form from '@atlaskit/form';
|
|
478
|
+
|
|
479
|
+
const MyComponent = () => (
|
|
480
|
+
<div>
|
|
481
|
+
<Form onSubmit={handleSubmit1}>
|
|
482
|
+
{({ formProps }) => (
|
|
483
|
+
<form {...formProps} data-testid="first-form">
|
|
484
|
+
<input type="text" />
|
|
485
|
+
</form>
|
|
486
|
+
)}
|
|
487
|
+
</Form>
|
|
488
|
+
<Form onSubmit={handleSubmit2}>
|
|
489
|
+
{({ formProps }) => (
|
|
490
|
+
<form {...formProps} id="second-form">
|
|
491
|
+
<input type="text" />
|
|
492
|
+
</form>
|
|
493
|
+
)}
|
|
494
|
+
</Form>
|
|
495
|
+
</div>
|
|
496
|
+
);`,
|
|
497
|
+
`import Form from '@atlaskit/form';
|
|
498
|
+
|
|
499
|
+
const MyComponent = () => (
|
|
500
|
+
<div>
|
|
501
|
+
<Form onSubmit={handleSubmit1} testId="first-form">
|
|
502
|
+
{({ formProps }) => (
|
|
503
|
+
<form {...formProps}>
|
|
504
|
+
<input type="text" />
|
|
505
|
+
</form>
|
|
506
|
+
)}
|
|
507
|
+
</Form>
|
|
508
|
+
<Form onSubmit={handleSubmit2}>
|
|
509
|
+
{({ formProps }) => (
|
|
510
|
+
<form {...formProps} id="second-form">
|
|
511
|
+
<input type="text" />
|
|
512
|
+
</form>
|
|
513
|
+
)}
|
|
514
|
+
</Form>
|
|
515
|
+
</div>
|
|
516
|
+
);`,
|
|
517
|
+
'should only transform forms with data-testid, leave others unchanged',
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
// Test: Complex real-world example like select-scopes
|
|
521
|
+
defineInlineTest(
|
|
522
|
+
{ default: transformer, parser: 'tsx' },
|
|
523
|
+
{},
|
|
524
|
+
`import AkForm from '@atlaskit/form';
|
|
525
|
+
|
|
526
|
+
const MyComponent = () => (
|
|
527
|
+
<AkForm onSubmit={handleSubmit}>
|
|
528
|
+
{({ formProps }) => (
|
|
529
|
+
<form {...formProps} id="form-id" data-testid="my-test-id">
|
|
530
|
+
<input type="text" />
|
|
531
|
+
</form>
|
|
532
|
+
)}
|
|
533
|
+
</AkForm>
|
|
534
|
+
);`,
|
|
535
|
+
`import AkForm from '@atlaskit/form';
|
|
536
|
+
|
|
537
|
+
const MyComponent = () => (
|
|
538
|
+
<AkForm onSubmit={handleSubmit} testId="my-test-id">
|
|
539
|
+
{({ formProps }) => (
|
|
540
|
+
<form {...formProps} id="form-id">
|
|
541
|
+
<input type="text" />
|
|
542
|
+
</form>
|
|
543
|
+
)}
|
|
544
|
+
</AkForm>
|
|
545
|
+
);`,
|
|
546
|
+
'should handle complex real-world example with AkForm and mixed attributes',
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Test: Different import styles from various packages should not transform
|
|
550
|
+
defineInlineTest(
|
|
551
|
+
{ default: transformer, parser: 'tsx' },
|
|
552
|
+
{},
|
|
553
|
+
`import CustomForm from '@some/other-package';
|
|
554
|
+
import { Form } from '@another/package';
|
|
555
|
+
|
|
556
|
+
const MyComponent = () => (
|
|
557
|
+
<CustomForm onSubmit={handleSubmit}>
|
|
558
|
+
{({ formProps }) => (
|
|
559
|
+
<form {...formProps} data-testid="should-not-change">
|
|
560
|
+
<input type="text" />
|
|
561
|
+
</form>
|
|
562
|
+
)}
|
|
563
|
+
</CustomForm>
|
|
564
|
+
);`,
|
|
565
|
+
`import CustomForm from '@some/other-package';
|
|
566
|
+
import { Form } from '@another/package';
|
|
567
|
+
|
|
568
|
+
const MyComponent = () => (
|
|
569
|
+
<CustomForm onSubmit={handleSubmit}>
|
|
570
|
+
{({ formProps }) => (
|
|
571
|
+
<form {...formProps} data-testid="should-not-change">
|
|
572
|
+
<input type="text" />
|
|
573
|
+
</form>
|
|
574
|
+
)}
|
|
575
|
+
</CustomForm>
|
|
576
|
+
);`,
|
|
577
|
+
'should not transform forms from other packages',
|
|
578
|
+
);
|
|
579
|
+
|
|
580
|
+
// Test: Preserve attribute order when adding testId
|
|
581
|
+
defineInlineTest(
|
|
582
|
+
{ default: transformer, parser: 'tsx' },
|
|
583
|
+
{},
|
|
584
|
+
`import Form from '@atlaskit/form';
|
|
585
|
+
|
|
586
|
+
const MyComponent = () => (
|
|
587
|
+
<Form isDisabled onSubmit={handleSubmit}>
|
|
588
|
+
{({ formProps }) => (
|
|
589
|
+
<form {...formProps} data-testid="ordered-form">
|
|
590
|
+
<input type="text" />
|
|
591
|
+
</form>
|
|
592
|
+
)}
|
|
593
|
+
</Form>
|
|
594
|
+
);`,
|
|
595
|
+
`import Form from '@atlaskit/form';
|
|
596
|
+
|
|
597
|
+
const MyComponent = () => (
|
|
598
|
+
<Form isDisabled onSubmit={handleSubmit} testId="ordered-form">
|
|
599
|
+
{({ formProps }) => (
|
|
600
|
+
<form {...formProps}>
|
|
601
|
+
<input type="text" />
|
|
602
|
+
</form>
|
|
603
|
+
)}
|
|
604
|
+
</Form>
|
|
605
|
+
);`,
|
|
606
|
+
'should preserve existing prop order when adding testId',
|
|
607
|
+
);
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type API,
|
|
3
|
+
type FileInfo,
|
|
4
|
+
type JSCodeshift,
|
|
5
|
+
type JSXElement,
|
|
6
|
+
type Options,
|
|
7
|
+
} from 'jscodeshift';
|
|
8
|
+
import { type Collection } from 'jscodeshift/src/Collection';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
addJSXAttributeToJSXElement,
|
|
12
|
+
getImportDeclarationCollection,
|
|
13
|
+
getImportDefaultSpecifierCollection,
|
|
14
|
+
getImportDefaultSpecifierName,
|
|
15
|
+
hasImportDeclaration,
|
|
16
|
+
} from './utils/helpers';
|
|
17
|
+
|
|
18
|
+
const importPath = '@atlaskit/form';
|
|
19
|
+
|
|
20
|
+
const migrateDataTestIdToTestIdProp = (j: JSCodeshift, collection: Collection<any>) => {
|
|
21
|
+
const importDeclarationCollection = getImportDeclarationCollection(j, collection, importPath);
|
|
22
|
+
const defaultImport = getImportDefaultSpecifierCollection(j, importDeclarationCollection);
|
|
23
|
+
const defaultImportName = getImportDefaultSpecifierName(defaultImport);
|
|
24
|
+
|
|
25
|
+
// if no default import is present, exit early
|
|
26
|
+
if (defaultImportName === null) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
collection.findJSXElements(defaultImportName).forEach((jsxElementPath) => {
|
|
31
|
+
const node = jsxElementPath.node;
|
|
32
|
+
|
|
33
|
+
// If no children, exit early
|
|
34
|
+
if (!node.children || node.children.length === 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if Form already has a testId prop - if so, skip to avoid conflicts
|
|
39
|
+
const existingTestIdProp = node.openingElement.attributes?.find(
|
|
40
|
+
(attr) => attr.type === 'JSXAttribute' && attr.name.name === 'testId',
|
|
41
|
+
);
|
|
42
|
+
if (existingTestIdProp) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Find the function child
|
|
47
|
+
const children = node.children.filter((child) => child.type === 'JSXExpressionContainer');
|
|
48
|
+
if (children.length === 0 || children.length > 1) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const childFunction = children[0];
|
|
53
|
+
if (
|
|
54
|
+
childFunction.type !== 'JSXExpressionContainer' ||
|
|
55
|
+
childFunction.expression.type !== 'ArrowFunctionExpression'
|
|
56
|
+
) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check if function has formProps parameter
|
|
61
|
+
const args = childFunction.expression.params
|
|
62
|
+
.filter((arg) => arg.type === 'ObjectPattern' && 'properties' in arg)
|
|
63
|
+
.flatMap((arg) => arg.properties)
|
|
64
|
+
.filter((property) => property.type === 'ObjectProperty');
|
|
65
|
+
|
|
66
|
+
const formPropsArg = args.find(
|
|
67
|
+
(arg) => arg.key.type === 'Identifier' && arg.key.name === 'formProps',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!formPropsArg || formPropsArg.value.type !== 'Identifier') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Find the HTML form element inside the child function
|
|
75
|
+
const body = childFunction.expression.body;
|
|
76
|
+
let htmlForm: JSXElement | null = null;
|
|
77
|
+
const q: any[] = [body];
|
|
78
|
+
|
|
79
|
+
while (q.length > 0) {
|
|
80
|
+
const child = q.pop();
|
|
81
|
+
if (!child || child.type !== 'JSXElement') {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
child.children?.forEach((el: any) => q.push(el));
|
|
85
|
+
|
|
86
|
+
if (child.type === 'JSXElement' && child.openingElement.name.name === 'form') {
|
|
87
|
+
htmlForm = child;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// if no HTML form, exit early
|
|
93
|
+
if (htmlForm === null) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Look for data-testid attribute on the form element
|
|
98
|
+
let dataTestIdAttr: any = null;
|
|
99
|
+
let dataTestIdValue: any = null;
|
|
100
|
+
|
|
101
|
+
htmlForm.openingElement.attributes?.forEach((attr, index) => {
|
|
102
|
+
// Find data-testid attribute
|
|
103
|
+
if (
|
|
104
|
+
attr.type === 'JSXAttribute' &&
|
|
105
|
+
attr.name.type === 'JSXIdentifier' &&
|
|
106
|
+
attr.name.name === 'data-testid'
|
|
107
|
+
) {
|
|
108
|
+
dataTestIdAttr = attr;
|
|
109
|
+
dataTestIdValue = attr.value;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// If no data-testid found, exit early
|
|
114
|
+
if (!dataTestIdAttr || !dataTestIdValue) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Extract the testId value
|
|
119
|
+
let testIdValue: any;
|
|
120
|
+
if (dataTestIdValue.type === 'StringLiteral' || dataTestIdValue.type === 'Literal') {
|
|
121
|
+
// String literal: data-testid="my-form"
|
|
122
|
+
testIdValue = dataTestIdValue;
|
|
123
|
+
} else if (dataTestIdValue.type === 'JSXExpressionContainer') {
|
|
124
|
+
// Expression: data-testid={someVariable}
|
|
125
|
+
testIdValue = j.jsxExpressionContainer(dataTestIdValue.expression);
|
|
126
|
+
} else {
|
|
127
|
+
// Unknown format, skip
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Remove the data-testid attribute from the form element
|
|
132
|
+
const filteredAttributes = htmlForm.openingElement.attributes?.filter(
|
|
133
|
+
(attr) => attr !== dataTestIdAttr,
|
|
134
|
+
);
|
|
135
|
+
if (filteredAttributes) {
|
|
136
|
+
htmlForm.openingElement.attributes = filteredAttributes;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Add testId prop to the Form component
|
|
140
|
+
const testIdProp = j.jsxAttribute(j.jsxIdentifier('testId'), testIdValue);
|
|
141
|
+
addJSXAttributeToJSXElement(j, jsxElementPath, testIdProp, 1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export default function transformer(fileInfo: FileInfo, { jscodeshift: j }: API, options: Options) {
|
|
148
|
+
const { source } = fileInfo;
|
|
149
|
+
const collection = j(source);
|
|
150
|
+
|
|
151
|
+
// If our component is not here, skip the file
|
|
152
|
+
if (!hasImportDeclaration(j, collection, importPath)) {
|
|
153
|
+
return source;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Migrate data-testid to testId prop
|
|
157
|
+
migrateDataTestIdToTestIdProp(j, collection);
|
|
158
|
+
|
|
159
|
+
return collection.toSource(options.printOptions || { quote: 'single' });
|
|
160
|
+
}
|
package/dist/cjs/form.js
CHANGED
|
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
8
8
|
exports.IsDisabledContext = exports.FormContext = void 0;
|
|
9
9
|
exports.default = Form;
|
|
10
10
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
11
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
11
12
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
12
13
|
var _react = _interopRequireWildcard(require("react"));
|
|
13
14
|
var _finalForm = require("final-form");
|
|
@@ -16,6 +17,8 @@ var _set = _interopRequireDefault(require("lodash/set"));
|
|
|
16
17
|
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
|
|
17
18
|
var _utils = require("./utils");
|
|
18
19
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
20
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
21
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
19
22
|
/**
|
|
20
23
|
* __Form context__
|
|
21
24
|
*
|
|
@@ -41,7 +44,8 @@ var FormContext = exports.FormContext = /*#__PURE__*/(0, _react.createContext)({
|
|
|
41
44
|
var IsDisabledContext = exports.IsDisabledContext = /*#__PURE__*/(0, _react.createContext)(false);
|
|
42
45
|
function Form(props) {
|
|
43
46
|
var userProvidedFormProps = props.formProps,
|
|
44
|
-
onSubmit = props.onSubmit
|
|
47
|
+
onSubmit = props.onSubmit,
|
|
48
|
+
testId = props.testId;
|
|
45
49
|
var formRef = (0, _react.useRef)(null);
|
|
46
50
|
var onSubmitRef = (0, _react.useRef)(onSubmit);
|
|
47
51
|
onSubmitRef.current = onSubmit;
|
|
@@ -144,11 +148,13 @@ function Form(props) {
|
|
|
144
148
|
}, [registerField, getCurrentValue, form.subscribe]);
|
|
145
149
|
|
|
146
150
|
// Abstracting so we can use the same for both rendering patterns
|
|
147
|
-
var formProps = {
|
|
151
|
+
var formProps = _objectSpread({
|
|
148
152
|
onKeyDown: handleKeyDown,
|
|
149
153
|
onSubmit: handleSubmit,
|
|
150
154
|
ref: formRef
|
|
151
|
-
}
|
|
155
|
+
}, testId && {
|
|
156
|
+
'data-testid': testId
|
|
157
|
+
});
|
|
152
158
|
var childrenContent = function () {
|
|
153
159
|
if (typeof children === 'function') {
|
|
154
160
|
var result = children.length > 0 ? children({
|
package/dist/es2019/form.js
CHANGED
|
@@ -29,7 +29,8 @@ export const IsDisabledContext = /*#__PURE__*/createContext(false);
|
|
|
29
29
|
export default function Form(props) {
|
|
30
30
|
const {
|
|
31
31
|
formProps: userProvidedFormProps,
|
|
32
|
-
onSubmit
|
|
32
|
+
onSubmit,
|
|
33
|
+
testId
|
|
33
34
|
} = props;
|
|
34
35
|
const formRef = useRef(null);
|
|
35
36
|
const onSubmitRef = useRef(onSubmit);
|
|
@@ -128,7 +129,10 @@ export default function Form(props) {
|
|
|
128
129
|
const formProps = {
|
|
129
130
|
onKeyDown: handleKeyDown,
|
|
130
131
|
onSubmit: handleSubmit,
|
|
131
|
-
ref: formRef
|
|
132
|
+
ref: formRef,
|
|
133
|
+
...(testId && {
|
|
134
|
+
'data-testid': testId
|
|
135
|
+
})
|
|
132
136
|
};
|
|
133
137
|
const childrenContent = (() => {
|
|
134
138
|
if (typeof children === 'function') {
|
package/dist/esm/form.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/extends";
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
3
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
4
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
5
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
3
6
|
import React, { createContext, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
7
|
import { createForm } from 'final-form';
|
|
5
8
|
import createDecorator from 'final-form-focus';
|
|
@@ -31,7 +34,8 @@ export var FormContext = /*#__PURE__*/createContext({
|
|
|
31
34
|
export var IsDisabledContext = /*#__PURE__*/createContext(false);
|
|
32
35
|
export default function Form(props) {
|
|
33
36
|
var userProvidedFormProps = props.formProps,
|
|
34
|
-
onSubmit = props.onSubmit
|
|
37
|
+
onSubmit = props.onSubmit,
|
|
38
|
+
testId = props.testId;
|
|
35
39
|
var formRef = useRef(null);
|
|
36
40
|
var onSubmitRef = useRef(onSubmit);
|
|
37
41
|
onSubmitRef.current = onSubmit;
|
|
@@ -134,11 +138,13 @@ export default function Form(props) {
|
|
|
134
138
|
}, [registerField, getCurrentValue, form.subscribe]);
|
|
135
139
|
|
|
136
140
|
// Abstracting so we can use the same for both rendering patterns
|
|
137
|
-
var formProps = {
|
|
141
|
+
var formProps = _objectSpread({
|
|
138
142
|
onKeyDown: handleKeyDown,
|
|
139
143
|
onSubmit: handleSubmit,
|
|
140
144
|
ref: formRef
|
|
141
|
-
}
|
|
145
|
+
}, testId && {
|
|
146
|
+
'data-testid': testId
|
|
147
|
+
});
|
|
142
148
|
var childrenContent = function () {
|
|
143
149
|
if (typeof children === 'function') {
|
|
144
150
|
var result = children.length > 0 ? children({
|
package/dist/types/form.d.ts
CHANGED
|
@@ -62,6 +62,10 @@ export interface FormProps<FormValues> {
|
|
|
62
62
|
* Sets the form and its fields as disabled. Users cannot edit or focus on the fields.
|
|
63
63
|
*/
|
|
64
64
|
isDisabled?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* A test identifier for the form element. This will be applied as `data-testid` attribute.
|
|
67
|
+
*/
|
|
68
|
+
testId?: string;
|
|
65
69
|
}
|
|
66
70
|
export default function Form<FormValues extends Record<string, any> = {}>(props: FormProps<FormValues>): React.JSX.Element;
|
|
67
71
|
export {};
|
|
@@ -62,6 +62,10 @@ export interface FormProps<FormValues> {
|
|
|
62
62
|
* Sets the form and its fields as disabled. Users cannot edit or focus on the fields.
|
|
63
63
|
*/
|
|
64
64
|
isDisabled?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* A test identifier for the form element. This will be applied as `data-testid` attribute.
|
|
67
|
+
*/
|
|
68
|
+
testId?: string;
|
|
65
69
|
}
|
|
66
70
|
export default function Form<FormValues extends Record<string, any> = {}>(props: FormProps<FormValues>): React.JSX.Element;
|
|
67
71
|
export {};
|