@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,160 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeTypeText"
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("removeTypeText transform", () => {
21
+ describe("basic transformation", () => {
22
+ it("should preserve Input without type", () => {
23
+ const input = `
24
+ import { Input } from "@planningcenter/tapestry-react"
25
+
26
+ function Component() {
27
+ return <Input onChange={handleChange} />
28
+ }
29
+ `
30
+
31
+ const result = applyTransform(input)
32
+
33
+ expect(result).toBeNull()
34
+ })
35
+
36
+ it("should preserve Input with type='email'", () => {
37
+ const input = `
38
+ import { Input } from "@planningcenter/tapestry-react"
39
+
40
+ function Component() {
41
+ return <Input type="email" />
42
+ }
43
+ `
44
+
45
+ const result = applyTransform(input)
46
+
47
+ expect(result).toBeNull()
48
+ })
49
+
50
+ it("should preserve Input with type='number'", () => {
51
+ const input = `
52
+ import { Input } from "@planningcenter/tapestry-react"
53
+
54
+ function Component() {
55
+ return <Input type="number" />
56
+ }
57
+ `
58
+
59
+ const result = applyTransform(input)
60
+
61
+ expect(result).toBeNull()
62
+ })
63
+
64
+ it("should remove type='text' from Input", () => {
65
+ const input = `
66
+ import { Input } from "@planningcenter/tapestry-react"
67
+
68
+ function Component() {
69
+ return <Input type="text" />
70
+ }
71
+ `
72
+
73
+ const result = applyTransform(input)
74
+
75
+ expect(result).toContain("<Input />")
76
+ expect(result).not.toContain('type="text"')
77
+ })
78
+
79
+ it("should remove type='text' from Input with other props", () => {
80
+ const input = `
81
+ import { Input } from "@planningcenter/tapestry-react"
82
+
83
+ function Component() {
84
+ return <Input type="text" value={searchTerm} placeholder="Search" onChange={handleChange} />
85
+ }
86
+ `
87
+
88
+ const result = applyTransform(input)
89
+
90
+ expect(result).not.toContain('type="text"')
91
+ expect(result).toContain("value={searchTerm}")
92
+ expect(result).toContain('placeholder="Search"')
93
+ expect(result).toContain("onChange={handleChange}")
94
+ })
95
+ })
96
+
97
+ describe("multiple inputs", () => {
98
+ it("should handle mixed Input usage", () => {
99
+ const input = `
100
+ import { Input } from "@planningcenter/tapestry-react"
101
+
102
+ function Component() {
103
+ return (
104
+ <div>
105
+ <Input type="text" />
106
+ <Input type="email" />
107
+ <Input type="text" onChange={handleChange} />
108
+ </div>
109
+ )
110
+ }
111
+ `
112
+
113
+ const result = applyTransform(input)
114
+
115
+ expect(result).toContain('<Input type="email" />')
116
+ expect(result).toContain("<Input onChange={handleChange} />")
117
+ expect(result).not.toContain('type="text"')
118
+ })
119
+ })
120
+
121
+ describe("edge cases", () => {
122
+ it("should not affect HTML input elements", () => {
123
+ const input = `
124
+ import { Input } from "@planningcenter/tapestry-react"
125
+
126
+ function Component() {
127
+ return (
128
+ <div>
129
+ <Input type="text" />
130
+ <input type="text" />
131
+ </div>
132
+ )
133
+ }
134
+ `
135
+
136
+ const result = applyTransform(input)
137
+
138
+ expect(result).toContain("<Input />")
139
+ expect(result).toContain('<input type="text" />')
140
+ })
141
+ })
142
+
143
+ describe("import handling", () => {
144
+ it("should not affect imports", () => {
145
+ const input = `
146
+ import { Input } from "@planningcenter/tapestry-react"
147
+
148
+ function Component() {
149
+ return <Input type="text" />
150
+ }
151
+ `
152
+
153
+ const result = applyTransform(input)
154
+
155
+ expect(result).toContain(
156
+ 'import { Input } from "@planningcenter/tapestry-react"'
157
+ )
158
+ })
159
+ })
160
+ })
@@ -0,0 +1,18 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
4
+ import { hasAttributeValue } from "../../shared/conditions/hasAttributeValue"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+ import { transformableInput } from "../transformableInput"
7
+
8
+ const transform: Transform = attributeTransformFactory({
9
+ condition: (element, context) =>
10
+ transformableInput(element, context) &&
11
+ hasAttributeValue("type", "text")(element, context),
12
+ targetComponent: "Input",
13
+ targetPackage: "@planningcenter/tapestry-react",
14
+ transform: (element, { j, source }) =>
15
+ removeAttribute("type", { element, j, source }),
16
+ })
17
+
18
+ export default transform
@@ -0,0 +1,198 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./sizeMapping"
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("sizeMapping transform", () => {
19
+ describe("size value transformations", () => {
20
+ it("should transform xs to md", () => {
21
+ const input = `
22
+ import { Input } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Input size="xs" label="Name" />
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).toContain('size="md"')
31
+ expect(result).not.toContain('size="xs"')
32
+ expect(result).toContain('Size "xs" was mapped to "md"')
33
+ })
34
+
35
+ it("should transform sm to md", () => {
36
+ const input = `
37
+ import { Input } from "@planningcenter/tapestry-react"
38
+
39
+ function Test() {
40
+ return <Input size="sm" label="Name" />
41
+ }
42
+ `.trim()
43
+
44
+ const result = applyTransform(input)
45
+ expect(result).toContain('size="md"')
46
+ expect(result).not.toContain('size="sm"')
47
+ expect(result).toContain('Size "sm" was mapped to "md"')
48
+ })
49
+
50
+ it("should transform xl to lg", () => {
51
+ const input = `
52
+ import { Input } from "@planningcenter/tapestry-react"
53
+
54
+ function Test() {
55
+ return <Input size="xl" label="Name" />
56
+ }
57
+ `.trim()
58
+
59
+ const result = applyTransform(input)
60
+ expect(result).toContain('size="lg"')
61
+ expect(result).not.toContain('size="xl"')
62
+ expect(result).toContain('Size "xl" was mapped to "lg"')
63
+ })
64
+
65
+ it('should transform size={"sm"} expression container', () => {
66
+ const input = `
67
+ import { Input } from "@planningcenter/tapestry-react"
68
+
69
+ function Test() {
70
+ return <Input size={"sm"} label="Name" />
71
+ }
72
+ `.trim()
73
+
74
+ const result = applyTransform(input)
75
+ expect(result).toContain('size="md"')
76
+ expect(result).toContain('Size "sm" was mapped to "md"')
77
+ })
78
+
79
+ it("should not transform already valid sizes", () => {
80
+ const input = `
81
+ import { Input } from "@planningcenter/tapestry-react"
82
+
83
+ function Test() {
84
+ return (
85
+ <div>
86
+ <Input size="md" label="Medium" />
87
+ <Input size="lg" label="Large" />
88
+ </div>
89
+ )
90
+ }
91
+ `.trim()
92
+
93
+ const result = applyTransform(input)
94
+ expect(result).toContain('size="md"')
95
+ expect(result).toContain('size="lg"')
96
+ expect(result).not.toContain("TODO: tapestry-migration")
97
+ })
98
+ })
99
+
100
+ describe("edge cases", () => {
101
+ it("should not affect Input without size prop", () => {
102
+ const input = `
103
+ import { Input } from "@planningcenter/tapestry-react"
104
+
105
+ function Test() {
106
+ return <Input label="Name" />
107
+ }
108
+ `.trim()
109
+
110
+ const result = applyTransform(input)
111
+ expect(result).toBe(input)
112
+ })
113
+
114
+ it("should not transform expression values (variables)", () => {
115
+ const input = `
116
+ import { Input } from "@planningcenter/tapestry-react"
117
+
118
+ function Test() {
119
+ const size = "sm"
120
+ return <Input size={size} label="Name" />
121
+ }
122
+ `.trim()
123
+
124
+ const result = applyTransform(input)
125
+ expect(result).toContain("size={size}")
126
+ expect(result).not.toContain("Size")
127
+ })
128
+
129
+ it("should not affect other components", () => {
130
+ const input = `
131
+ import { Input, Button } from "@planningcenter/tapestry-react"
132
+
133
+ function Test() {
134
+ return (
135
+ <div>
136
+ <Button size="sm">Small button</Button>
137
+ <Input size="md" label="Name" />
138
+ </div>
139
+ )
140
+ }
141
+ `.trim()
142
+
143
+ const result = applyTransform(input)
144
+ expect(result).toContain('size="sm"')
145
+ expect(result).toContain('size="md"')
146
+ })
147
+
148
+ it("should handle multiple inputs with different sizes", () => {
149
+ const input = `
150
+ import { Input } from "@planningcenter/tapestry-react"
151
+
152
+ function Test() {
153
+ return (
154
+ <div>
155
+ <Input size="xs" label="Extra small" />
156
+ <Input size="sm" label="Small" />
157
+ <Input size="md" label="Medium" />
158
+ <Input size="xl" label="Extra large" />
159
+ </div>
160
+ )
161
+ }
162
+ `.trim()
163
+
164
+ const result = applyTransform(input)
165
+ expect(result).toContain('size="md"')
166
+ expect(result).not.toContain('size="xs"')
167
+ expect(result).not.toContain('size="sm"')
168
+ expect(result).not.toContain('size="xl"')
169
+ const sizeMappingMatches = result.match(/Size ".*" was mapped to/g)
170
+ expect(sizeMappingMatches).toHaveLength(3)
171
+ })
172
+
173
+ it("should preserve other props", () => {
174
+ const input = `
175
+ import { Input } from "@planningcenter/tapestry-react"
176
+
177
+ function Test() {
178
+ return (
179
+ <Input
180
+ size="sm"
181
+ label="Name"
182
+ placeholder="Enter name"
183
+ disabled
184
+ onChange={() => {}}
185
+ />
186
+ )
187
+ }
188
+ `.trim()
189
+
190
+ const result = applyTransform(input)
191
+ expect(result).toContain('size="md"')
192
+ expect(result).toContain('label="Name"')
193
+ expect(result).toContain('placeholder="Enter name"')
194
+ expect(result).toContain("disabled")
195
+ expect(result).toContain("onChange={() => {}}")
196
+ })
197
+ })
198
+ })
@@ -0,0 +1,17 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { sizeMappingFactory } from "../../shared/transformFactories/sizeMappingFactory"
4
+ import { transformableInput } from "../transformableInput"
5
+
6
+ const transform: Transform = sizeMappingFactory({
7
+ condition: transformableInput,
8
+ sizeMapping: {
9
+ sm: "md",
10
+ xl: "lg",
11
+ xs: "md",
12
+ },
13
+ targetComponent: "Input",
14
+ targetPackage: "@planningcenter/tapestry-react",
15
+ })
16
+
17
+ export default transform
@@ -0,0 +1,236 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import pipeline from "../index"
5
+ import transform from "./skipRenderSideProps"
6
+
7
+ const j = jscodeshift.withParser("tsx")
8
+
9
+ const SKIP_COMMENT =
10
+ "TODO: tapestry-migration (migration): Automatic migration skipped: Input uses renderLeft/renderRight props which have no equivalent in the new API. Migrate this Input manually."
11
+
12
+ function applyTransform(source: string): string | null {
13
+ const fileInfo = { path: "test.tsx", source }
14
+ return transform(
15
+ fileInfo,
16
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
17
+ {}
18
+ ) as string | null
19
+ }
20
+
21
+ describe("skipRenderSideProps transform", () => {
22
+ describe("inputs that should be skipped", () => {
23
+ it("should add skip comment to Input with renderLeft", () => {
24
+ const input = `
25
+ import { Input } from "@planningcenter/tapestry-react"
26
+
27
+ function Test() {
28
+ return <Input renderLeft={<Icon />} label="Name" />
29
+ }
30
+ `.trim()
31
+
32
+ const result = applyTransform(input)
33
+ expect(result).not.toBeNull()
34
+ expect(result).toContain(SKIP_COMMENT)
35
+ })
36
+
37
+ it("should add skip comment to Input with renderRight", () => {
38
+ const input = `
39
+ import { Input } from "@planningcenter/tapestry-react"
40
+
41
+ function Test() {
42
+ return <Input renderRight={<Button />} label="Name" />
43
+ }
44
+ `.trim()
45
+
46
+ const result = applyTransform(input)
47
+ expect(result).not.toBeNull()
48
+ expect(result).toContain(SKIP_COMMENT)
49
+ })
50
+
51
+ it("should add skip comment to Input with both renderLeft and renderRight", () => {
52
+ const input = `
53
+ import { Input } from "@planningcenter/tapestry-react"
54
+
55
+ function Test() {
56
+ return <Input renderLeft={<Icon />} renderRight={<Button />} label="Name" />
57
+ }
58
+ `.trim()
59
+
60
+ const result = applyTransform(input)
61
+ expect(result).not.toBeNull()
62
+ expect(result).toContain(SKIP_COMMENT)
63
+ })
64
+
65
+ it("should add skip comment for each Input that has renderLeft/renderRight", () => {
66
+ const input = `
67
+ import { Input } from "@planningcenter/tapestry-react"
68
+
69
+ function Test() {
70
+ return (
71
+ <div>
72
+ <Input renderLeft={<Icon />} label="First" />
73
+ <Input renderRight={<Button />} label="Second" />
74
+ </div>
75
+ )
76
+ }
77
+ `.trim()
78
+
79
+ const result = applyTransform(input)
80
+ const matches = result?.match(/Automatic migration skipped/g)
81
+ expect(matches).toHaveLength(2)
82
+ })
83
+
84
+ it("should preserve all props on skipped Input", () => {
85
+ const input = `
86
+ import { Input } from "@planningcenter/tapestry-react"
87
+
88
+ function Test() {
89
+ return (
90
+ <Input
91
+ renderLeft={<Icon />}
92
+ label="Name"
93
+ size="lg"
94
+ state="error"
95
+ onChange={handleChange}
96
+ />
97
+ )
98
+ }
99
+ `.trim()
100
+
101
+ const result = applyTransform(input)
102
+ expect(result).toContain(SKIP_COMMENT)
103
+ expect(result).toContain('label="Name"')
104
+ expect(result).toContain('size="lg"')
105
+ expect(result).toContain('state="error"')
106
+ expect(result).toContain("onChange={handleChange}")
107
+ })
108
+ })
109
+
110
+ describe("inputs that should NOT be skipped", () => {
111
+ it("should return null for Input without renderLeft or renderRight", () => {
112
+ const input = `
113
+ import { Input } from "@planningcenter/tapestry-react"
114
+
115
+ function Test() {
116
+ return <Input label="Name" />
117
+ }
118
+ `.trim()
119
+
120
+ const result = applyTransform(input)
121
+ expect(result).toBeNull()
122
+ })
123
+
124
+ it("should return null for Input with other props but no render side props", () => {
125
+ const input = `
126
+ import { Input } from "@planningcenter/tapestry-react"
127
+
128
+ function Test() {
129
+ return <Input label="Name" size="lg" state="error" onChange={handleChange} />
130
+ }
131
+ `.trim()
132
+
133
+ const result = applyTransform(input)
134
+ expect(result).toBeNull()
135
+ })
136
+ })
137
+
138
+ describe("mixed files", () => {
139
+ it("should only flag Inputs with renderLeft/renderRight in a mixed file", () => {
140
+ const input = `
141
+ import { Input } from "@planningcenter/tapestry-react"
142
+
143
+ function Test() {
144
+ return (
145
+ <div>
146
+ <Input renderLeft={<Icon />} label="With render" />
147
+ <Input label="Without render" />
148
+ </div>
149
+ )
150
+ }
151
+ `.trim()
152
+
153
+ const result = applyTransform(input)
154
+ expect(result).toContain(SKIP_COMMENT)
155
+ const matches = result?.match(/Automatic migration skipped/g)
156
+ expect(matches).toHaveLength(1)
157
+ })
158
+
159
+ it("should not affect other components with renderLeft prop", () => {
160
+ const input = `
161
+ import { Input, Button } from "@planningcenter/tapestry-react"
162
+
163
+ function Test() {
164
+ return (
165
+ <div>
166
+ <Button renderLeft={<Icon />}>Click</Button>
167
+ <Input label="Name" />
168
+ </div>
169
+ )
170
+ }
171
+ `.trim()
172
+
173
+ const result = applyTransform(input)
174
+ expect(result).toBeNull()
175
+ })
176
+ })
177
+
178
+ describe("other transforms are bypassed for skipped inputs", () => {
179
+ it("should not migrate the import when all Inputs have renderLeft/renderRight", () => {
180
+ const input = `
181
+ import { Input } from "@planningcenter/tapestry-react"
182
+
183
+ function Test() {
184
+ return <Input renderLeft={<Icon />} label="Name" size="lg" state="error" />
185
+ }
186
+ `.trim()
187
+
188
+ // Run the full pipeline via the index transform
189
+ const fileInfo = { path: "test.tsx", source: input }
190
+ const result = pipeline(
191
+ fileInfo,
192
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
193
+ {}
194
+ ) as string | null
195
+
196
+ // Import stays on tapestry-react since Input was skipped
197
+ expect(result).toContain("@planningcenter/tapestry-react")
198
+ expect(result).not.toContain('@planningcenter/tapestry"')
199
+ // Size and state are NOT migrated
200
+ expect(result).toContain('size="lg"')
201
+ expect(result).toContain('state="error"')
202
+ // Skip comment is present
203
+ expect(result).toContain("Automatic migration skipped")
204
+ })
205
+
206
+ it("should migrate non-renderSide Inputs while skipping renderSide ones", () => {
207
+ const input = `
208
+ import { Input } from "@planningcenter/tapestry-react"
209
+
210
+ function Test() {
211
+ return (
212
+ <div>
213
+ <Input renderLeft={<Icon />} label="Skip me" size="sm" />
214
+ <Input label="Migrate me" size="sm" />
215
+ </div>
216
+ )
217
+ }
218
+ `.trim()
219
+
220
+ const fileInfo = { path: "test.tsx", source: input }
221
+ const result = pipeline(
222
+ fileInfo,
223
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
224
+ {}
225
+ ) as string | null
226
+
227
+ expect(result).not.toBeNull()
228
+ // The skipped Input keeps size="sm"
229
+ expect(result).toContain('size="sm"')
230
+ // The migrated Input has size="md"
231
+ expect(result).toContain('size="md"')
232
+ // Import moves to tapestry (because at least one Input was migrated)
233
+ expect(result).toContain('@planningcenter/tapestry"')
234
+ })
235
+ })
236
+ })
@@ -0,0 +1,27 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { addComment } from "../../shared/actions/addComment"
4
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
5
+ import { orConditions } from "../../shared/conditions/orConditions"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+
8
+ const transform: Transform = attributeTransformFactory({
9
+ condition: orConditions(
10
+ hasAttribute("renderLeft"),
11
+ hasAttribute("renderRight")
12
+ ),
13
+ targetComponent: "Input",
14
+ targetPackage: "@planningcenter/tapestry-react",
15
+ transform: (element, { j, source }) => {
16
+ addComment({
17
+ element,
18
+ j,
19
+ scope: "migration",
20
+ source,
21
+ text: "Automatic migration skipped: Input uses renderLeft/renderRight props which have no equivalent in the new API. Migrate this Input manually.",
22
+ })
23
+ return true
24
+ },
25
+ })
26
+
27
+ export default transform