@planningcenter/tapestry-migration-cli 3.1.0-rc.2 → 3.1.0-rc.21

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 (115) hide show
  1. package/dist/tapestry-react-shim.cjs +7 -1
  2. package/package.json +3 -3
  3. package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
  4. package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
  5. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
  6. package/src/components/input/index.ts +66 -0
  7. package/src/components/input/transformableInput.ts +49 -0
  8. package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
  9. package/src/components/input/transforms/auditSpreadProps.ts +26 -0
  10. package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
  11. package/src/components/input/transforms/autoWidthTransform.ts +41 -0
  12. package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
  13. package/src/components/input/transforms/convertStyleProps.ts +12 -0
  14. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
  15. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
  16. package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
  17. package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
  18. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +469 -0
  19. package/src/components/input/transforms/mergeFieldIntoInput.ts +7 -0
  20. package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
  21. package/src/components/input/transforms/mergeInputLabel.ts +204 -0
  22. package/src/components/input/transforms/moveInputImport.test.ts +166 -0
  23. package/src/components/input/transforms/moveInputImport.ts +14 -0
  24. package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
  25. package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
  26. package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
  27. package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
  28. package/src/components/input/transforms/removeAsInput.test.ts +139 -0
  29. package/src/components/input/transforms/removeAsInput.ts +20 -0
  30. package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
  31. package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
  32. package/src/components/input/transforms/removeInputBox.test.ts +352 -0
  33. package/src/components/input/transforms/removeInputBox.ts +109 -0
  34. package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
  35. package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
  36. package/src/components/input/transforms/removeTypeInput.test.ts +212 -0
  37. package/src/components/input/transforms/removeTypeInput.ts +22 -0
  38. package/src/components/input/transforms/removeTypeText.test.ts +160 -0
  39. package/src/components/input/transforms/removeTypeText.ts +17 -0
  40. package/src/components/input/transforms/sizeMapping.test.ts +198 -0
  41. package/src/components/input/transforms/sizeMapping.ts +17 -0
  42. package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
  43. package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
  44. package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
  45. package/src/components/input/transforms/stateToInvalid.ts +59 -0
  46. package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
  47. package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
  48. package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
  49. package/src/components/input/transforms/unsupportedProps.ts +84 -0
  50. package/src/components/link/transforms/reviewStyles.test.ts +0 -1
  51. package/src/components/select/index.ts +58 -0
  52. package/src/components/select/transformableSelect.ts +7 -0
  53. package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
  54. package/src/components/select/transforms/auditSpreadProps.ts +26 -0
  55. package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
  56. package/src/components/select/transforms/childrenToOptions.ts +295 -0
  57. package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
  58. package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
  59. package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
  60. package/src/components/select/transforms/convertStyleProps.ts +12 -0
  61. package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
  62. package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
  63. package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
  64. package/src/components/select/transforms/innerRefToRef.ts +18 -0
  65. package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
  66. package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
  67. package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
  68. package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
  69. package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
  70. package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
  71. package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
  72. package/src/components/select/transforms/moveSelectImport.ts +14 -0
  73. package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
  74. package/src/components/select/transforms/removeDefaultProps.ts +112 -0
  75. package/src/components/select/transforms/sizeMapping.test.ts +188 -0
  76. package/src/components/select/transforms/sizeMapping.ts +17 -0
  77. package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
  78. package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
  79. package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
  80. package/src/components/select/transforms/stateToInvalid.ts +59 -0
  81. package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
  82. package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
  83. package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
  84. package/src/components/select/transforms/unsupportedProps.ts +44 -0
  85. package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
  86. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +102 -0
  87. package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
  88. package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
  89. package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
  90. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +56 -17
  91. package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
  92. package/src/components/text-area/index.ts +48 -0
  93. package/src/components/text-area/transforms/auditSpreadProps.test.ts +139 -0
  94. package/src/components/text-area/transforms/auditSpreadProps.ts +10 -0
  95. package/src/components/text-area/transforms/convertStyleProps.test.ts +158 -0
  96. package/src/components/text-area/transforms/convertStyleProps.ts +10 -0
  97. package/src/components/text-area/transforms/innerRefToRef.test.ts +206 -0
  98. package/src/components/text-area/transforms/innerRefToRef.ts +14 -0
  99. package/src/components/text-area/transforms/mergeFieldIntoTextArea.test.ts +477 -0
  100. package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +5 -0
  101. package/src/components/text-area/transforms/moveTextAreaImport.test.ts +168 -0
  102. package/src/components/text-area/transforms/moveTextAreaImport.ts +13 -0
  103. package/src/components/text-area/transforms/removeDuplicateKeys.test.ts +129 -0
  104. package/src/components/text-area/transforms/removeDuplicateKeys.ts +8 -0
  105. package/src/components/text-area/transforms/removeRedundantAriaLabel.test.ts +183 -0
  106. package/src/components/text-area/transforms/removeRedundantAriaLabel.ts +59 -0
  107. package/src/components/text-area/transforms/sizeMapping.test.ts +199 -0
  108. package/src/components/text-area/transforms/sizeMapping.ts +15 -0
  109. package/src/components/text-area/transforms/stateToInvalid.test.ts +204 -0
  110. package/src/components/text-area/transforms/stateToInvalid.ts +57 -0
  111. package/src/components/text-area/transforms/stateToInvalidTernary.test.ts +133 -0
  112. package/src/components/text-area/transforms/stateToInvalidTernary.ts +11 -0
  113. package/src/components/text-area/transforms/unsupportedProps.test.ts +275 -0
  114. package/src/components/text-area/transforms/unsupportedProps.ts +35 -0
  115. package/src/index.ts +2 -1
@@ -0,0 +1,469 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./mergeFieldIntoInput"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string | null {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ return transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ }
16
+
17
+ describe("mergeFieldIntoInput transform", () => {
18
+ describe("basic prop merging", () => {
19
+ it("merges label prop from Field into Input", () => {
20
+ const input = `
21
+ import { Field, Input } from "@planningcenter/tapestry-react"
22
+
23
+ function Test() {
24
+ return (
25
+ <Box>
26
+ <Field label="Name"><Input /></Field>
27
+ </Box>
28
+ )
29
+ }
30
+ `.trim()
31
+
32
+ const result = applyTransform(input)
33
+ expect(result).not.toBeNull()
34
+ expect(result).not.toContain("<Field")
35
+ expect(result).toContain('label="Name"')
36
+ expect(result).toContain("<Input")
37
+ })
38
+
39
+ it("renames feedbackText to description on Input", () => {
40
+ const input = `
41
+ import { Field, Input } from "@planningcenter/tapestry-react"
42
+
43
+ function Test() {
44
+ return (
45
+ <Field feedbackText="Required"><Input /></Field>
46
+ )
47
+ }
48
+ `.trim()
49
+
50
+ const result = applyTransform(input)
51
+ expect(result).not.toBeNull()
52
+ expect(result).not.toContain("<Field")
53
+ expect(result).not.toContain("feedbackText")
54
+ expect(result).toContain('description="Required"')
55
+ })
56
+
57
+ it("copies state prop as-is to Input", () => {
58
+ const input = `
59
+ import { Field, Input } from "@planningcenter/tapestry-react"
60
+
61
+ function Test() {
62
+ return (
63
+ <Field state="error"><Input /></Field>
64
+ )
65
+ }
66
+ `.trim()
67
+
68
+ const result = applyTransform(input)
69
+ expect(result).not.toBeNull()
70
+ expect(result).not.toContain("<Field")
71
+ expect(result).toContain('state="error"')
72
+ })
73
+
74
+ it("preserves dynamic label value", () => {
75
+ const input = `
76
+ import { Field, Input } from "@planningcenter/tapestry-react"
77
+
78
+ function Test() {
79
+ return (
80
+ <Field label={labelText}><Input /></Field>
81
+ )
82
+ }
83
+ `.trim()
84
+
85
+ const result = applyTransform(input)
86
+ expect(result).not.toBeNull()
87
+ expect(result).not.toContain("<Field")
88
+ expect(result).toContain("label={labelText}")
89
+ })
90
+ })
91
+
92
+ describe("unsupported props", () => {
93
+ it("adds TODO comment for helpContent and removes Field", () => {
94
+ const input = `
95
+ import { Field, Input } from "@planningcenter/tapestry-react"
96
+
97
+ function Test() {
98
+ return (
99
+ <Field helpContent="Help"><Input /></Field>
100
+ )
101
+ }
102
+ `.trim()
103
+
104
+ const result = applyTransform(input)
105
+ expect(result).not.toBeNull()
106
+ expect(result).not.toContain("<Field")
107
+ expect(result).toContain("TODO: tapestry-migration (mergeFieldIntoInput)")
108
+ expect(result).toContain("helpContent")
109
+ expect(result).toContain("not supported by Input")
110
+ })
111
+
112
+ it("adds a comment per unsupported prop when multiple unsupported props are present", () => {
113
+ const input = `
114
+ import { Field, Input } from "@planningcenter/tapestry-react"
115
+
116
+ function Test() {
117
+ return (
118
+ <Field inline compact spacing={8}><Input /></Field>
119
+ )
120
+ }
121
+ `.trim()
122
+
123
+ const result = applyTransform(input)
124
+ expect(result).not.toBeNull()
125
+ expect(result).not.toContain("<Field")
126
+ expect(result).toContain("inline")
127
+ expect(result).toContain("compact")
128
+ expect(result).toContain("spacing")
129
+ })
130
+ })
131
+
132
+ describe("Field with no props", () => {
133
+ it("unwraps Field with no props cleanly", () => {
134
+ const input = `
135
+ import { Field, Input } from "@planningcenter/tapestry-react"
136
+
137
+ function Test() {
138
+ return (
139
+ <Field><Input /></Field>
140
+ )
141
+ }
142
+ `.trim()
143
+
144
+ const result = applyTransform(input)
145
+ expect(result).not.toBeNull()
146
+ expect(result).not.toContain("<Field")
147
+ expect(result).toContain("<Input")
148
+ expect(result).not.toContain("TODO")
149
+ })
150
+ })
151
+
152
+ describe("conflict detection", () => {
153
+ it("adds warning comment when Input already has label and keeps Input's existing label", () => {
154
+ const input = `
155
+ import { Field, Input } from "@planningcenter/tapestry-react"
156
+
157
+ function Test() {
158
+ return (
159
+ <Field label="Name"><Input label="Other" /></Field>
160
+ )
161
+ }
162
+ `.trim()
163
+
164
+ const result = applyTransform(input)
165
+ expect(result).not.toBeNull()
166
+ expect(result).not.toContain("<Field")
167
+ expect(result).toContain('label="Other"')
168
+ expect(result).toContain("TODO: tapestry-migration (mergeFieldIntoInput)")
169
+ expect(result).toContain("already has label")
170
+ })
171
+
172
+ it("merges label from Field when Input has description but not label", () => {
173
+ const input = `
174
+ import { Field, Input } from "@planningcenter/tapestry-react"
175
+
176
+ function Test() {
177
+ return (
178
+ <Field label="Name"><Input description="x" /></Field>
179
+ )
180
+ }
181
+ `.trim()
182
+
183
+ const result = applyTransform(input)
184
+ expect(result).not.toBeNull()
185
+ expect(result).not.toContain("<Field")
186
+ expect(result).toContain('label="Name"')
187
+ expect(result).toContain('description="x"')
188
+ })
189
+ })
190
+
191
+ describe("multiple children", () => {
192
+ it("adds comment to Input and leaves Field when Field has multiple children", () => {
193
+ const input = `
194
+ import { Field, Input } from "@planningcenter/tapestry-react"
195
+
196
+ function Test() {
197
+ return (
198
+ <Field><Input /><button /></Field>
199
+ )
200
+ }
201
+ `.trim()
202
+
203
+ const result = applyTransform(input)
204
+ expect(result).not.toBeNull()
205
+ expect(result).toContain("<Field")
206
+ expect(result).toContain("TODO: tapestry-migration (mergeFieldIntoInput)")
207
+ expect(result).toContain("multiple children")
208
+ })
209
+ })
210
+
211
+ describe("non-Input child", () => {
212
+ it("returns null when Field contains only a non-Input child", () => {
213
+ const input = `
214
+ import { Field, Input } from "@planningcenter/tapestry-react"
215
+
216
+ function Test() {
217
+ return (
218
+ <Field><textarea /></Field>
219
+ )
220
+ }
221
+ `.trim()
222
+
223
+ const result = applyTransform(input)
224
+ expect(result).toBeNull()
225
+ })
226
+ })
227
+
228
+ describe("import cleanup", () => {
229
+ it("removes Field from import specifiers when all Fields are converted", () => {
230
+ const input = `
231
+ import { Field, Input } from "@planningcenter/tapestry-react"
232
+
233
+ function Test() {
234
+ return (
235
+ <Field label="Name"><Input /></Field>
236
+ )
237
+ }
238
+ `.trim()
239
+
240
+ const result = applyTransform(input)
241
+ expect(result).not.toBeNull()
242
+ expect(result).not.toContain("Field")
243
+ expect(result).toContain("Input")
244
+ })
245
+
246
+ it("removes entire import declaration when Field was the only specifier", () => {
247
+ const input = `
248
+ import { Field } from "@planningcenter/tapestry-react"
249
+ import { Input } from "@planningcenter/tapestry-react"
250
+
251
+ function Test() {
252
+ return (
253
+ <Field><Input /></Field>
254
+ )
255
+ }
256
+ `.trim()
257
+
258
+ const result = applyTransform(input)
259
+ expect(result).not.toBeNull()
260
+ expect(result).not.toContain("{ Field }")
261
+ })
262
+
263
+ it("keeps Field in import when some Fields cannot be converted", () => {
264
+ const input = `
265
+ import { Field, Input } from "@planningcenter/tapestry-react"
266
+
267
+ function Test() {
268
+ return (
269
+ <div>
270
+ <Field label="Name"><Input /></Field>
271
+ <Field><Input /><button /></Field>
272
+ </div>
273
+ )
274
+ }
275
+ `.trim()
276
+
277
+ const result = applyTransform(input)
278
+ expect(result).not.toBeNull()
279
+ expect(result).toContain("Field")
280
+ })
281
+ })
282
+
283
+ describe("full prop set", () => {
284
+ it("merges label, feedbackText, state and flags helpContent as unsupported", () => {
285
+ const input = `
286
+ import { Field, Input } from "@planningcenter/tapestry-react"
287
+
288
+ function Test() {
289
+ return (
290
+ <Field label="Name" feedbackText="Required" state="error" helpContent="Help">
291
+ <Input />
292
+ </Field>
293
+ )
294
+ }
295
+ `.trim()
296
+
297
+ const result = applyTransform(input)
298
+ expect(result).not.toBeNull()
299
+ expect(result).not.toContain("<Field")
300
+ expect(result).toContain('label="Name"')
301
+ expect(result).toContain('description="Required"')
302
+ expect(result).toContain('state="error"')
303
+ expect(result).toContain("helpContent")
304
+ expect(result).toContain("not supported by Input")
305
+ })
306
+ })
307
+
308
+ describe("key prop handling", () => {
309
+ it("moves key from Field to Input when Input has no key", () => {
310
+ const input = `
311
+ import { Field, Input } from "@planningcenter/tapestry-react"
312
+
313
+ function Test() {
314
+ return (
315
+ <Box>
316
+ <Field key="item-1" label="Name"><Input /></Field>
317
+ </Box>
318
+ )
319
+ }
320
+ `.trim()
321
+
322
+ const result = applyTransform(input)
323
+ expect(result).not.toBeNull()
324
+ expect(result).not.toContain("<Field")
325
+ expect(result).toContain('key="item-1"')
326
+ expect(result).toContain("<Input")
327
+ })
328
+
329
+ it("keeps Input's key when both Field and Input have key", () => {
330
+ const input = `
331
+ import { Field, Input } from "@planningcenter/tapestry-react"
332
+
333
+ function Test() {
334
+ return (
335
+ <Box>
336
+ <Field key="field-1" label="Name"><Input key="input-1" /></Field>
337
+ </Box>
338
+ )
339
+ }
340
+ `.trim()
341
+
342
+ const result = applyTransform(input)
343
+ expect(result).not.toBeNull()
344
+ expect(result).not.toContain("<Field")
345
+ expect(result).toContain('key="input-1"')
346
+ })
347
+ })
348
+
349
+ describe("spread props on Field", () => {
350
+ it("bails out with TODO comment when Field has spread props", () => {
351
+ const input = `
352
+ import { Field, Input } from "@planningcenter/tapestry-react"
353
+
354
+ function Test() {
355
+ return (
356
+ <Field {...fieldProps}><Input /></Field>
357
+ )
358
+ }
359
+ `.trim()
360
+
361
+ const result = applyTransform(input)
362
+ expect(result).not.toBeNull()
363
+ expect(result).toContain("<Field")
364
+ expect(result).toContain("TODO: tapestry-migration (mergeFieldIntoInput)")
365
+ expect(result).toContain("spread props")
366
+ })
367
+
368
+ it("bails out when Field has spread props mixed with regular props", () => {
369
+ const input = `
370
+ import { Field, Input } from "@planningcenter/tapestry-react"
371
+
372
+ function Test() {
373
+ return (
374
+ <Field label="Name" {...fieldProps}><Input /></Field>
375
+ )
376
+ }
377
+ `.trim()
378
+
379
+ const result = applyTransform(input)
380
+ expect(result).not.toBeNull()
381
+ expect(result).toContain("<Field")
382
+ expect(result).toContain("spread props")
383
+ })
384
+ })
385
+
386
+ describe("not from tapestry-react", () => {
387
+ it("returns null when Field is not imported from tapestry-react", () => {
388
+ const input = `
389
+ import { Field } from "some-other-library"
390
+ import { Input } from "@planningcenter/tapestry-react"
391
+
392
+ function Test() {
393
+ return (
394
+ <Field label="Name"><Input /></Field>
395
+ )
396
+ }
397
+ `.trim()
398
+
399
+ const result = applyTransform(input)
400
+ expect(result).toBeNull()
401
+ })
402
+
403
+ it("returns null when Input is not imported from tapestry-react", () => {
404
+ const input = `
405
+ import { Field } from "@planningcenter/tapestry-react"
406
+ import { Input } from "some-other-library"
407
+
408
+ function Test() {
409
+ return (
410
+ <Field label="Name"><Input /></Field>
411
+ )
412
+ }
413
+ `.trim()
414
+
415
+ const result = applyTransform(input)
416
+ expect(result).toBeNull()
417
+ })
418
+
419
+ it("returns null when neither import exists", () => {
420
+ const input = `
421
+ import { Button } from "@planningcenter/tapestry-react"
422
+
423
+ function Test() {
424
+ return <Button label="Click" />
425
+ }
426
+ `.trim()
427
+
428
+ const result = applyTransform(input)
429
+ expect(result).toBeNull()
430
+ })
431
+ })
432
+
433
+ describe("aliased imports", () => {
434
+ it("handles aliased Field import", () => {
435
+ const input = `
436
+ import { Field as TapField, Input } from "@planningcenter/tapestry-react"
437
+
438
+ function Test() {
439
+ return (
440
+ <TapField label="Name"><Input /></TapField>
441
+ )
442
+ }
443
+ `.trim()
444
+
445
+ const result = applyTransform(input)
446
+ expect(result).not.toBeNull()
447
+ expect(result).not.toContain("<TapField")
448
+ expect(result).toContain('label="Name"')
449
+ })
450
+
451
+ it("handles aliased Input import", () => {
452
+ const input = `
453
+ import { Field, Input as TapInput } from "@planningcenter/tapestry-react"
454
+
455
+ function Test() {
456
+ return (
457
+ <Field label="Name"><TapInput /></Field>
458
+ )
459
+ }
460
+ `.trim()
461
+
462
+ const result = applyTransform(input)
463
+ expect(result).not.toBeNull()
464
+ expect(result).not.toContain("<Field")
465
+ expect(result).toContain('label="Name"')
466
+ expect(result).toContain("<TapInput")
467
+ })
468
+ })
469
+ })
@@ -0,0 +1,7 @@
1
+ import { mergeFieldFactory } from "../../shared/transformFactories/mergeFieldFactory"
2
+ import { transformableInput } from "../transformableInput"
3
+
4
+ export default mergeFieldFactory({
5
+ condition: transformableInput,
6
+ targetComponent: "Input",
7
+ })