@planningcenter/tapestry-migration-cli 3.1.0-rc.6 → 3.1.0-rc.7

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 (52) hide show
  1. package/package.json +3 -3
  2. package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
  3. package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
  4. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
  5. package/src/components/input/index.ts +66 -0
  6. package/src/components/input/transformableInput.ts +8 -0
  7. package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
  8. package/src/components/input/transforms/auditSpreadProps.ts +26 -0
  9. package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
  10. package/src/components/input/transforms/autoWidthTransform.ts +41 -0
  11. package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
  12. package/src/components/input/transforms/convertStyleProps.ts +12 -0
  13. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
  14. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
  15. package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
  16. package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
  17. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +391 -0
  18. package/src/components/input/transforms/mergeFieldIntoInput.ts +213 -0
  19. package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
  20. package/src/components/input/transforms/mergeInputLabel.ts +204 -0
  21. package/src/components/input/transforms/moveInputImport.test.ts +166 -0
  22. package/src/components/input/transforms/moveInputImport.ts +14 -0
  23. package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
  24. package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
  25. package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
  26. package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
  27. package/src/components/input/transforms/removeAsInput.test.ts +139 -0
  28. package/src/components/input/transforms/removeAsInput.ts +20 -0
  29. package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
  30. package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
  31. package/src/components/input/transforms/removeInputBox.test.ts +352 -0
  32. package/src/components/input/transforms/removeInputBox.ts +109 -0
  33. package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
  34. package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
  35. package/src/components/input/transforms/removeTypeText.test.ts +160 -0
  36. package/src/components/input/transforms/removeTypeText.ts +18 -0
  37. package/src/components/input/transforms/sizeMapping.test.ts +198 -0
  38. package/src/components/input/transforms/sizeMapping.ts +17 -0
  39. package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
  40. package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
  41. package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
  42. package/src/components/input/transforms/stateToInvalid.ts +59 -0
  43. package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
  44. package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
  45. package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
  46. package/src/components/input/transforms/unsupportedProps.ts +84 -0
  47. package/src/components/link/transforms/reviewStyles.test.ts +0 -1
  48. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +52 -0
  49. package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
  50. package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
  51. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +54 -16
  52. package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
@@ -0,0 +1,139 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeAsInput"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string | null {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ const api = {
11
+ j,
12
+ jscodeshift: j,
13
+ report: () => {},
14
+ stats: () => {},
15
+ }
16
+ const result = transform(fileInfo, api, {})
17
+ return result as string | null
18
+ }
19
+
20
+ describe("removeAsInput transform", () => {
21
+ describe("basic transformation", () => {
22
+ it("should remove as='input' from Input", () => {
23
+ const input = `
24
+ import { Input } from "@planningcenter/tapestry-react"
25
+
26
+ function Component() {
27
+ return <Input as="input" />
28
+ }
29
+ `
30
+
31
+ const result = applyTransform(input)
32
+
33
+ expect(result).toContain("<Input />")
34
+ expect(result).not.toContain('as="input"')
35
+ })
36
+
37
+ it('should remove as="input" from Input with other props', () => {
38
+ const input = `
39
+ import { Input } from "@planningcenter/tapestry-react"
40
+
41
+ function Component() {
42
+ return <Input as="input" onChange={handleChange} />
43
+ }
44
+ `
45
+
46
+ const result = applyTransform(input)
47
+
48
+ expect(result).toContain("<Input onChange={handleChange} />")
49
+ expect(result).not.toContain('as="input"')
50
+ })
51
+
52
+ it("should preserve Input without as prop", () => {
53
+ const input = `
54
+ import { Input } from "@planningcenter/tapestry-react"
55
+
56
+ function Component() {
57
+ return <Input onChange={handleChange} />
58
+ }
59
+ `
60
+
61
+ const result = applyTransform(input)
62
+
63
+ expect(result).toBeNull()
64
+ })
65
+
66
+ it("should preserve Input with as='textarea'", () => {
67
+ const input = `
68
+ import { Input } from "@planningcenter/tapestry-react"
69
+
70
+ function Component() {
71
+ return <Input as="textarea" />
72
+ }
73
+ `
74
+
75
+ const result = applyTransform(input)
76
+
77
+ expect(result).toBeNull()
78
+ })
79
+ })
80
+
81
+ describe("edge cases", () => {
82
+ it("should not affect other components", () => {
83
+ const input = `
84
+ import { Input } from "@planningcenter/tapestry-react"
85
+
86
+ function Component() {
87
+ return (
88
+ <div>
89
+ <Input as="input" />
90
+ <input as="input" />
91
+ </div>
92
+ )
93
+ }
94
+ `
95
+
96
+ const result = applyTransform(input)
97
+
98
+ expect(result).toContain("<Input />")
99
+ expect(result).toContain('<input as="input" />')
100
+ })
101
+
102
+ it("should handle expression syntax as={'input'}", () => {
103
+ const input = `
104
+ import { Input } from "@planningcenter/tapestry-react"
105
+
106
+ function Component() {
107
+ return <Input as={"input"} />
108
+ }
109
+ `
110
+
111
+ const result = applyTransform(input)
112
+
113
+ if (result) {
114
+ expect(result).toContain("<Input />")
115
+ expect(result).not.toContain('as={"input"}')
116
+ } else {
117
+ expect(input).toContain('as={"input"}')
118
+ }
119
+ })
120
+ })
121
+
122
+ describe("import handling", () => {
123
+ it("should not affect imports", () => {
124
+ const input = `
125
+ import { Input } from "@planningcenter/tapestry-react"
126
+
127
+ function Component() {
128
+ return <Input as="input" />
129
+ }
130
+ `
131
+
132
+ const result = applyTransform(input)
133
+
134
+ expect(result).toContain(
135
+ 'import { Input } from "@planningcenter/tapestry-react"'
136
+ )
137
+ })
138
+ })
139
+ })
@@ -0,0 +1,20 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
4
+ import { andConditions } from "../../shared/conditions/andConditions"
5
+ import { hasAttributeValue } from "../../shared/conditions/hasAttributeValue"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+ import { transformableInput } from "../transformableInput"
8
+
9
+ const transform: Transform = attributeTransformFactory({
10
+ condition: andConditions(
11
+ transformableInput,
12
+ hasAttributeValue("as", "input")
13
+ ),
14
+ targetComponent: "Input",
15
+ targetPackage: "@planningcenter/tapestry-react",
16
+ transform: (element, { j, source }) =>
17
+ removeAttribute("as", { element, j, source }),
18
+ })
19
+
20
+ export default transform
@@ -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("Input elements with duplicate attributes", () => {
19
+ it("should remove duplicate attributes from Input elements", () => {
20
+ const source = `
21
+ import { Input } from "@planningcenter/tapestry-react"
22
+
23
+ export function TestComponent() {
24
+ return <Input kind="primary" disabled kind="secondary">Save</Input>
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 Input elements with duplicates", () => {
38
+ const source = `
39
+ import { Input } from "@planningcenter/tapestry-react"
40
+
41
+ export function TestComponent() {
42
+ return (
43
+ <div>
44
+ <Input kind="primary" size="large" kind="secondary">Save</Input>
45
+ <Input disabled loading disabled>Cancel</Input>
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("<Input disabled loading>")
59
+ })
60
+
61
+ it("should preserve first occurrence of duplicate attributes", () => {
62
+ const source = `
63
+ import { Input } from "@planningcenter/tapestry-react"
64
+
65
+ export function TestComponent() {
66
+ return <Input onClick={handleFirst} aria-label="First" onClick={handleSecond} aria-label="Second">Click</Input>
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("Input elements without duplicate attributes", () => {
81
+ it("should return null when no duplicates exist", () => {
82
+ const source = `
83
+ import { Input } from "@planningcenter/tapestry-react"
84
+
85
+ export function TestComponent() {
86
+ return <Input kind="primary" size="large" disabled>Save</Input>
87
+ }
88
+ `
89
+
90
+ const result = applyTransform(source)
91
+
92
+ expect(result).toBeNull() // No changes needed
93
+ })
94
+
95
+ it("should return null for Input with no attributes", () => {
96
+ const source = `
97
+ import { Input } from "@planningcenter/tapestry-react"
98
+
99
+ export function TestComponent() {
100
+ return <Input>Save</Input>
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 Input from tapestry-react", () => {
112
+ const source = `
113
+ import { Input } from "some-other-library"
114
+
115
+ export function TestComponent() {
116
+ return <Input kind="primary" kind="secondary">Save</Input>
117
+ }
118
+ `
119
+
120
+ const result = applyTransform(source)
121
+
122
+ expect(result).toBeNull() // Should not process non-tapestry Inputs
123
+ })
124
+
125
+ it("should handle aliased Input imports", () => {
126
+ const source = `
127
+ import { Input as TapestryInput } from "@planningcenter/tapestry-react"
128
+
129
+ export function TestComponent() {
130
+ return <TapestryInput kind="primary" disabled kind="secondary">Save</TapestryInput>
131
+ }
132
+ `
133
+
134
+ const result = applyTransform(source)
135
+
136
+ expect(result).not.toBeNull()
137
+ expect(result).toContain("<TapestryInput")
138
+ expect(result).toContain('kind="primary"')
139
+ expect(result).not.toContain('kind="secondary"')
140
+ expect(result).toContain("</TapestryInput>")
141
+ })
142
+
143
+ it("should handle renamed imports", () => {
144
+ const source = `
145
+ import { Input as PrimaryInput } from "@planningcenter/tapestry-react"
146
+
147
+ export function TestComponent() {
148
+ return <PrimaryInput size="large" onClick={handler} size="small">Click</PrimaryInput>
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 Input elements, not other elements", () => {
162
+ const source = `
163
+ import { Input } from "@planningcenter/tapestry-react"
164
+
165
+ export function TestComponent() {
166
+ return (
167
+ <div>
168
+ <Input kind="primary" kind="secondary">Save</Input>
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
+ // Input 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 Input elements independently", () => {
192
+ const source = `
193
+ import { Input } from "@planningcenter/tapestry-react"
194
+
195
+ export function TestComponent() {
196
+ return (
197
+ <form>
198
+ <Input type="submit" kind="primary" type="button">Submit</Input>
199
+ <Input disabled loading disabled>Loading</Input>
200
+ <Input size="small">Small</Input>
201
+ </form>
202
+ )
203
+ }
204
+ `
205
+
206
+ const result = applyTransform(source)
207
+
208
+ expect(result).not.toBeNull()
209
+
210
+ // First input: should keep first 'type'
211
+ expect(result).toContain('type="submit"')
212
+ expect(result).not.toContain('type="button"')
213
+
214
+ expect(result).toContain("<Input disabled loading>")
215
+
216
+ // Third input: should remain unchanged
217
+ expect(result).toContain('size="small"')
218
+ })
219
+ })
220
+
221
+ describe("complex JSX scenarios", () => {
222
+ it("should handle nested Input elements", () => {
223
+ const source = `
224
+ import { Input } from "@planningcenter/tapestry-react"
225
+
226
+ export function TestComponent() {
227
+ return (
228
+ <div>
229
+ <div className="toolbar">
230
+ <Input kind="primary" disabled kind="secondary">
231
+ Save
232
+ </Input>
233
+ </div>
234
+ <Input size="large" onClick={handler} size="small">
235
+ Cancel
236
+ </Input>
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 Input elements with children and complex attributes", () => {
252
+ const source = `
253
+ import { Input } from "@planningcenter/tapestry-react"
254
+
255
+ export function TestComponent() {
256
+ return (
257
+ <Input
258
+ kind="primary"
259
+ onClick={() => console.log('first')}
260
+ className="btn-primary"
261
+ kind="secondary"
262
+ onClick={() => console.log('second')}
263
+ data-testid="save-input"
264
+ >
265
+ <span>Save Changes</span>
266
+ </Input>
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-input"')
279
+ expect(result).toContain("<span>Save Changes</span>")
280
+ })
281
+ })
282
+
283
+ describe("self-closing Input elements", () => {
284
+ it("should handle self-closing Input elements with duplicates", () => {
285
+ const source = `
286
+ import { Input } from "@planningcenter/tapestry-react"
287
+
288
+ export function TestComponent() {
289
+ return <Input 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,10 @@
1
+ import { removeDuplicateKeys } from "../../shared/actions/removeDuplicateKeys"
2
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
3
+ import { transformableInput } from "../transformableInput"
4
+
5
+ export default attributeTransformFactory({
6
+ condition: transformableInput,
7
+ targetComponent: "Input",
8
+ targetPackage: "@planningcenter/tapestry-react",
9
+ transform: removeDuplicateKeys,
10
+ })