@planningcenter/tapestry-migration-cli 3.1.0-rc.9 → 3.1.0

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 (48) hide show
  1. package/dist/tapestry-react-shim.cjs +7 -1
  2. package/package.json +3 -3
  3. package/src/components/input/transformableInput.ts +47 -6
  4. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +78 -0
  5. package/src/components/input/transforms/mergeFieldIntoInput.ts +6 -212
  6. package/src/components/input/transforms/removeDuplicateKeys.test.ts +3 -3
  7. package/src/components/input/transforms/removeTypeInput.ts +3 -3
  8. package/src/components/input/transforms/removeTypeText.ts +2 -3
  9. package/src/components/input/transforms/unsupportedProps.test.ts +20 -20
  10. package/src/components/select/index.ts +58 -0
  11. package/src/components/select/transformableSelect.ts +7 -0
  12. package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
  13. package/src/components/select/transforms/auditSpreadProps.ts +26 -0
  14. package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
  15. package/src/components/select/transforms/childrenToOptions.ts +295 -0
  16. package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
  17. package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
  18. package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
  19. package/src/components/select/transforms/convertStyleProps.ts +12 -0
  20. package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
  21. package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
  22. package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
  23. package/src/components/select/transforms/innerRefToRef.ts +18 -0
  24. package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
  25. package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
  26. package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
  27. package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
  28. package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
  29. package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
  30. package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
  31. package/src/components/select/transforms/moveSelectImport.ts +14 -0
  32. package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
  33. package/src/components/select/transforms/removeDefaultProps.ts +112 -0
  34. package/src/components/select/transforms/sizeMapping.test.ts +188 -0
  35. package/src/components/select/transforms/sizeMapping.ts +17 -0
  36. package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
  37. package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
  38. package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
  39. package/src/components/select/transforms/stateToInvalid.ts +59 -0
  40. package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
  41. package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
  42. package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
  43. package/src/components/select/transforms/unsupportedProps.ts +44 -0
  44. package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
  45. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +19 -2
  46. package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
  47. package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +4 -226
  48. package/src/index.ts +2 -1
@@ -0,0 +1,148 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./moveSelectImport"
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("moveSelectImport transform", () => {
19
+ it("should change import from tapestry-react to tapestry", () => {
20
+ const input = `
21
+ import { Select } from "@planningcenter/tapestry-react"
22
+
23
+ function Test() {
24
+ return <Select label="Test" placeholder="Pick" options={[]} />
25
+ }
26
+ `.trim()
27
+
28
+ const result = applyTransform(input)
29
+ expect(result).toContain(
30
+ 'import { Select } from "@planningcenter/tapestry"'
31
+ )
32
+ expect(result).not.toContain("@planningcenter/tapestry-react")
33
+ })
34
+
35
+ it("should handle multiple imports", () => {
36
+ const input = `
37
+ import { Button, Select } from "@planningcenter/tapestry-react"
38
+
39
+ function Test() {
40
+ return (
41
+ <div>
42
+ <Button>Click</Button>
43
+ <Select label="Test" placeholder="Pick" options={[]} />
44
+ </div>
45
+ )
46
+ }
47
+ `.trim()
48
+
49
+ const result = applyTransform(input)
50
+ expect(result).toContain(
51
+ 'import { Button } from "@planningcenter/tapestry-react"'
52
+ )
53
+ expect(result).toContain(
54
+ 'import { Select } from "@planningcenter/tapestry"'
55
+ )
56
+ })
57
+
58
+ it("should handle separate import declarations", () => {
59
+ const input = `
60
+ import { Button } from "@planningcenter/tapestry-react"
61
+ import { Select } from "@planningcenter/tapestry-react"
62
+
63
+ function Test() {
64
+ return (
65
+ <div>
66
+ <Button>Click</Button>
67
+ <Select label="Test" placeholder="Pick" options={[]} />
68
+ </div>
69
+ )
70
+ }
71
+ `.trim()
72
+
73
+ const result = applyTransform(input)
74
+ expect(result).toContain(
75
+ 'import { Button } from "@planningcenter/tapestry-react"'
76
+ )
77
+ expect(result).toContain(
78
+ 'import { Select } from "@planningcenter/tapestry"'
79
+ )
80
+ })
81
+
82
+ it("should not affect other components", () => {
83
+ const input = `
84
+ import { Button } from "@planningcenter/tapestry-react"
85
+
86
+ function Test() {
87
+ return <Button>Click me</Button>
88
+ }
89
+ `.trim()
90
+
91
+ const result = applyTransform(input)
92
+ expect(result).toContain(
93
+ 'import { Button } from "@planningcenter/tapestry-react"'
94
+ )
95
+ })
96
+
97
+ it("should handle already migrated imports", () => {
98
+ const input = `
99
+ import { Select } from "@planningcenter/tapestry"
100
+
101
+ function Test() {
102
+ return <Select label="Test" placeholder="Pick" options={[]} />
103
+ }
104
+ `.trim()
105
+
106
+ const result = applyTransform(input)
107
+ expect(result).toContain(
108
+ 'import { Select } from "@planningcenter/tapestry"'
109
+ )
110
+ })
111
+
112
+ it("should handle no imports", () => {
113
+ const input = `
114
+ function Test() {
115
+ return <div>No imports</div>
116
+ }
117
+ `.trim()
118
+
119
+ const result = applyTransform(input)
120
+ expect(result).toBe(input)
121
+ })
122
+
123
+ it("should preserve other attributes", () => {
124
+ const input = `
125
+ import { Select } from "@planningcenter/tapestry-react"
126
+
127
+ function Test() {
128
+ return (
129
+ <Select
130
+ label="Test"
131
+ placeholder="Pick"
132
+ options={[]}
133
+ disabled
134
+ onChange={() => {}}
135
+ />
136
+ )
137
+ }
138
+ `.trim()
139
+
140
+ const result = applyTransform(input)
141
+ expect(result).toContain(
142
+ 'import { Select } from "@planningcenter/tapestry"'
143
+ )
144
+ expect(result).toContain('label="Test"')
145
+ expect(result).toContain("disabled")
146
+ expect(result).toContain("onChange={() => {}}")
147
+ })
148
+ })
@@ -0,0 +1,14 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
4
+ import { transformableSelect } from "../transformableSelect"
5
+
6
+ const transform: Transform = componentTransformFactory({
7
+ condition: transformableSelect,
8
+ fromComponent: "Select",
9
+ fromPackage: "@planningcenter/tapestry-react",
10
+ toComponent: "Select",
11
+ toPackage: "@planningcenter/tapestry",
12
+ })
13
+
14
+ export default transform
@@ -0,0 +1,249 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeDefaultProps"
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("removeDefaultProps transform", () => {
19
+ describe("matchWidths removal", () => {
20
+ it("should remove matchWidths boolean shorthand", () => {
21
+ const input = `
22
+ import { Select } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Select matchWidths emptyValue="Pick" />
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).not.toContain("matchWidths")
31
+ })
32
+
33
+ it("should remove matchWidths={true}", () => {
34
+ const input = `
35
+ import { Select } from "@planningcenter/tapestry-react"
36
+
37
+ function Test() {
38
+ return <Select matchWidths={true} emptyValue="Pick" />
39
+ }
40
+ `.trim()
41
+
42
+ const result = applyTransform(input)
43
+ expect(result).not.toContain("matchWidths")
44
+ })
45
+
46
+ it('should remove matchWidths="minimum"', () => {
47
+ const input = `
48
+ import { Select } from "@planningcenter/tapestry-react"
49
+
50
+ function Test() {
51
+ return <Select matchWidths="minimum" emptyValue="Pick" />
52
+ }
53
+ `.trim()
54
+
55
+ const result = applyTransform(input)
56
+ expect(result).not.toContain("matchWidths")
57
+ })
58
+
59
+ it('should remove matchWidths={"minimum"}', () => {
60
+ const input = `
61
+ import { Select } from "@planningcenter/tapestry-react"
62
+
63
+ function Test() {
64
+ return <Select matchWidths={"minimum"} emptyValue="Pick" />
65
+ }
66
+ `.trim()
67
+
68
+ const result = applyTransform(input)
69
+ expect(result).not.toContain("matchWidths")
70
+ })
71
+
72
+ it("should NOT remove matchWidths={false}", () => {
73
+ const input = `
74
+ import { Select } from "@planningcenter/tapestry-react"
75
+
76
+ function Test() {
77
+ return <Select matchWidths={false} emptyValue="Pick" />
78
+ }
79
+ `.trim()
80
+
81
+ const result = applyTransform(input)
82
+ expect(result).toContain("matchWidths={false}")
83
+ })
84
+
85
+ it("should NOT remove matchWidths with a variable value", () => {
86
+ const input = `
87
+ import { Select } from "@planningcenter/tapestry-react"
88
+
89
+ function Test() {
90
+ return <Select matchWidths={shouldMatch} emptyValue="Pick" />
91
+ }
92
+ `.trim()
93
+
94
+ const result = applyTransform(input)
95
+ expect(result).toContain("matchWidths={shouldMatch}")
96
+ })
97
+ })
98
+
99
+ describe("defaultOpen={false} removal", () => {
100
+ it("should remove defaultOpen={false}", () => {
101
+ const input = `
102
+ import { Select } from "@planningcenter/tapestry-react"
103
+
104
+ function Test() {
105
+ return <Select defaultOpen={false} emptyValue="Pick" />
106
+ }
107
+ `.trim()
108
+
109
+ const result = applyTransform(input)
110
+ expect(result).not.toContain("defaultOpen")
111
+ })
112
+
113
+ it("should NOT remove defaultOpen={true}", () => {
114
+ const input = `
115
+ import { Select } from "@planningcenter/tapestry-react"
116
+
117
+ function Test() {
118
+ return <Select defaultOpen={true} emptyValue="Pick" />
119
+ }
120
+ `.trim()
121
+
122
+ const result = applyTransform(input)
123
+ expect(result).toContain("defaultOpen={true}")
124
+ })
125
+
126
+ it("should NOT remove defaultOpen boolean shorthand (means true)", () => {
127
+ const input = `
128
+ import { Select } from "@planningcenter/tapestry-react"
129
+
130
+ function Test() {
131
+ return <Select defaultOpen emptyValue="Pick" />
132
+ }
133
+ `.trim()
134
+
135
+ const result = applyTransform(input)
136
+ expect(result).toContain("defaultOpen")
137
+ })
138
+ })
139
+
140
+ describe("lockScrollWhileOpen removal", () => {
141
+ it("should remove lockScrollWhileOpen boolean shorthand", () => {
142
+ const input = `
143
+ import { Select } from "@planningcenter/tapestry-react"
144
+
145
+ function Test() {
146
+ return <Select lockScrollWhileOpen emptyValue="Pick" />
147
+ }
148
+ `.trim()
149
+
150
+ const result = applyTransform(input)
151
+ expect(result).not.toContain("lockScrollWhileOpen")
152
+ })
153
+
154
+ it("should remove lockScrollWhileOpen={true}", () => {
155
+ const input = `
156
+ import { Select } from "@planningcenter/tapestry-react"
157
+
158
+ function Test() {
159
+ return <Select lockScrollWhileOpen={true} emptyValue="Pick" />
160
+ }
161
+ `.trim()
162
+
163
+ const result = applyTransform(input)
164
+ expect(result).not.toContain("lockScrollWhileOpen")
165
+ })
166
+
167
+ it("should NOT remove lockScrollWhileOpen={false}", () => {
168
+ const input = `
169
+ import { Select } from "@planningcenter/tapestry-react"
170
+
171
+ function Test() {
172
+ return <Select lockScrollWhileOpen={false} emptyValue="Pick" />
173
+ }
174
+ `.trim()
175
+
176
+ const result = applyTransform(input)
177
+ expect(result).toContain("lockScrollWhileOpen={false}")
178
+ })
179
+ })
180
+
181
+ describe("multiple removals", () => {
182
+ it("should remove multiple default props at once", () => {
183
+ const input = `
184
+ import { Select } from "@planningcenter/tapestry-react"
185
+
186
+ function Test() {
187
+ return (
188
+ <Select
189
+ matchWidths
190
+ defaultOpen={false}
191
+ lockScrollWhileOpen
192
+ emptyValue="Pick"
193
+ />
194
+ )
195
+ }
196
+ `.trim()
197
+
198
+ const result = applyTransform(input)
199
+ expect(result).not.toContain("matchWidths")
200
+ expect(result).not.toContain("defaultOpen")
201
+ expect(result).not.toContain("lockScrollWhileOpen")
202
+ expect(result).toContain('emptyValue="Pick"')
203
+ })
204
+ })
205
+
206
+ describe("edge cases", () => {
207
+ it("should not transform Select without removable props", () => {
208
+ const input = `
209
+ import { Select } from "@planningcenter/tapestry-react"
210
+
211
+ function Test() {
212
+ return <Select emptyValue="Pick" disabled />
213
+ }
214
+ `.trim()
215
+
216
+ const result = applyTransform(input)
217
+ expect(result).toBe(input)
218
+ })
219
+
220
+ it("should not transform other components", () => {
221
+ const input = `
222
+ import { Dropdown } from "other-library"
223
+
224
+ function Test() {
225
+ return <Dropdown matchWidths />
226
+ }
227
+ `.trim()
228
+
229
+ const result = applyTransform(input)
230
+ expect(result).toBe(input)
231
+ })
232
+
233
+ it("should preserve other props", () => {
234
+ const input = `
235
+ import { Select } from "@planningcenter/tapestry-react"
236
+
237
+ function Test() {
238
+ return <Select matchWidths emptyValue="Pick" disabled onChange={handler} />
239
+ }
240
+ `.trim()
241
+
242
+ const result = applyTransform(input)
243
+ expect(result).not.toContain("matchWidths")
244
+ expect(result).toContain('emptyValue="Pick"')
245
+ expect(result).toContain("disabled")
246
+ expect(result).toContain("onChange={handler}")
247
+ })
248
+ })
249
+ })
@@ -0,0 +1,112 @@
1
+ import { JSXAttribute, Transform } from "jscodeshift"
2
+
3
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
4
+ import { transformableSelect } from "../transformableSelect"
5
+
6
+ /**
7
+ * Props that were legacy defaults and should be removed because they are
8
+ * either not supported or are no-ops in the new Select component.
9
+ *
10
+ * - matchWidths: legacy default was "minimum" (always on). The new Select
11
+ * does not use this prop (only relevant in complex/popover mode via CSS).
12
+ * Remove any value: matchWidths, matchWidths={true}, matchWidths="minimum".
13
+ * - defaultOpen={false}: false is the default in both old and new.
14
+ * - lockScrollWhileOpen / lockScrollWhileOpen={true}: legacy default, removed in new API.
15
+ */
16
+
17
+ function isRemovableMatchWidths(attr: JSXAttribute): boolean {
18
+ if ((attr.name.name as string) !== "matchWidths") return false
19
+
20
+ // Shorthand: matchWidths (boolean true)
21
+ if (!attr.value) return true
22
+
23
+ // matchWidths={true}
24
+ if (
25
+ attr.value.type === "JSXExpressionContainer" &&
26
+ attr.value.expression.type === "BooleanLiteral" &&
27
+ attr.value.expression.value === true
28
+ ) {
29
+ return true
30
+ }
31
+
32
+ // matchWidths="minimum"
33
+ if (attr.value.type === "StringLiteral" && attr.value.value === "minimum") {
34
+ return true
35
+ }
36
+
37
+ // matchWidths={"minimum"}
38
+ if (
39
+ attr.value.type === "JSXExpressionContainer" &&
40
+ attr.value.expression.type === "StringLiteral" &&
41
+ attr.value.expression.value === "minimum"
42
+ ) {
43
+ return true
44
+ }
45
+
46
+ return false
47
+ }
48
+
49
+ function isRemovableDefaultOpen(attr: JSXAttribute): boolean {
50
+ if ((attr.name.name as string) !== "defaultOpen") return false
51
+
52
+ // defaultOpen={false}
53
+ if (
54
+ attr.value &&
55
+ attr.value.type === "JSXExpressionContainer" &&
56
+ attr.value.expression.type === "BooleanLiteral" &&
57
+ attr.value.expression.value === false
58
+ ) {
59
+ return true
60
+ }
61
+
62
+ return false
63
+ }
64
+
65
+ function isRemovableLockScroll(attr: JSXAttribute): boolean {
66
+ const name = attr.name.name as string
67
+ if (name !== "lockScrollWhileOpen") return false
68
+
69
+ // lockScrollWhileOpen (shorthand true) or lockScrollWhileOpen={true}
70
+ if (!attr.value) return true
71
+ if (
72
+ attr.value.type === "JSXExpressionContainer" &&
73
+ attr.value.expression.type === "BooleanLiteral" &&
74
+ attr.value.expression.value === true
75
+ ) {
76
+ return true
77
+ }
78
+
79
+ return false
80
+ }
81
+
82
+ const transform: Transform = attributeTransformFactory({
83
+ condition: transformableSelect,
84
+ targetComponent: "Select",
85
+ targetPackage: "@planningcenter/tapestry-react",
86
+ transform: (element) => {
87
+ const attributes = element.openingElement.attributes || []
88
+ const toRemove: number[] = []
89
+
90
+ attributes.forEach((attr, index) => {
91
+ if (attr.type !== "JSXAttribute") return
92
+ if (
93
+ isRemovableMatchWidths(attr) ||
94
+ isRemovableDefaultOpen(attr) ||
95
+ isRemovableLockScroll(attr)
96
+ ) {
97
+ toRemove.push(index)
98
+ }
99
+ })
100
+
101
+ if (toRemove.length === 0) return false
102
+
103
+ // Remove in reverse order to preserve indices
104
+ for (let i = toRemove.length - 1; i >= 0; i--) {
105
+ attributes.splice(toRemove[i], 1)
106
+ }
107
+
108
+ return true
109
+ },
110
+ })
111
+
112
+ export default transform
@@ -0,0 +1,188 @@
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 { Select } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Select size="xs" emptyValue="Pick" />
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 { Select } from "@planningcenter/tapestry-react"
38
+
39
+ function Test() {
40
+ return <Select size="sm" emptyValue="Pick" />
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 { Select } from "@planningcenter/tapestry-react"
53
+
54
+ function Test() {
55
+ return <Select size="xl" emptyValue="Pick" />
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={"xs"} expression to md', () => {
66
+ const input = `
67
+ import { Select } from "@planningcenter/tapestry-react"
68
+
69
+ function Test() {
70
+ return <Select size={"xs"} emptyValue="Pick" />
71
+ }
72
+ `.trim()
73
+
74
+ const result = applyTransform(input)
75
+ expect(result).toContain('size="md"')
76
+ expect(result).toContain('Size "xs" was mapped to "md"')
77
+ })
78
+
79
+ it("should not transform supported sizes (md, lg)", () => {
80
+ const input = `
81
+ import { Select } from "@planningcenter/tapestry-react"
82
+
83
+ function Test() {
84
+ return (
85
+ <div>
86
+ <Select size="md" emptyValue="Pick" />
87
+ <Select size="lg" emptyValue="Pick" />
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 Select without size prop", () => {
102
+ const input = `
103
+ import { Select } from "@planningcenter/tapestry-react"
104
+
105
+ function Test() {
106
+ return <Select emptyValue="Pick" />
107
+ }
108
+ `.trim()
109
+
110
+ const result = applyTransform(input)
111
+ expect(result).toBe(input)
112
+ })
113
+
114
+ it("should not affect other components", () => {
115
+ const input = `
116
+ import { Button, Select } from "@planningcenter/tapestry-react"
117
+
118
+ function Test() {
119
+ return (
120
+ <div>
121
+ <Button size="xs">Click</Button>
122
+ <Select size="md" emptyValue="Pick" />
123
+ </div>
124
+ )
125
+ }
126
+ `.trim()
127
+
128
+ const result = applyTransform(input)
129
+ expect(result).toContain('size="xs"')
130
+ expect(result).toContain('size="md"')
131
+ })
132
+
133
+ it("should handle multiple Selects with different sizes", () => {
134
+ const input = `
135
+ import { Select } from "@planningcenter/tapestry-react"
136
+
137
+ function Test() {
138
+ return (
139
+ <div>
140
+ <Select size="xs" emptyValue="Pick" />
141
+ <Select size="sm" emptyValue="Pick" />
142
+ <Select size="lg" emptyValue="Pick" />
143
+ <Select size="xl" emptyValue="Pick" />
144
+ </div>
145
+ )
146
+ }
147
+ `.trim()
148
+
149
+ const result = applyTransform(input)
150
+ expect(result).not.toContain('size="xs"')
151
+ expect(result).not.toContain('size="sm"')
152
+ expect(result).not.toContain('size="xl"')
153
+ const sizeMappingMatches = result.match(/Size ".*" was mapped to/g)
154
+ expect(sizeMappingMatches).toHaveLength(3)
155
+ })
156
+
157
+ it("should not transform expression values (variables)", () => {
158
+ const input = `
159
+ import { Select } from "@planningcenter/tapestry-react"
160
+
161
+ function Test() {
162
+ const size = "xs"
163
+ return <Select size={size} emptyValue="Pick" />
164
+ }
165
+ `.trim()
166
+
167
+ const result = applyTransform(input)
168
+ expect(result).toContain("size={size}")
169
+ expect(result).not.toContain("Size")
170
+ })
171
+
172
+ it("should preserve other props", () => {
173
+ const input = `
174
+ import { Select } from "@planningcenter/tapestry-react"
175
+
176
+ function Test() {
177
+ return <Select size="xs" emptyValue="Pick" disabled onChange={() => {}} />
178
+ }
179
+ `.trim()
180
+
181
+ const result = applyTransform(input)
182
+ expect(result).toContain('size="md"')
183
+ expect(result).toContain('emptyValue="Pick"')
184
+ expect(result).toContain("disabled")
185
+ expect(result).toContain("onChange={() => {}}")
186
+ })
187
+ })
188
+ })