@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.
- package/dist/tapestry-react-shim.cjs +7 -1
- package/package.json +3 -3
- package/src/components/button/transforms/convertStyleProps.test.ts +97 -0
- package/src/components/button/transforms/removeTypeButton.test.ts +0 -1
- package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +3 -0
- package/src/components/input/index.ts +66 -0
- package/src/components/input/transformableInput.ts +49 -0
- package/src/components/input/transforms/auditSpreadProps.test.ts +192 -0
- package/src/components/input/transforms/auditSpreadProps.ts +26 -0
- package/src/components/input/transforms/autoWidthTransform.test.ts +172 -0
- package/src/components/input/transforms/autoWidthTransform.ts +41 -0
- package/src/components/input/transforms/convertStyleProps.test.ts +128 -0
- package/src/components/input/transforms/convertStyleProps.ts +12 -0
- package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.test.ts +186 -0
- package/src/components/input/transforms/highlightOnInteractionToSelectTextOnFocus.ts +27 -0
- package/src/components/input/transforms/inputLabelToLabelProp.test.ts +319 -0
- package/src/components/input/transforms/inputLabelToLabelProp.ts +203 -0
- package/src/components/input/transforms/mergeFieldIntoInput.test.ts +469 -0
- package/src/components/input/transforms/mergeFieldIntoInput.ts +7 -0
- package/src/components/input/transforms/mergeInputLabel.test.ts +458 -0
- package/src/components/input/transforms/mergeInputLabel.ts +204 -0
- package/src/components/input/transforms/moveInputImport.test.ts +166 -0
- package/src/components/input/transforms/moveInputImport.ts +14 -0
- package/src/components/input/transforms/numberFieldAddTypeNumber.test.ts +92 -0
- package/src/components/input/transforms/numberFieldAddTypeNumber.ts +14 -0
- package/src/components/input/transforms/numberFieldRenameToInput.test.ts +126 -0
- package/src/components/input/transforms/numberFieldRenameToInput.ts +9 -0
- package/src/components/input/transforms/removeAsInput.test.ts +139 -0
- package/src/components/input/transforms/removeAsInput.ts +20 -0
- package/src/components/input/transforms/removeDuplicateKeys.test.ts +302 -0
- package/src/components/input/transforms/removeDuplicateKeys.ts +10 -0
- package/src/components/input/transforms/removeInputBox.test.ts +352 -0
- package/src/components/input/transforms/removeInputBox.ts +109 -0
- package/src/components/input/transforms/removeRedundantAriaLabel.test.ts +128 -0
- package/src/components/input/transforms/removeRedundantAriaLabel.ts +21 -0
- package/src/components/input/transforms/removeTypeInput.test.ts +212 -0
- package/src/components/input/transforms/removeTypeInput.ts +22 -0
- package/src/components/input/transforms/removeTypeText.test.ts +160 -0
- package/src/components/input/transforms/removeTypeText.ts +17 -0
- package/src/components/input/transforms/sizeMapping.test.ts +198 -0
- package/src/components/input/transforms/sizeMapping.ts +17 -0
- package/src/components/input/transforms/skipRenderSideProps.test.ts +236 -0
- package/src/components/input/transforms/skipRenderSideProps.ts +27 -0
- package/src/components/input/transforms/stateToInvalid.test.ts +208 -0
- package/src/components/input/transforms/stateToInvalid.ts +59 -0
- package/src/components/input/transforms/stateToInvalidTernary.test.ts +159 -0
- package/src/components/input/transforms/stateToInvalidTernary.ts +13 -0
- package/src/components/input/transforms/unsupportedProps.test.ts +566 -0
- package/src/components/input/transforms/unsupportedProps.ts +84 -0
- package/src/components/link/transforms/reviewStyles.test.ts +0 -1
- package/src/components/select/index.ts +58 -0
- package/src/components/select/transformableSelect.ts +7 -0
- package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
- package/src/components/select/transforms/auditSpreadProps.ts +26 -0
- package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
- package/src/components/select/transforms/childrenToOptions.ts +295 -0
- package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
- package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
- package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
- package/src/components/select/transforms/convertStyleProps.ts +12 -0
- package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
- package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
- package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
- package/src/components/select/transforms/innerRefToRef.ts +18 -0
- package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
- package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
- package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
- package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
- package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
- package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
- package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
- package/src/components/select/transforms/moveSelectImport.ts +14 -0
- package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
- package/src/components/select/transforms/removeDefaultProps.ts +112 -0
- package/src/components/select/transforms/sizeMapping.test.ts +188 -0
- package/src/components/select/transforms/sizeMapping.ts +17 -0
- package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
- package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
- package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
- package/src/components/select/transforms/stateToInvalid.ts +59 -0
- package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
- package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
- package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
- package/src/components/select/transforms/unsupportedProps.ts +44 -0
- package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +102 -0
- package/src/components/shared/transformFactories/helpers/manageImports.ts +14 -12
- package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
- package/src/components/shared/transformFactories/sizeMappingFactory.ts +9 -2
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +56 -17
- package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +65 -0
- package/src/components/text-area/index.ts +48 -0
- package/src/components/text-area/transforms/auditSpreadProps.test.ts +139 -0
- package/src/components/text-area/transforms/auditSpreadProps.ts +10 -0
- package/src/components/text-area/transforms/convertStyleProps.test.ts +158 -0
- package/src/components/text-area/transforms/convertStyleProps.ts +10 -0
- package/src/components/text-area/transforms/innerRefToRef.test.ts +206 -0
- package/src/components/text-area/transforms/innerRefToRef.ts +14 -0
- package/src/components/text-area/transforms/mergeFieldIntoTextArea.test.ts +477 -0
- package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +5 -0
- package/src/components/text-area/transforms/moveTextAreaImport.test.ts +168 -0
- package/src/components/text-area/transforms/moveTextAreaImport.ts +13 -0
- package/src/components/text-area/transforms/removeDuplicateKeys.test.ts +129 -0
- package/src/components/text-area/transforms/removeDuplicateKeys.ts +8 -0
- package/src/components/text-area/transforms/removeRedundantAriaLabel.test.ts +183 -0
- package/src/components/text-area/transforms/removeRedundantAriaLabel.ts +59 -0
- package/src/components/text-area/transforms/sizeMapping.test.ts +199 -0
- package/src/components/text-area/transforms/sizeMapping.ts +15 -0
- package/src/components/text-area/transforms/stateToInvalid.test.ts +204 -0
- package/src/components/text-area/transforms/stateToInvalid.ts +57 -0
- package/src/components/text-area/transforms/stateToInvalidTernary.test.ts +133 -0
- package/src/components/text-area/transforms/stateToInvalidTernary.ts +11 -0
- package/src/components/text-area/transforms/unsupportedProps.test.ts +275 -0
- package/src/components/text-area/transforms/unsupportedProps.ts +35 -0
- 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
|