@planningcenter/tapestry-migration-cli 2.2.1-qa-362.0 → 2.3.0-rc.10

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 (64) hide show
  1. package/dist/tapestry-react-shim.cjs +5065 -0
  2. package/package.json +9 -5
  3. package/src/components/button/index.ts +53 -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/iconLeftToPrefix.test.ts +432 -0
  12. package/src/components/button/transforms/iconLeftToPrefix.ts +33 -0
  13. package/src/components/button/transforms/iconRightToSuffix.test.ts +407 -0
  14. package/src/components/button/transforms/iconRightToSuffix.ts +33 -0
  15. package/src/components/button/transforms/iconToIconButton.test.ts +377 -0
  16. package/src/components/button/transforms/iconToIconButton.ts +53 -0
  17. package/src/components/button/transforms/removeAsButton.ts +15 -0
  18. package/src/components/button/transforms/removeDuplicateKeys.test.ts +302 -0
  19. package/src/components/button/transforms/removeDuplicateKeys.ts +8 -0
  20. package/src/components/button/transforms/reviewStyles.ts +17 -0
  21. package/src/components/button/transforms/spinnerToLoadingButton.test.ts +165 -0
  22. package/src/components/button/transforms/spinnerToLoadingButton.ts +14 -0
  23. package/src/components/button/transforms/themeVariantToKind.test.ts +401 -0
  24. package/src/components/button/transforms/themeVariantToKind.ts +90 -0
  25. package/src/components/button/transforms/tooltipToWrapper.test.ts +392 -0
  26. package/src/components/button/transforms/tooltipToWrapper.ts +35 -0
  27. package/src/components/button/transforms/unsupportedProps.ts +73 -0
  28. package/src/components/shared/actions/addAttribute.test.ts +300 -0
  29. package/src/components/shared/actions/addAttribute.ts +65 -0
  30. package/src/components/shared/actions/addComment.test.ts +1 -1
  31. package/src/components/shared/actions/addCommentToAttribute.test.ts +45 -0
  32. package/src/components/shared/actions/addCommentToAttribute.ts +28 -0
  33. package/src/components/shared/actions/addCommentToUnsupportedProps.ts +29 -0
  34. package/src/components/shared/actions/convertAttributeFromObjectToJSXElement.test.ts +139 -0
  35. package/src/components/shared/actions/convertAttributeFromObjectToJSXElement.ts +81 -0
  36. package/src/components/shared/actions/createWrapper.test.ts +642 -0
  37. package/src/components/shared/actions/createWrapper.ts +70 -0
  38. package/src/components/shared/actions/getAttribute.ts +18 -0
  39. package/src/components/shared/actions/getAttributeValue.test.ts +261 -0
  40. package/src/components/shared/actions/getAttributeValue.ts +15 -0
  41. package/src/components/shared/actions/getAttributeValueAsProps.ts +57 -0
  42. package/src/components/shared/actions/getSpreadProps.ts +7 -0
  43. package/src/components/shared/actions/hasSpreadProps.ts +7 -0
  44. package/src/components/shared/actions/removeChildren.ts +7 -0
  45. package/src/components/shared/actions/removeDuplicateKeys.test.ts +280 -0
  46. package/src/components/shared/actions/removeDuplicateKeys.ts +45 -0
  47. package/src/components/shared/actions/removeUnusedImport.test.ts +302 -0
  48. package/src/components/shared/actions/removeUnusedImport.ts +81 -0
  49. package/src/components/shared/actions/transformElementName.test.ts +9 -9
  50. package/src/components/shared/actions/transformElementName.ts +13 -16
  51. package/src/components/shared/conditions/hasChildren.ts +5 -0
  52. package/src/components/shared/getJavaScriptTheme.ts +68 -0
  53. package/src/components/shared/jsThemeLoader.ts +85 -0
  54. package/src/components/shared/transformFactories/attributeCombineFactory.test.ts +374 -0
  55. package/src/components/shared/transformFactories/attributeCombineFactory.ts +300 -0
  56. package/src/components/shared/transformFactories/attributeTransformFactory.ts +14 -6
  57. package/src/components/shared/transformFactories/componentTransformFactory.ts +1 -1
  58. package/src/components/shared/transformFactories/helpers/addImport.test.ts +278 -0
  59. package/src/components/shared/transformFactories/helpers/manageImports.ts +53 -20
  60. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +362 -0
  61. package/src/index.ts +4 -0
  62. package/src/stubs/stackViewPlugin.ts +33 -0
  63. package/src/stubs/tapestry-stub.ts +16 -0
  64. package/src/tapestry-react-shim.ts +7 -0
@@ -0,0 +1,302 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeDuplicateKeys"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string) {
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("removeDuplicateKeys transform", () => {
18
+ describe("Button elements with duplicate attributes", () => {
19
+ it("should remove duplicate attributes from Button elements", () => {
20
+ const source = `
21
+ import { Button } from "@planningcenter/tapestry-react"
22
+
23
+ export function TestComponent() {
24
+ return <Button kind="primary" disabled kind="secondary">Save</Button>
25
+ }
26
+ `
27
+
28
+ const result = applyTransform(source)
29
+
30
+ expect(result).not.toBeNull()
31
+ expect(result).toContain('kind="primary"')
32
+ expect(result).not.toContain('kind="secondary"')
33
+ expect(result).toContain("disabled")
34
+ expect(result).toContain("Save")
35
+ })
36
+
37
+ it("should handle multiple Button elements with duplicates", () => {
38
+ const source = `
39
+ import { Button } from "@planningcenter/tapestry-react"
40
+
41
+ export function TestComponent() {
42
+ return (
43
+ <div>
44
+ <Button kind="primary" size="large" kind="secondary">Save</Button>
45
+ <Button disabled loading disabled>Cancel</Button>
46
+ </div>
47
+ )
48
+ }
49
+ `
50
+
51
+ const result = applyTransform(source)
52
+
53
+ expect(result).not.toBeNull()
54
+ expect(result).toContain('kind="primary"')
55
+ expect(result).not.toContain('kind="secondary"')
56
+ expect(result).toContain('size="large"')
57
+
58
+ expect(result).toContain("<Button disabled loading>")
59
+ })
60
+
61
+ it("should preserve first occurrence of duplicate attributes", () => {
62
+ const source = `
63
+ import { Button } from "@planningcenter/tapestry-react"
64
+
65
+ export function TestComponent() {
66
+ return <Button onClick={handleFirst} aria-label="First" onClick={handleSecond} aria-label="Second">Click</Button>
67
+ }
68
+ `
69
+
70
+ const result = applyTransform(source)
71
+
72
+ expect(result).not.toBeNull()
73
+ expect(result).toContain("onClick={handleFirst}")
74
+ expect(result).not.toContain("onClick={handleSecond}")
75
+ expect(result).toContain('aria-label="First"')
76
+ expect(result).not.toContain('aria-label="Second"')
77
+ })
78
+ })
79
+
80
+ describe("Button elements without duplicate attributes", () => {
81
+ it("should return null when no duplicates exist", () => {
82
+ const source = `
83
+ import { Button } from "@planningcenter/tapestry-react"
84
+
85
+ export function TestComponent() {
86
+ return <Button kind="primary" size="large" disabled>Save</Button>
87
+ }
88
+ `
89
+
90
+ const result = applyTransform(source)
91
+
92
+ expect(result).toBeNull() // No changes needed
93
+ })
94
+
95
+ it("should return null for Button with no attributes", () => {
96
+ const source = `
97
+ import { Button } from "@planningcenter/tapestry-react"
98
+
99
+ export function TestComponent() {
100
+ return <Button>Save</Button>
101
+ }
102
+ `
103
+
104
+ const result = applyTransform(source)
105
+
106
+ expect(result).toBeNull()
107
+ })
108
+ })
109
+
110
+ describe("import handling", () => {
111
+ it("should only process files that import Button from tapestry-react", () => {
112
+ const source = `
113
+ import { Button } from "some-other-library"
114
+
115
+ export function TestComponent() {
116
+ return <Button kind="primary" kind="secondary">Save</Button>
117
+ }
118
+ `
119
+
120
+ const result = applyTransform(source)
121
+
122
+ expect(result).toBeNull() // Should not process non-tapestry Buttons
123
+ })
124
+
125
+ it("should handle aliased Button imports", () => {
126
+ const source = `
127
+ import { Button as TapestryButton } from "@planningcenter/tapestry-react"
128
+
129
+ export function TestComponent() {
130
+ return <TapestryButton kind="primary" disabled kind="secondary">Save</TapestryButton>
131
+ }
132
+ `
133
+
134
+ const result = applyTransform(source)
135
+
136
+ expect(result).not.toBeNull()
137
+ expect(result).toContain("<TapestryButton")
138
+ expect(result).toContain('kind="primary"')
139
+ expect(result).not.toContain('kind="secondary"')
140
+ expect(result).toContain("</TapestryButton>")
141
+ })
142
+
143
+ it("should handle renamed imports", () => {
144
+ const source = `
145
+ import { Button as PrimaryButton } from "@planningcenter/tapestry-react"
146
+
147
+ export function TestComponent() {
148
+ return <PrimaryButton size="large" onClick={handler} size="small">Click</PrimaryButton>
149
+ }
150
+ `
151
+
152
+ const result = applyTransform(source)
153
+
154
+ expect(result).not.toBeNull()
155
+ expect(result).toContain('size="large"')
156
+ expect(result).not.toContain('size="small"')
157
+ })
158
+ })
159
+
160
+ describe("mixed elements", () => {
161
+ it("should only process Button elements, not other elements", () => {
162
+ const source = `
163
+ import { Button } from "@planningcenter/tapestry-react"
164
+
165
+ export function TestComponent() {
166
+ return (
167
+ <div>
168
+ <Button kind="primary" kind="secondary">Save</Button>
169
+ <div className="container" className="wrapper">Content</div>
170
+ <span role="button" role="link">Text</span>
171
+ </div>
172
+ )
173
+ }
174
+ `
175
+
176
+ const result = applyTransform(source)
177
+
178
+ expect(result).not.toBeNull()
179
+
180
+ // Button duplicates should be removed
181
+ expect(result).toContain('kind="primary"')
182
+ expect(result).not.toContain('kind="secondary"')
183
+
184
+ // Other elements should remain unchanged (duplicates preserved)
185
+ expect(result).toContain('className="container"')
186
+ expect(result).toContain('className="wrapper"')
187
+ expect(result).toContain('role="button"')
188
+ expect(result).toContain('role="link"')
189
+ })
190
+
191
+ it("should process multiple Button elements independently", () => {
192
+ const source = `
193
+ import { Button } from "@planningcenter/tapestry-react"
194
+
195
+ export function TestComponent() {
196
+ return (
197
+ <form>
198
+ <Button type="submit" kind="primary" type="button">Submit</Button>
199
+ <Button disabled loading disabled>Loading</Button>
200
+ <Button size="small">Small</Button>
201
+ </form>
202
+ )
203
+ }
204
+ `
205
+
206
+ const result = applyTransform(source)
207
+
208
+ expect(result).not.toBeNull()
209
+
210
+ // First button: should keep first 'type'
211
+ expect(result).toContain('type="submit"')
212
+ expect(result).not.toContain('type="button"')
213
+
214
+ expect(result).toContain("<Button disabled loading>")
215
+
216
+ // Third button: should remain unchanged
217
+ expect(result).toContain('size="small"')
218
+ })
219
+ })
220
+
221
+ describe("complex JSX scenarios", () => {
222
+ it("should handle nested Button elements", () => {
223
+ const source = `
224
+ import { Button } from "@planningcenter/tapestry-react"
225
+
226
+ export function TestComponent() {
227
+ return (
228
+ <div>
229
+ <div className="toolbar">
230
+ <Button kind="primary" disabled kind="secondary">
231
+ Save
232
+ </Button>
233
+ </div>
234
+ <Button size="large" onClick={handler} size="small">
235
+ Cancel
236
+ </Button>
237
+ </div>
238
+ )
239
+ }
240
+ `
241
+
242
+ const result = applyTransform(source)
243
+
244
+ expect(result).not.toBeNull()
245
+ expect(result).toContain('kind="primary"')
246
+ expect(result).not.toContain('kind="secondary"')
247
+ expect(result).toContain('size="large"')
248
+ expect(result).not.toContain('size="small"')
249
+ })
250
+
251
+ it("should handle Button elements with children and complex attributes", () => {
252
+ const source = `
253
+ import { Button } from "@planningcenter/tapestry-react"
254
+
255
+ export function TestComponent() {
256
+ return (
257
+ <Button
258
+ kind="primary"
259
+ onClick={() => console.log('first')}
260
+ className="btn-primary"
261
+ kind="secondary"
262
+ onClick={() => console.log('second')}
263
+ data-testid="save-button"
264
+ >
265
+ <span>Save Changes</span>
266
+ </Button>
267
+ )
268
+ }
269
+ `
270
+
271
+ const result = applyTransform(source)
272
+
273
+ expect(result).not.toBeNull()
274
+ expect(result).toContain('kind="primary"')
275
+ expect(result).not.toContain('kind="secondary"')
276
+ expect(result).toContain("onClick={() => console.log('first')}")
277
+ expect(result).not.toContain("onClick={() => console.log('second')}")
278
+ expect(result).toContain('data-testid="save-button"')
279
+ expect(result).toContain("<span>Save Changes</span>")
280
+ })
281
+ })
282
+
283
+ describe("self-closing Button elements", () => {
284
+ it("should handle self-closing Button elements with duplicates", () => {
285
+ const source = `
286
+ import { Button } from "@planningcenter/tapestry-react"
287
+
288
+ export function TestComponent() {
289
+ return <Button kind="primary" disabled kind="secondary" />
290
+ }
291
+ `
292
+
293
+ const result = applyTransform(source)
294
+
295
+ expect(result).not.toBeNull()
296
+ expect(result).toContain('kind="primary"')
297
+ expect(result).not.toContain('kind="secondary"')
298
+ expect(result).toContain("disabled")
299
+ expect(result).toContain("/>")
300
+ })
301
+ })
302
+ })
@@ -0,0 +1,8 @@
1
+ import { removeDuplicateKeys } from "../../shared/actions/removeDuplicateKeys"
2
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
3
+
4
+ export default attributeTransformFactory({
5
+ targetComponent: "Button",
6
+ targetPackage: "@planningcenter/tapestry-react",
7
+ transform: removeDuplicateKeys,
8
+ })
@@ -0,0 +1,17 @@
1
+ import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
2
+ import { getAttribute } from "../../shared/actions/getAttribute"
3
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
4
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
5
+
6
+ const COMMENT = `review custom styles - Button styles may need to be updated for new design system.`
7
+
8
+ export default attributeTransformFactory({
9
+ condition: hasAttribute("style"),
10
+ targetComponent: "Button",
11
+ targetPackage: "@planningcenter/tapestry-react",
12
+ transform: (element, { j }) => {
13
+ const attribute = getAttribute({ element, name: "style" })!
14
+ addCommentToAttribute({ attribute, j, text: COMMENT })
15
+ return true
16
+ },
17
+ })
@@ -0,0 +1,165 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./spinnerToLoadingButton"
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("spinnerToLoadingButton transform", () => {
18
+ describe("Button with spinner prop", () => {
19
+ it("should Button with spinner to use loading prop", () => {
20
+ const source = `
21
+ import { Button } from "@planningcenter/tapestry-react"
22
+
23
+ export function TestComponent() {
24
+ return <Button spinner={isLoading} kind="primary">Save</Button>
25
+ }
26
+ `
27
+
28
+ const result = applyTransform(source)
29
+
30
+ expect(result).toContain("<Button")
31
+ expect(result).toContain("</Button>")
32
+ expect(result).toContain("loading={isLoading}")
33
+ expect(result).not.toContain("spinner={isLoading}")
34
+ expect(result).toContain('kind="primary"')
35
+ expect(result).toContain("Save")
36
+ })
37
+
38
+ it("should handle self-closing Button with spinner", () => {
39
+ const source = `
40
+ import { Button } from "@planningcenter/tapestry-react"
41
+
42
+ export function TestComponent() {
43
+ return <Button spinner={true} aria-label="Loading" />
44
+ }
45
+ `
46
+
47
+ const result = applyTransform(source)
48
+
49
+ expect(result).toContain("<Button")
50
+ expect(result).toContain("loading={true}")
51
+ expect(result).toContain('aria-label="Loading" />')
52
+ expect(result).not.toContain("spinner={true}")
53
+ })
54
+
55
+ it("should handle boolean spinner prop", () => {
56
+ const source = `
57
+ import { Button } from "@planningcenter/tapestry-react"
58
+
59
+ export function TestComponent() {
60
+ return <Button spinner disabled>Loading...</Button>
61
+ }
62
+ `
63
+
64
+ const result = applyTransform(source)
65
+
66
+ expect(result).toContain("<Button")
67
+ expect(result).toContain("loading")
68
+ expect(result).toContain("disabled")
69
+ expect(result).not.toContain("spinner")
70
+ })
71
+ })
72
+
73
+ describe("Button without spinner prop", () => {
74
+ it("should return null when no spinner prop is present", () => {
75
+ const source = `
76
+ import { Button } from "@planningcenter/tapestry-react"
77
+
78
+ export function TestComponent() {
79
+ return <Button kind="primary">No Spinner</Button>
80
+ }
81
+ `
82
+
83
+ const result = applyTransform(source)
84
+
85
+ expect(result).toBeNull()
86
+ })
87
+
88
+ it("should not process non-tapestry Buttons", () => {
89
+ const source = `
90
+ import { Button } from "some-other-library"
91
+
92
+ export function TestComponent() {
93
+ return <Button spinner={true}>Other Button</Button>
94
+ }
95
+ `
96
+
97
+ const result = applyTransform(source)
98
+
99
+ expect(result).toBeNull()
100
+ })
101
+ })
102
+
103
+ describe("edge cases", () => {
104
+ it("should preserve all other Button props", () => {
105
+ const source = `
106
+ import { Button } from "@planningcenter/tapestry-react"
107
+
108
+ export function TestComponent() {
109
+ return (
110
+ <Button
111
+ spinner={isSubmitting}
112
+ kind="primary"
113
+ size="large"
114
+ disabled={isDisabled}
115
+ onClick={handleClick}
116
+ className="submit-btn"
117
+ type="submit"
118
+ >
119
+ Submit Form
120
+ </Button>
121
+ )
122
+ }
123
+ `
124
+
125
+ const result = applyTransform(source)
126
+
127
+ expect(result).toContain('kind="primary"')
128
+ expect(result).toContain('size="large"')
129
+ expect(result).toContain("disabled={isDisabled}")
130
+ expect(result).toContain("onClick={handleClick}")
131
+ expect(result).toContain('className="submit-btn"')
132
+ expect(result).toContain('type="submit"')
133
+ expect(result).toContain("Submit Form")
134
+ expect(result).toContain("<Button")
135
+ expect(result).toContain("loading={isSubmitting}")
136
+ expect(result).not.toContain("spinner={isSubmitting}")
137
+ })
138
+
139
+ it("should handle multiple Buttons with different spinner configurations", () => {
140
+ const source = `
141
+ import { Button } from "@planningcenter/tapestry-react"
142
+
143
+ export function TestComponent() {
144
+ return (
145
+ <div>
146
+ <Button spinner={loading1}>Save</Button>
147
+ <Button kind="secondary">Cancel</Button>
148
+ <Button spinner={loading2} disabled>Delete</Button>
149
+ </div>
150
+ )
151
+ }
152
+ `
153
+
154
+ const result = applyTransform(source)
155
+
156
+ expect(result).not.toBeNull()
157
+
158
+ expect(result).toContain('<Button kind="secondary">Cancel</Button>')
159
+ expect(result).toContain("<Button loading={loading1}")
160
+ expect(result).toContain("<Button loading={loading2}")
161
+ expect(result).not.toContain("spinner={loading1}")
162
+ expect(result).not.toContain("spinner={loading2}")
163
+ })
164
+ })
165
+ })
@@ -0,0 +1,14 @@
1
+ import { transformAttributeName } from "../../shared/actions/transformAttributeName"
2
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
3
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
4
+
5
+ export default attributeTransformFactory({
6
+ condition: hasAttribute("spinner"),
7
+ targetComponent: "Button",
8
+ targetPackage: "@planningcenter/tapestry-react",
9
+ transform: (element) => {
10
+ transformAttributeName("spinner", "loading", { element })
11
+
12
+ return true
13
+ },
14
+ })