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