@planningcenter/tapestry-migration-cli 2.3.0-rc.7 → 2.3.0-rc.9

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 (41) hide show
  1. package/dist/tapestry-react-shim.cjs +5065 -0
  2. package/package.json +9 -5
  3. package/src/components/button/index.ts +45 -3
  4. package/src/components/button/transforms/auditSpreadProps.test.ts +352 -0
  5. package/src/components/button/transforms/auditSpreadProps.ts +24 -0
  6. package/src/components/button/transforms/childrenToLabel.test.ts +363 -0
  7. package/src/components/button/transforms/childrenToLabel.ts +84 -0
  8. package/src/components/button/transforms/commentOnVisualKindDifference.ts +24 -0
  9. package/src/components/button/transforms/convertStyleProps.test.ts +464 -0
  10. package/src/components/button/transforms/convertStyleProps.ts +16 -0
  11. package/src/components/button/transforms/iconToIconButton.test.ts +377 -0
  12. package/src/components/button/transforms/iconToIconButton.ts +53 -0
  13. package/src/components/button/transforms/removeAsButton.ts +15 -0
  14. package/src/components/button/transforms/removeDuplicateKeys.test.ts +302 -0
  15. package/src/components/button/transforms/removeDuplicateKeys.ts +8 -0
  16. package/src/components/button/transforms/reviewStyles.ts +17 -0
  17. package/src/components/button/transforms/spinnerToLoadingButton.test.ts +165 -0
  18. package/src/components/button/transforms/spinnerToLoadingButton.ts +14 -0
  19. package/src/components/button/transforms/unsupportedProps.ts +73 -0
  20. package/src/components/shared/actions/addCommentToAttribute.test.ts +45 -0
  21. package/src/components/shared/actions/addCommentToAttribute.ts +28 -0
  22. package/src/components/shared/actions/addCommentToUnsupportedProps.ts +29 -0
  23. package/src/components/shared/actions/getSpreadProps.ts +7 -0
  24. package/src/components/shared/actions/hasSpreadProps.ts +7 -0
  25. package/src/components/shared/actions/removeChildren.ts +7 -0
  26. package/src/components/shared/actions/removeDuplicateKeys.test.ts +280 -0
  27. package/src/components/shared/actions/removeDuplicateKeys.ts +45 -0
  28. package/src/components/shared/actions/removeUnusedImport.test.ts +302 -0
  29. package/src/components/shared/actions/removeUnusedImport.ts +81 -0
  30. package/src/components/shared/actions/transformElementName.test.ts +9 -9
  31. package/src/components/shared/actions/transformElementName.ts +13 -16
  32. package/src/components/shared/conditions/hasChildren.ts +5 -0
  33. package/src/components/shared/getJavaScriptTheme.ts +68 -0
  34. package/src/components/shared/jsThemeLoader.ts +85 -0
  35. package/src/components/shared/transformFactories/attributeTransformFactory.ts +14 -6
  36. package/src/components/shared/transformFactories/componentTransformFactory.ts +1 -1
  37. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +362 -0
  38. package/src/index.ts +4 -0
  39. package/src/stubs/stackViewPlugin.ts +33 -0
  40. package/src/stubs/tapestry-stub.ts +16 -0
  41. package/src/tapestry-react-shim.ts +7 -0
@@ -0,0 +1,464 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./convertStyleProps"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string, options = {}) {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ return transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ options
14
+ ) as string | null
15
+ }
16
+
17
+ describe("convertStyleProps transform", () => {
18
+ describe("KEEP_STYLE_PROPS - preserved as style object properties", () => {
19
+ it("should convert margin and padding props to style object", () => {
20
+ const source = `
21
+ import { Button } from "@planningcenter/tapestry-react"
22
+
23
+ export function TestComponent() {
24
+ return (
25
+ <Button
26
+ marginLeft={16}
27
+ paddingTop="8px"
28
+ marginHorizontal={20}
29
+ paddingVertical="12px"
30
+ >
31
+ Save
32
+ </Button>
33
+ )
34
+ }
35
+ `
36
+
37
+ const result = applyTransform(source)
38
+
39
+ expect(result).toContain("style={{")
40
+ expect(result).toContain('paddingTop: "8px"')
41
+ expect(result).toContain('paddingBottom: "12px"')
42
+ expect(result).toContain('marginLeft: "128px"')
43
+ expect(result).toContain('marginRight: "160px"')
44
+ expect(result).toContain("}}>Save")
45
+ expect(result).not.toContain("marginLeft={16}")
46
+ expect(result).not.toContain('paddingTop="8px"')
47
+ expect(result).not.toContain("marginHorizontal={20}")
48
+ expect(result).not.toContain('paddingVertical="12px"')
49
+ })
50
+
51
+ it("should convert alignment props to style object", () => {
52
+ const source = `
53
+ import { Button } from "@planningcenter/tapestry-react"
54
+
55
+ export function TestComponent() {
56
+ return (
57
+ <Button
58
+ alignSelf="center"
59
+ alignItems="flex-start"
60
+ justifyContent="space-between"
61
+ >
62
+ Align
63
+ </Button>
64
+ )
65
+ }
66
+ `
67
+
68
+ const result = applyTransform(source)
69
+
70
+ expect(result).toContain("style={{")
71
+ expect(result).toContain('alignSelf: "center"')
72
+ expect(result).toContain('alignItems: "flex-start"')
73
+ expect(result).toContain('justifyContent: "space-between"')
74
+ expect(result).toContain("}}>Align")
75
+ expect(result).not.toContain('alignSelf="center"')
76
+ expect(result).not.toContain('alignItems="flex-start"')
77
+ expect(result).not.toContain('justifyContent="space-between"')
78
+ })
79
+
80
+ it("should handle variable expressions in kept props", () => {
81
+ const source = `
82
+ import { Button } from "@planningcenter/tapestry-react"
83
+
84
+ export function TestComponent() {
85
+ const margin = 16
86
+ return <Button marginLeft={margin} paddingTop={spacing.small}>Save</Button>
87
+ }
88
+ `
89
+
90
+ const result = applyTransform(source)
91
+
92
+ expect(result).toContain("style={{")
93
+ expect(result).toContain("marginLeft: margin")
94
+ expect(result).toContain("paddingTop: spacing.small")
95
+ expect(result).toContain("}}>Save</Button>")
96
+ expect(result).not.toContain("marginLeft={margin}")
97
+ expect(result).not.toContain("paddingTop={spacing.small}")
98
+ })
99
+ })
100
+
101
+ describe("REMOVE_STYLE_PROPS - removed with comments", () => {
102
+ it("should remove height prop and add TODO comment", () => {
103
+ const source = `
104
+ import { Button } from "@planningcenter/tapestry-react"
105
+
106
+ export function TestComponent() {
107
+ return <Button height="40px" kind="primary">Save</Button>
108
+ }
109
+ `
110
+
111
+ const result = applyTransform(source)
112
+
113
+ expect(result).not.toContain('height="40px"')
114
+ expect(result).toContain(
115
+ 'TODO: tapestry-migration (height): height has been removed as this is covered by default styling: height: "40px"'
116
+ )
117
+ expect(result).toContain('kind="primary"')
118
+ })
119
+
120
+ it("should handle height with variable expression", () => {
121
+ const source = `
122
+ import { Button } from "@planningcenter/tapestry-react"
123
+
124
+ export function TestComponent() {
125
+ return <Button height={buttonHeight} disabled>Save</Button>
126
+ }
127
+ `
128
+
129
+ const result = applyTransform(source)
130
+
131
+ expect(result).not.toContain("height={buttonHeight}")
132
+ expect(result).toContain("TODO: tapestry-migration")
133
+ expect(result).toContain("buttonHeight")
134
+ expect(result).toContain("disabled")
135
+ })
136
+ })
137
+
138
+ describe("STYLE_PROP_MAPPINGS - special handling", () => {
139
+ it("should map distribution prop to justifyContent in styles", () => {
140
+ const source = `
141
+ import { Button } from "@planningcenter/tapestry-react"
142
+
143
+ export function TestComponent() {
144
+ return <Button distribution="center">Save</Button>
145
+ }
146
+ `
147
+
148
+ const result = applyTransform(source)
149
+
150
+ expect(result).toContain("style={{")
151
+ expect(result).toContain('justifyContent: "center"')
152
+ expect(result).toContain("}}>Save</Button>")
153
+ expect(result).not.toContain('distribution="center"')
154
+ })
155
+
156
+ it("should add audit comment for distribution='fill' special value", () => {
157
+ const source = `
158
+ import { Button } from "@planningcenter/tapestry-react"
159
+
160
+ export function TestComponent() {
161
+ return <Button distribution="fill" kind="primary">Save</Button>
162
+ }
163
+ `
164
+
165
+ const result = applyTransform(source)
166
+
167
+ expect(result).toContain(
168
+ 'TODO: tapestry-migration (styleProp): distribution="fill" needs manual review for replacement'
169
+ )
170
+ expect(result).not.toContain('<Button distribution="fill"')
171
+ expect(result).not.toContain("style={{")
172
+ expect(result).toContain('kind="primary"')
173
+ })
174
+
175
+ it("should handle distribution with variable expression", () => {
176
+ const source = `
177
+ import { Button } from "@planningcenter/tapestry-react"
178
+
179
+ export function TestComponent() {
180
+ const dist = "space-between"
181
+ return <Button distribution={dist}>Save</Button>
182
+ }
183
+ `
184
+
185
+ const result = applyTransform(source)
186
+
187
+ expect(result).toContain("style={{")
188
+ expect(result).toContain("justifyContent: dist")
189
+ expect(result).toContain("}}>Save</Button>")
190
+ expect(result).not.toContain("distribution={dist}")
191
+ })
192
+ })
193
+
194
+ describe("mixed style props", () => {
195
+ it("should handle combination of keep, remove, and mapping props", () => {
196
+ const source = `
197
+ import { Button } from "@planningcenter/tapestry-react"
198
+
199
+ export function TestComponent() {
200
+ return (
201
+ <Button
202
+ marginLeft={16}
203
+ height="40px"
204
+ distribution="center"
205
+ paddingTop="8px"
206
+ kind="primary"
207
+ >
208
+ Mixed Props
209
+ </Button>
210
+ )
211
+ }
212
+ `
213
+
214
+ const result = applyTransform(source)
215
+
216
+ expect(result).toContain("style={{")
217
+ expect(result).toContain('justifyContent: "center"')
218
+ expect(result).toContain('paddingTop: "8px"')
219
+ expect(result).toContain('marginLeft: "128px"')
220
+ expect(result).toContain("}}>")
221
+ expect(result).toContain(
222
+ 'TODO: tapestry-migration (height): height has been removed as this is covered by default styling: height: "40px"'
223
+ )
224
+ expect(result).not.toContain("marginLeft={16}")
225
+ expect(result).not.toContain('height="40px"')
226
+ expect(result).not.toContain('distribution="center"')
227
+ expect(result).not.toContain('paddingTop="8px"')
228
+ expect(result).toContain('kind="primary"')
229
+ })
230
+
231
+ it("should handle existing style prop and merge with converted props", () => {
232
+ const source = `
233
+ import { Button } from "@planningcenter/tapestry-react"
234
+
235
+ export function TestComponent() {
236
+ return (
237
+ <Button
238
+ style={{ color: "red", fontSize: 14 }}
239
+ marginLeft={16}
240
+ paddingTop="8px"
241
+ >
242
+ Existing Style
243
+ </Button>
244
+ )
245
+ }
246
+ `
247
+
248
+ const result = applyTransform(source)
249
+
250
+ expect(result).not.toBeNull()
251
+ expect(result).toContain("style={{")
252
+ expect(result).toContain("marginLeft:")
253
+ expect(result).toContain("paddingTop:")
254
+ expect(result).not.toContain("marginLeft={16}")
255
+ expect(result).not.toContain('paddingTop="8px"')
256
+ })
257
+ })
258
+
259
+ describe("no style props", () => {
260
+ it("should return null when no style props are present", () => {
261
+ const source = `
262
+ import { Button } from "@planningcenter/tapestry-react"
263
+
264
+ export function TestComponent() {
265
+ return <Button kind="primary" disabled onClick={handleClick}>No Styles</Button>
266
+ }
267
+ `
268
+
269
+ const result = applyTransform(source)
270
+
271
+ expect(result).toBeNull()
272
+ })
273
+
274
+ it("should handle Button with no props at all", () => {
275
+ const source = `
276
+ import { Button } from "@planningcenter/tapestry-react"
277
+
278
+ export function TestComponent() {
279
+ return <Button>No Props</Button>
280
+ }
281
+ `
282
+
283
+ const result = applyTransform(source)
284
+
285
+ expect(result).toBeNull()
286
+ })
287
+ })
288
+
289
+ describe("import handling", () => {
290
+ it("should only process files that import Button from tapestry-react", () => {
291
+ const source = `
292
+ import { Button } from "some-other-library"
293
+
294
+ export function TestComponent() {
295
+ return <Button marginLeft={16} paddingTop="8px">Save</Button>
296
+ }
297
+ `
298
+
299
+ const result = applyTransform(source)
300
+
301
+ expect(result).toBeNull()
302
+ })
303
+
304
+ it("should handle aliased Button imports", () => {
305
+ const source = `
306
+ import { Button as TapestryButton } from "@planningcenter/tapestry-react"
307
+
308
+ export function TestComponent() {
309
+ return <TapestryButton marginLeft={16} paddingTop="8px">Save</TapestryButton>
310
+ }
311
+ `
312
+
313
+ const result = applyTransform(source)
314
+
315
+ expect(result).not.toBeNull()
316
+ expect(result).toContain("<TapestryButton")
317
+ expect(result).toContain("style={{")
318
+ expect(result).toContain("marginLeft:")
319
+ expect(result).toContain("paddingTop:")
320
+ expect(result).toContain("</TapestryButton>")
321
+ })
322
+ })
323
+
324
+ describe("complex expressions", () => {
325
+ it("should handle complex expressions in style props", () => {
326
+ const source = `
327
+ import { Button } from "@planningcenter/tapestry-react"
328
+
329
+ export function TestComponent() {
330
+ const theme = { spacing: { small: 8, large: 16 } }
331
+ return (
332
+ <Button
333
+ marginLeft={theme.spacing.large}
334
+ paddingTop={isLarge ? "16px" : "8px"}
335
+ alignSelf={responsive ? "stretch" : "center"}
336
+ >
337
+ Complex
338
+ </Button>
339
+ )
340
+ }
341
+ `
342
+
343
+ const result = applyTransform(source)
344
+
345
+ expect(result).not.toBeNull()
346
+ expect(result).toContain("style={{")
347
+ expect(result).toContain('paddingTop: isLarge ? "16px" : "8px"')
348
+ expect(result).toContain('alignSelf: responsive ? "stretch" : "center"')
349
+ expect(result).toContain("marginLeft: theme.spacing.large")
350
+ })
351
+
352
+ it("should handle numeric literals correctly", () => {
353
+ const source = `
354
+ import { Button } from "@planningcenter/tapestry-react"
355
+
356
+ export function TestComponent() {
357
+ return <Button marginLeft={16} paddingTop={8} marginRight={0}>Numbers</Button>
358
+ }
359
+ `
360
+
361
+ const result = applyTransform(source)
362
+
363
+ expect(result).not.toBeNull()
364
+
365
+ expect(result).toContain("style={{")
366
+ expect(result).toContain('paddingTop: "64px"')
367
+ expect(result).toContain('marginLeft: "128px"')
368
+ expect(result).toContain('marginRight: "0px"')
369
+ expect(result).toContain("}}>Numbers</Button>")
370
+ expect(result).not.toContain("marginLeft={16}")
371
+ expect(result).not.toContain("paddingTop={8}")
372
+ expect(result).not.toContain("marginRight={0}")
373
+ })
374
+
375
+ it("should handle boolean literals in style props", () => {
376
+ const source = `
377
+ import { Button } from "@planningcenter/tapestry-react"
378
+
379
+ export function TestComponent() {
380
+ return <Button marginLeft={true ? 16 : 0} alignSelf="center">Boolean</Button>
381
+ }
382
+ `
383
+
384
+ const result = applyTransform(source)
385
+
386
+ expect(result).not.toBeNull()
387
+ expect(result).toContain("style={{")
388
+ expect(result).toContain("marginLeft:")
389
+ expect(result).toContain("alignSelf:")
390
+ })
391
+ })
392
+
393
+ describe("edge cases", () => {
394
+ it("should handle self-closing Button elements", () => {
395
+ const source = `
396
+ import { Button } from "@planningcenter/tapestry-react"
397
+
398
+ export function TestComponent() {
399
+ return <Button marginLeft={16} paddingTop="8px" />
400
+ }
401
+ `
402
+
403
+ const result = applyTransform(source)
404
+
405
+ expect(result).not.toBeNull()
406
+ expect(result).toContain(`<Button
407
+ style={{
408
+ paddingTop: "8px",
409
+ marginLeft: "128px"
410
+ }} />`)
411
+ })
412
+
413
+ it("should handle empty/undefined attribute values gracefully", () => {
414
+ const source = `
415
+ import { Button } from "@planningcenter/tapestry-react"
416
+
417
+ export function TestComponent() {
418
+ return <Button marginLeft={16} paddingTop="">Save</Button>
419
+ }
420
+ `
421
+
422
+ const result = applyTransform(source)
423
+
424
+ expect(result).toContain(`<Button
425
+ style={{
426
+ paddingTop: "",
427
+ marginLeft: "128px"
428
+ }}>`)
429
+ })
430
+
431
+ it("should handle multiple Buttons with different style combinations", () => {
432
+ const source = `
433
+ import { Button } from "@planningcenter/tapestry-react"
434
+
435
+ export function TestComponent() {
436
+ return (
437
+ <form>
438
+ <Button marginLeft={16} height="40px">Submit</Button>
439
+ <Button distribution="fill" kind="secondary">Fill</Button>
440
+ <Button paddingTop="8px" alignSelf="center">Centered</Button>
441
+ <Button kind="primary">No Styles</Button>
442
+ </form>
443
+ )
444
+ }
445
+ `
446
+
447
+ const result = applyTransform(source)
448
+
449
+ expect(result).toContain(`<Button
450
+ style={{
451
+ marginLeft: "128px"
452
+ }}>`)
453
+ expect(result).toContain(
454
+ `{/* TODO: tapestry-migration (styleProp): distribution="fill" needs manual review for replacement */
455
+ }<Button kind="secondary">`
456
+ )
457
+ expect(result).toContain(`style={{
458
+ alignSelf: "center",
459
+ paddingTop: "8px"
460
+ }}`)
461
+ expect(result).toContain('<Button kind="primary">')
462
+ })
463
+ })
464
+ })
@@ -0,0 +1,16 @@
1
+ import { stackViewPlugin } from "../../../stubs/stackViewPlugin"
2
+ import { stylePropTransformFactory } from "../../shared/transformFactories/stylePropTransformFactory"
3
+
4
+ export default stylePropTransformFactory({
5
+ plugin: stackViewPlugin,
6
+ stylePropMapping: {
7
+ distribution: {
8
+ defaultMapping: "justifyContent",
9
+ specialValues: { fill: "AUDIT" },
10
+ },
11
+ },
12
+ stylesToKeep: ["visible"],
13
+ stylesToRemove: ["height", "minHeight"],
14
+ targetComponent: "Button",
15
+ targetPackage: "@planningcenter/tapestry-react",
16
+ })