@planningcenter/tapestry-migration-cli 3.1.0-rc.2 → 3.1.0-rc.20

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 (115) hide show
  1. package/dist/tapestry-react-shim.cjs +7 -1
  2. package/package.json +3 -3
  3. package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
  4. package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
  5. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
  6. package/src/components/input/index.ts +66 -0
  7. package/src/components/input/transformableInput.ts +49 -0
  8. package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
  9. package/src/components/input/transforms/auditSpreadProps.ts +26 -0
  10. package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
  11. package/src/components/input/transforms/autoWidthTransform.ts +41 -0
  12. package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
  13. package/src/components/input/transforms/convertStyleProps.ts +12 -0
  14. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
  15. package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
  16. package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
  17. package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
  18. package/src/components/input/transforms/mergeFieldIntoInput.test.ts +469 -0
  19. package/src/components/input/transforms/mergeFieldIntoInput.ts +7 -0
  20. package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
  21. package/src/components/input/transforms/mergeInputLabel.ts +204 -0
  22. package/src/components/input/transforms/moveInputImport.test.ts +166 -0
  23. package/src/components/input/transforms/moveInputImport.ts +14 -0
  24. package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
  25. package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
  26. package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
  27. package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
  28. package/src/components/input/transforms/removeAsInput.test.ts +139 -0
  29. package/src/components/input/transforms/removeAsInput.ts +20 -0
  30. package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
  31. package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
  32. package/src/components/input/transforms/removeInputBox.test.ts +352 -0
  33. package/src/components/input/transforms/removeInputBox.ts +109 -0
  34. package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
  35. package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
  36. package/src/components/input/transforms/removeTypeInput.test.ts +212 -0
  37. package/src/components/input/transforms/removeTypeInput.ts +22 -0
  38. package/src/components/input/transforms/removeTypeText.test.ts +160 -0
  39. package/src/components/input/transforms/removeTypeText.ts +17 -0
  40. package/src/components/input/transforms/sizeMapping.test.ts +198 -0
  41. package/src/components/input/transforms/sizeMapping.ts +17 -0
  42. package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
  43. package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
  44. package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
  45. package/src/components/input/transforms/stateToInvalid.ts +59 -0
  46. package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
  47. package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
  48. package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
  49. package/src/components/input/transforms/unsupportedProps.ts +84 -0
  50. package/src/components/link/transforms/reviewStyles.test.ts +0 -1
  51. package/src/components/select/index.ts +58 -0
  52. package/src/components/select/transformableSelect.ts +7 -0
  53. package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
  54. package/src/components/select/transforms/auditSpreadProps.ts +26 -0
  55. package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
  56. package/src/components/select/transforms/childrenToOptions.ts +295 -0
  57. package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
  58. package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
  59. package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
  60. package/src/components/select/transforms/convertStyleProps.ts +12 -0
  61. package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
  62. package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
  63. package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
  64. package/src/components/select/transforms/innerRefToRef.ts +18 -0
  65. package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
  66. package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
  67. package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
  68. package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
  69. package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
  70. package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
  71. package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
  72. package/src/components/select/transforms/moveSelectImport.ts +14 -0
  73. package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
  74. package/src/components/select/transforms/removeDefaultProps.ts +112 -0
  75. package/src/components/select/transforms/sizeMapping.test.ts +188 -0
  76. package/src/components/select/transforms/sizeMapping.ts +17 -0
  77. package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
  78. package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
  79. package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
  80. package/src/components/select/transforms/stateToInvalid.ts +59 -0
  81. package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
  82. package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
  83. package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
  84. package/src/components/select/transforms/unsupportedProps.ts +44 -0
  85. package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
  86. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +102 -0
  87. package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
  88. package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
  89. package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
  90. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +56 -17
  91. package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
  92. package/src/components/text-area/index.ts +48 -0
  93. package/src/components/text-area/transforms/auditSpreadProps.test.ts +139 -0
  94. package/src/components/text-area/transforms/auditSpreadProps.ts +10 -0
  95. package/src/components/text-area/transforms/convertStyleProps.test.ts +158 -0
  96. package/src/components/text-area/transforms/convertStyleProps.ts +10 -0
  97. package/src/components/text-area/transforms/innerRefToRef.test.ts +206 -0
  98. package/src/components/text-area/transforms/innerRefToRef.ts +14 -0
  99. package/src/components/text-area/transforms/mergeFieldIntoTextArea.test.ts +477 -0
  100. package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +5 -0
  101. package/src/components/text-area/transforms/moveTextAreaImport.test.ts +168 -0
  102. package/src/components/text-area/transforms/moveTextAreaImport.ts +13 -0
  103. package/src/components/text-area/transforms/removeDuplicateKeys.test.ts +129 -0
  104. package/src/components/text-area/transforms/removeDuplicateKeys.ts +8 -0
  105. package/src/components/text-area/transforms/removeRedundantAriaLabel.test.ts +183 -0
  106. package/src/components/text-area/transforms/removeRedundantAriaLabel.ts +59 -0
  107. package/src/components/text-area/transforms/sizeMapping.test.ts +199 -0
  108. package/src/components/text-area/transforms/sizeMapping.ts +15 -0
  109. package/src/components/text-area/transforms/stateToInvalid.test.ts +204 -0
  110. package/src/components/text-area/transforms/stateToInvalid.ts +57 -0
  111. package/src/components/text-area/transforms/stateToInvalidTernary.test.ts +133 -0
  112. package/src/components/text-area/transforms/stateToInvalidTernary.ts +11 -0
  113. package/src/components/text-area/transforms/unsupportedProps.test.ts +275 -0
  114. package/src/components/text-area/transforms/unsupportedProps.ts +35 -0
  115. package/src/index.ts +2 -1
@@ -0,0 +1,109 @@
1
+ import { JSXAttribute, JSXElement, JSXText, Transform } from "jscodeshift"
2
+
3
+ type JSXChild = NonNullable<JSXElement["children"]>[number]
4
+
5
+ import { addComment } from "../../shared/actions/addComment"
6
+ import { getImportName } from "../../shared/transformFactories/helpers/manageImports"
7
+
8
+ const SCOPE = "removeInputBox"
9
+
10
+ const transform: Transform = (fileInfo, api) => {
11
+ const j = api.jscodeshift
12
+ const source = j(fileInfo.source)
13
+
14
+ const inputLocalName = getImportName(
15
+ "Input",
16
+ "@planningcenter/tapestry-react",
17
+ { j, source }
18
+ )
19
+ if (!inputLocalName) return null
20
+
21
+ let hasChanges = false
22
+
23
+ const isConvertibleInputChild = (child: JSXChild): child is JSXElement => {
24
+ if (child.type !== "JSXElement") return false
25
+ if (child.openingElement.name.type !== "JSXIdentifier") return false
26
+ if (child.openingElement.name.name !== inputLocalName) return false
27
+ const attrs = child.openingElement.attributes || []
28
+ return !attrs.some(
29
+ (a) =>
30
+ a.type === "JSXAttribute" &&
31
+ a.name.type === "JSXIdentifier" &&
32
+ (a.name.name === "renderLeft" || a.name.name === "renderRight")
33
+ )
34
+ }
35
+
36
+ source.find(j.JSXElement).forEach((path) => {
37
+ const el = path.value
38
+ const opening = el.openingElement
39
+
40
+ if (opening.name.type !== "JSXMemberExpression") return
41
+ const { object, property } = opening.name
42
+ if (object.type !== "JSXIdentifier" || object.name !== inputLocalName)
43
+ return
44
+ if (property.name !== "InputBox") return
45
+
46
+ const elementChildren = (el.children || []).filter(
47
+ (child) =>
48
+ child.type !== "JSXText" || (child as JSXText).value.trim() !== ""
49
+ )
50
+ const inputChildren = elementChildren.filter(isConvertibleInputChild)
51
+
52
+ // Case: InputBox has props — add TODO to each Input child, don't unwrap
53
+ const attrs = opening.attributes || []
54
+ if (attrs.length > 0) {
55
+ const propNames = attrs
56
+ .filter(
57
+ (a): a is JSXAttribute =>
58
+ a.type === "JSXAttribute" && a.name?.type === "JSXIdentifier"
59
+ )
60
+ .map((a) => a.name.name)
61
+ .join(", ")
62
+
63
+ for (const child of inputChildren) {
64
+ addComment({
65
+ element: child,
66
+ j,
67
+ scope: SCOPE,
68
+ source,
69
+ text: `Tapestry doesn't support InputBox passing props to children (${propNames}). Please migrate manually.`,
70
+ })
71
+ hasChanges = true
72
+ }
73
+ return
74
+ }
75
+
76
+ // Case: InputBox has more than one non-whitespace child — add TODO to each Input child, don't unwrap
77
+ if (elementChildren.length > 1) {
78
+ for (const child of inputChildren) {
79
+ addComment({
80
+ element: child,
81
+ j,
82
+ scope: SCOPE,
83
+ source,
84
+ text: "InputBox has multiple children and cannot be auto-unwrapped. Please migrate manually.",
85
+ })
86
+ hasChanges = true
87
+ }
88
+ return
89
+ }
90
+
91
+ // Only unwrap when there is exactly one non-whitespace child and it is a convertible Input
92
+ if (elementChildren.length !== 1 || inputChildren.length !== 1) return
93
+
94
+ const parent = path.parent?.value
95
+ if (parent?.children) {
96
+ const idx = parent.children.indexOf(el)
97
+ if (idx === -1) return
98
+ parent.children.splice(idx, 1, ...(el.children || []))
99
+ } else {
100
+ // Root-level JSX — use path.replace
101
+ path.replace(inputChildren[0])
102
+ }
103
+ hasChanges = true
104
+ })
105
+
106
+ return hasChanges ? source.toSource() : null
107
+ }
108
+
109
+ export default transform
@@ -0,0 +1,128 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeRedundantAriaLabel"
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("removeRedundantAriaLabel transform", () => {
21
+ describe("basic transformation", () => {
22
+ it("should remove aria-label when both aria-label and label are present", () => {
23
+ const input = `
24
+ import { Input } from "@planningcenter/tapestry-react"
25
+
26
+ function Component() {
27
+ return <Input aria-label="Name" label="Name" />
28
+ }
29
+ `
30
+
31
+ const result = applyTransform(input)
32
+
33
+ expect(result).toContain('<Input label="Name" />')
34
+ expect(result).not.toContain("aria-label")
35
+ })
36
+
37
+ it("should preserve aria-label when no label prop exists", () => {
38
+ const input = `
39
+ import { Input } from "@planningcenter/tapestry-react"
40
+
41
+ function Component() {
42
+ return <Input aria-label="Name" />
43
+ }
44
+ `
45
+
46
+ const result = applyTransform(input)
47
+
48
+ expect(result).toBeNull()
49
+ })
50
+
51
+ it("should return null when Input has neither aria-label nor label", () => {
52
+ const input = `
53
+ import { Input } from "@planningcenter/tapestry-react"
54
+
55
+ function Component() {
56
+ return <Input onChange={handleChange} />
57
+ }
58
+ `
59
+
60
+ const result = applyTransform(input)
61
+
62
+ expect(result).toBeNull()
63
+ })
64
+ })
65
+
66
+ describe("edge cases", () => {
67
+ it("should not affect other components with the same props", () => {
68
+ const input = `
69
+ import { Input } from "@planningcenter/tapestry-react"
70
+
71
+ function Component() {
72
+ return (
73
+ <div>
74
+ <Input aria-label="Name" label="Name" />
75
+ <input aria-label="Name" label="Name" />
76
+ </div>
77
+ )
78
+ }
79
+ `
80
+
81
+ const result = applyTransform(input)
82
+
83
+ expect(result).toContain('<Input label="Name" />')
84
+ expect(result).toContain('<input aria-label="Name" label="Name" />')
85
+ })
86
+
87
+ it("should handle expression syntax aria-label={'text'}", () => {
88
+ const input = `
89
+ import { Input } from "@planningcenter/tapestry-react"
90
+
91
+ function Component() {
92
+ return <Input aria-label={"Name"} label="Name" />
93
+ }
94
+ `
95
+
96
+ const result = applyTransform(input)
97
+
98
+ if (result) {
99
+ expect(result).toContain('<Input label="Name" />')
100
+ expect(result).not.toContain("aria-label")
101
+ } else {
102
+ expect(input).toContain('aria-label={"Name"}')
103
+ }
104
+ })
105
+
106
+ it("should handle multiple Inputs in one file with mixed cases", () => {
107
+ const input = `
108
+ import { Input } from "@planningcenter/tapestry-react"
109
+
110
+ function Component() {
111
+ return (
112
+ <div>
113
+ <Input aria-label="Name" label="Name" />
114
+ <Input aria-label="Email" />
115
+ <Input label="Phone" />
116
+ </div>
117
+ )
118
+ }
119
+ `
120
+
121
+ const result = applyTransform(input)
122
+
123
+ expect(result).toContain('<Input label="Name" />')
124
+ expect(result).toContain('aria-label="Email"')
125
+ expect(result).toContain('<Input label="Phone" />')
126
+ })
127
+ })
128
+ })
@@ -0,0 +1,21 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
4
+ import { andConditions } from "../../shared/conditions/andConditions"
5
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+ import { transformableInput } from "../transformableInput"
8
+
9
+ const transform: Transform = attributeTransformFactory({
10
+ condition: andConditions(
11
+ transformableInput,
12
+ hasAttribute("aria-label"),
13
+ hasAttribute("label")
14
+ ),
15
+ targetComponent: "Input",
16
+ targetPackage: "@planningcenter/tapestry-react",
17
+ transform: (element, { j, source }) =>
18
+ removeAttribute("aria-label", { element, j, source }),
19
+ })
20
+
21
+ export default transform
@@ -0,0 +1,212 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeTypeInput"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ // Helper to run transform and get result
9
+ function applyTransform(source: string): string | null {
10
+ const fileInfo = { path: "test.tsx", source }
11
+ const api = {
12
+ j,
13
+ jscodeshift: j,
14
+ report: () => {},
15
+ stats: () => {},
16
+ }
17
+ const result = transform(fileInfo, api, {})
18
+ return result as string | null
19
+ }
20
+
21
+ describe("removeTypeInput transform", () => {
22
+ describe("basic transformation", () => {
23
+ it("should remove type='input' from Input", () => {
24
+ const input = `
25
+ import { Input } from "@planningcenter/tapestry-react"
26
+
27
+ function Component() {
28
+ return <Input type="input" />
29
+ }
30
+ `
31
+
32
+ const result = applyTransform(input)
33
+
34
+ expect(result).toContain("<Input />")
35
+ expect(result).not.toContain('type="input"')
36
+ })
37
+
38
+ it('should remove type="input" from Input with other props', () => {
39
+ const input = `
40
+ import { Input } from "@planningcenter/tapestry-react"
41
+
42
+ function Component() {
43
+ return <Input type="input" onChange={handleChange} />
44
+ }
45
+ `
46
+
47
+ const result = applyTransform(input)
48
+
49
+ expect(result).toContain("<Input onChange={handleChange} />")
50
+ expect(result).not.toContain('type="input"')
51
+ })
52
+
53
+ it("should preserve Input without type='input'", () => {
54
+ const input = `
55
+ import { Input } from "@planningcenter/tapestry-react"
56
+
57
+ function Component() {
58
+ return <Input onChange={handleChange} />
59
+ }
60
+ `
61
+
62
+ const result = applyTransform(input)
63
+
64
+ expect(result).toBeNull()
65
+ })
66
+
67
+ it("should preserve Input with type='email'", () => {
68
+ const input = `
69
+ import { Input } from "@planningcenter/tapestry-react"
70
+
71
+ function Component() {
72
+ return <Input type="email" />
73
+ }
74
+ `
75
+
76
+ const result = applyTransform(input)
77
+
78
+ expect(result).toBeNull()
79
+ })
80
+
81
+ it("should preserve Input with type='number'", () => {
82
+ const input = `
83
+ import { Input } from "@planningcenter/tapestry-react"
84
+
85
+ function Component() {
86
+ return <Input type="number" />
87
+ }
88
+ `
89
+
90
+ const result = applyTransform(input)
91
+
92
+ expect(result).toBeNull()
93
+ })
94
+
95
+ it("should remove type='text' from Input", () => {
96
+ const input = `
97
+ import { Input } from "@planningcenter/tapestry-react"
98
+
99
+ function Component() {
100
+ return <Input type="text" />
101
+ }
102
+ `
103
+
104
+ const result = applyTransform(input)
105
+
106
+ expect(result).toContain("<Input />")
107
+ expect(result).not.toContain('type="text"')
108
+ })
109
+
110
+ it("should remove type='text' from Input with other props", () => {
111
+ const input = `
112
+ import { Input } from "@planningcenter/tapestry-react"
113
+
114
+ function Component() {
115
+ return <Input type="text" value={searchTerm} placeholder="Search" onChange={handleChange} />
116
+ }
117
+ `
118
+
119
+ const result = applyTransform(input)
120
+
121
+ expect(result).not.toContain('type="text"')
122
+ expect(result).toContain("value={searchTerm}")
123
+ expect(result).toContain('placeholder="Search"')
124
+ expect(result).toContain("onChange={handleChange}")
125
+ })
126
+ })
127
+
128
+ describe("multiple inputs", () => {
129
+ it("should handle mixed Input usage", () => {
130
+ const input = `
131
+ import { Input } from "@planningcenter/tapestry-react"
132
+
133
+ function Component() {
134
+ return (
135
+ <div>
136
+ <Input type="input" />
137
+ <Input type="text" />
138
+ <Input type="email" />
139
+ <Input type="input" onChange={handleChange} />
140
+ </div>
141
+ )
142
+ }
143
+ `
144
+
145
+ const result = applyTransform(input)
146
+
147
+ expect(result).toContain('<Input type="email" />')
148
+ expect(result).toContain("<Input onChange={handleChange} />")
149
+ expect(result).not.toContain('type="input"')
150
+ expect(result).not.toContain('type="text"')
151
+ })
152
+ })
153
+
154
+ describe("edge cases", () => {
155
+ it("should not affect HTML input elements", () => {
156
+ const input = `
157
+ import { Input } from "@planningcenter/tapestry-react"
158
+
159
+ function Component() {
160
+ return (
161
+ <div>
162
+ <Input type="input" />
163
+ <input type="input" />
164
+ </div>
165
+ )
166
+ }
167
+ `
168
+
169
+ const result = applyTransform(input)
170
+
171
+ expect(result).toContain("<Input />")
172
+ expect(result).toContain('<input type="input" />')
173
+ })
174
+
175
+ it("should handle expression syntax type={'input'}", () => {
176
+ const input = `
177
+ import { Input } from "@planningcenter/tapestry-react"
178
+
179
+ function Component() {
180
+ return <Input type={"input"} />
181
+ }
182
+ `
183
+
184
+ const result = applyTransform(input)
185
+
186
+ if (result) {
187
+ expect(result).toContain("<Input />")
188
+ expect(result).not.toContain('type={"input"}')
189
+ } else {
190
+ expect(input).toContain('type={"input"}')
191
+ }
192
+ })
193
+ })
194
+
195
+ describe("import handling", () => {
196
+ it("should not affect imports", () => {
197
+ const input = `
198
+ import { Input } from "@planningcenter/tapestry-react"
199
+
200
+ function Component() {
201
+ return <Input type="input" />
202
+ }
203
+ `
204
+
205
+ const result = applyTransform(input)
206
+
207
+ expect(result).toContain(
208
+ 'import { Input } from "@planningcenter/tapestry-react"'
209
+ )
210
+ })
211
+ })
212
+ })
@@ -0,0 +1,22 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
4
+ import { hasAttributeValue } from "../../shared/conditions/hasAttributeValue"
5
+ import { orConditions } from "../../shared/conditions/orConditions"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+ import { transformableInput } from "../transformableInput"
8
+
9
+ const transform: Transform = attributeTransformFactory({
10
+ condition: (element) =>
11
+ transformableInput(element) &&
12
+ orConditions(
13
+ hasAttributeValue("type", "input"),
14
+ hasAttributeValue("type", "text")
15
+ )(element),
16
+ targetComponent: "Input",
17
+ targetPackage: "@planningcenter/tapestry-react",
18
+ transform: (element, { j, source }) =>
19
+ removeAttribute("type", { element, j, source }),
20
+ })
21
+
22
+ export default transform
@@ -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,17 @@
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) =>
10
+ transformableInput(element) && hasAttributeValue("type", "text")(element),
11
+ targetComponent: "Input",
12
+ targetPackage: "@planningcenter/tapestry-react",
13
+ transform: (element, { j, source }) =>
14
+ removeAttribute("type", { element, j, source }),
15
+ })
16
+
17
+ export default transform