@planningcenter/tapestry-migration-cli 3.4.1 → 3.6.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.
- package/README.md +59 -0
- package/dist/tapestry-react-shim.cjs +5 -1
- package/package.json +3 -3
- package/src/availableComponents.ts +14 -0
- package/src/components/button/transforms/tooltipToWrapper.test.ts +50 -3
- package/src/components/button/transforms/tooltipToWrapper.ts +18 -0
- package/src/components/dropdown/index.test.ts +67 -0
- package/src/components/dropdown/index.ts +4 -0
- package/src/components/dropdown/transforms/placementIdToMenu.test.ts +6 -1
- package/src/components/dropdown/transforms/placementIdToMenu.ts +1 -1
- package/src/components/dropdown/transforms/variantToKind.test.ts +292 -0
- package/src/components/dropdown/transforms/variantToKind.ts +138 -0
- package/src/components/dropdown/transforms/wrapIconTrigger.test.ts +336 -0
- package/src/components/dropdown/transforms/wrapIconTrigger.ts +233 -0
- package/src/components/flex/index.test.ts +140 -0
- package/src/components/flex/index.ts +40 -0
- package/src/components/flex/transforms/alignmentToAlign.test.ts +185 -0
- package/src/components/flex/transforms/alignmentToAlign.ts +73 -0
- package/src/components/flex/transforms/axisToDirection.test.ts +140 -0
- package/src/components/flex/transforms/axisToDirection.ts +51 -0
- package/src/components/flex/transforms/distributionToJustify.test.ts +214 -0
- package/src/components/flex/transforms/distributionToJustify.ts +76 -0
- package/src/components/flex/transforms/innerRefToRef.test.ts +100 -0
- package/src/components/flex/transforms/innerRefToRef.ts +14 -0
- package/src/components/flex/transforms/moveFlexImport.test.ts +202 -0
- package/src/components/flex/transforms/moveFlexImport.ts +14 -0
- package/src/components/flex/transforms/setDefaultAxis.test.ts +114 -0
- package/src/components/flex/transforms/setDefaultAxis.ts +29 -0
- package/src/components/flex/transforms/spacingToGap.test.ts +176 -0
- package/src/components/flex/transforms/spacingToGap.ts +49 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/select/transforms/onChangeSignature.test.ts +224 -0
- package/src/components/select/transforms/onChangeSignature.ts +172 -0
- package/src/components/shared/actions/resolveConstantAttributeValue.test.ts +122 -0
- package/src/components/shared/actions/resolveConstantAttributeValue.ts +31 -0
- package/src/components/toggle-switch/index.ts +2 -0
- package/src/components/toggle-switch/transforms/onClickToOnChange.test.ts +210 -0
- package/src/components/toggle-switch/transforms/onClickToOnChange.ts +71 -0
- package/src/index.test.ts +79 -0
- package/src/index.ts +34 -18
- package/src/migrationList.ts +22 -0
- package/src/utils/componentLabel.test.ts +61 -0
- package/src/utils/componentLabel.ts +15 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./setDefaultAxis"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
function applyTransform(source: string): string | null {
|
|
9
|
+
const fileInfo = { path: "test.tsx", source }
|
|
10
|
+
return transform(
|
|
11
|
+
fileInfo,
|
|
12
|
+
{ j, jscodeshift: j, report: () => {}, stats: () => {} },
|
|
13
|
+
{}
|
|
14
|
+
) as string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("setDefaultAxis transform", () => {
|
|
18
|
+
it('adds axis="vertical" to a StackView with no axis', () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
export default function Test() {
|
|
23
|
+
return <StackView>Test</StackView>
|
|
24
|
+
}
|
|
25
|
+
`.trim()
|
|
26
|
+
|
|
27
|
+
const result = applyTransform(input)
|
|
28
|
+
expect(result).toContain('<StackView axis="vertical">Test</StackView>')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("adds the default to self-closing elements", () => {
|
|
32
|
+
const input = `
|
|
33
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
34
|
+
|
|
35
|
+
export default function Test() {
|
|
36
|
+
return <StackView spacing={2} />
|
|
37
|
+
}
|
|
38
|
+
`.trim()
|
|
39
|
+
|
|
40
|
+
const result = applyTransform(input)
|
|
41
|
+
expect(result).toContain('axis="vertical"')
|
|
42
|
+
expect(result).toContain("spacing={2}")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("adds the default to every StackView lacking an axis", () => {
|
|
46
|
+
const input = `
|
|
47
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
48
|
+
|
|
49
|
+
export default function Test() {
|
|
50
|
+
return (
|
|
51
|
+
<StackView>
|
|
52
|
+
<StackView>Inner</StackView>
|
|
53
|
+
</StackView>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
`.trim()
|
|
57
|
+
|
|
58
|
+
const result = applyTransform(input)
|
|
59
|
+
expect(result?.match(/axis="vertical"/g)).toHaveLength(2)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it("does not change a StackView that already has an axis", () => {
|
|
63
|
+
const input = `
|
|
64
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
65
|
+
|
|
66
|
+
export default function Test() {
|
|
67
|
+
return <StackView axis="horizontal">Test</StackView>
|
|
68
|
+
}
|
|
69
|
+
`.trim()
|
|
70
|
+
|
|
71
|
+
expect(applyTransform(input)).toBe(null)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it("skips elements with spread props", () => {
|
|
75
|
+
const input = `
|
|
76
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
77
|
+
|
|
78
|
+
export default function Test(props) {
|
|
79
|
+
return <StackView {...props}>Test</StackView>
|
|
80
|
+
}
|
|
81
|
+
`.trim()
|
|
82
|
+
|
|
83
|
+
expect(applyTransform(input)).toBe(null)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it("respects an aliased StackView import", () => {
|
|
87
|
+
const input = `
|
|
88
|
+
import { StackView as Stack } from "@planningcenter/tapestry-react"
|
|
89
|
+
|
|
90
|
+
export default function Test() {
|
|
91
|
+
return <Stack>Test</Stack>
|
|
92
|
+
}
|
|
93
|
+
`.trim()
|
|
94
|
+
|
|
95
|
+
const result = applyTransform(input)
|
|
96
|
+
expect(result).toContain('<Stack axis="vertical">Test</Stack>')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("returns null when StackView is not imported from tapestry-react", () => {
|
|
100
|
+
const input = `
|
|
101
|
+
import { StackView } from "some-other-package"
|
|
102
|
+
|
|
103
|
+
export default function Test() {
|
|
104
|
+
return <StackView>Test</StackView>
|
|
105
|
+
}
|
|
106
|
+
`.trim()
|
|
107
|
+
|
|
108
|
+
expect(applyTransform(input)).toBe(null)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it("returns null for an empty file", () => {
|
|
112
|
+
expect(applyTransform("")).toBe(null)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addAttribute } from "../../shared/actions/addAttribute"
|
|
4
|
+
import { hasSpreadProps } from "../../shared/actions/hasSpreadProps"
|
|
5
|
+
import { andConditions } from "../../shared/conditions/andConditions"
|
|
6
|
+
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
7
|
+
import { notCondition } from "../../shared/conditions/notCondition"
|
|
8
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
9
|
+
|
|
10
|
+
const transform: Transform = attributeTransformFactory({
|
|
11
|
+
condition: andConditions(
|
|
12
|
+
notCondition(hasAttribute("axis")),
|
|
13
|
+
notCondition(hasSpreadProps)
|
|
14
|
+
),
|
|
15
|
+
targetComponent: "StackView",
|
|
16
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
17
|
+
transform: (element, { j }) => {
|
|
18
|
+
addAttribute({
|
|
19
|
+
element,
|
|
20
|
+
j,
|
|
21
|
+
name: "axis",
|
|
22
|
+
value: "vertical",
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return true
|
|
26
|
+
},
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export default transform
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./spacingToGap"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
function applyTransform(source: string): string | null {
|
|
9
|
+
const fileInfo = { path: "test.tsx", source }
|
|
10
|
+
return transform(
|
|
11
|
+
fileInfo,
|
|
12
|
+
{ j, jscodeshift: j, report: () => {}, stats: () => {} },
|
|
13
|
+
{}
|
|
14
|
+
) as string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("spacingToGap transform", () => {
|
|
18
|
+
it("renames spacing to gap for all valid scale values", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
export default function Test() {
|
|
23
|
+
return (
|
|
24
|
+
<StackView spacing={0}>
|
|
25
|
+
<StackView spacing={0.25}>
|
|
26
|
+
<StackView spacing={0.5}>
|
|
27
|
+
<StackView spacing={1}>
|
|
28
|
+
<StackView spacing={1.5}>
|
|
29
|
+
<StackView spacing={2}>
|
|
30
|
+
<StackView spacing={3}>
|
|
31
|
+
<StackView spacing={4}>
|
|
32
|
+
<StackView spacing={5}>
|
|
33
|
+
<StackView spacing={6}>
|
|
34
|
+
<StackView spacing={7}>Test</StackView>
|
|
35
|
+
</StackView>
|
|
36
|
+
</StackView>
|
|
37
|
+
</StackView>
|
|
38
|
+
</StackView>
|
|
39
|
+
</StackView>
|
|
40
|
+
</StackView>
|
|
41
|
+
</StackView>
|
|
42
|
+
</StackView>
|
|
43
|
+
</StackView>
|
|
44
|
+
</StackView>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
`.trim()
|
|
48
|
+
|
|
49
|
+
const result = applyTransform(input)
|
|
50
|
+
expect(result).toContain("gap={0}")
|
|
51
|
+
expect(result).toContain("gap={0.25}")
|
|
52
|
+
expect(result).toContain("gap={0.5}")
|
|
53
|
+
expect(result).toContain("gap={1}")
|
|
54
|
+
expect(result).toContain("gap={1.5}")
|
|
55
|
+
expect(result).toContain("gap={2}")
|
|
56
|
+
expect(result).toContain("gap={3}")
|
|
57
|
+
expect(result).toContain("gap={4}")
|
|
58
|
+
expect(result).toContain("gap={5}")
|
|
59
|
+
expect(result).toContain("gap={6}")
|
|
60
|
+
expect(result).toContain("gap={7}")
|
|
61
|
+
expect(result).not.toContain("spacing")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("flags an off-scale number with a TODO", () => {
|
|
65
|
+
const input = `
|
|
66
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
67
|
+
|
|
68
|
+
export default function Test() {
|
|
69
|
+
return <StackView spacing={10}>Test</StackView>
|
|
70
|
+
}
|
|
71
|
+
`.trim()
|
|
72
|
+
|
|
73
|
+
const result = applyTransform(input)
|
|
74
|
+
expect(result).toContain("spacing={10}")
|
|
75
|
+
expect(result).toContain("TODO: tapestry-migration (spacing)")
|
|
76
|
+
expect(result).not.toContain("gap=")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("flags a string spacing value with a TODO", () => {
|
|
80
|
+
const input = `
|
|
81
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
82
|
+
|
|
83
|
+
export default function Test() {
|
|
84
|
+
return <StackView spacing="8px">Test</StackView>
|
|
85
|
+
}
|
|
86
|
+
`.trim()
|
|
87
|
+
|
|
88
|
+
const result = applyTransform(input)
|
|
89
|
+
expect(result).toContain('spacing="8px"')
|
|
90
|
+
expect(result).toContain("TODO: tapestry-migration (spacing)")
|
|
91
|
+
expect(result).not.toContain("gap=")
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("flags a ReactNode spacer with a TODO", () => {
|
|
95
|
+
const input = `
|
|
96
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
97
|
+
|
|
98
|
+
export default function Test() {
|
|
99
|
+
return <StackView spacing={<Divider />}>Test</StackView>
|
|
100
|
+
}
|
|
101
|
+
`.trim()
|
|
102
|
+
|
|
103
|
+
const result = applyTransform(input)
|
|
104
|
+
expect(result).toContain("spacing={<Divider />}")
|
|
105
|
+
expect(result).toContain("TODO: tapestry-migration (spacing)")
|
|
106
|
+
expect(result).not.toContain("gap=")
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it("flags an expression-wrapped string spacing value with a TODO", () => {
|
|
110
|
+
const input = `
|
|
111
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
112
|
+
|
|
113
|
+
export default function Test() {
|
|
114
|
+
return <StackView spacing={"8px"}>Test</StackView>
|
|
115
|
+
}
|
|
116
|
+
`.trim()
|
|
117
|
+
|
|
118
|
+
const result = applyTransform(input)
|
|
119
|
+
expect(result).toContain('spacing={"8px"}')
|
|
120
|
+
expect(result).toContain("TODO: tapestry-migration (spacing)")
|
|
121
|
+
expect(result).not.toContain("gap=")
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it("flags a dynamic spacing value with a TODO", () => {
|
|
125
|
+
const input = `
|
|
126
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
127
|
+
|
|
128
|
+
export default function Test({ spacingProp }) {
|
|
129
|
+
return <StackView spacing={spacingProp}>Test</StackView>
|
|
130
|
+
}
|
|
131
|
+
`.trim()
|
|
132
|
+
|
|
133
|
+
const result = applyTransform(input)
|
|
134
|
+
expect(result).toContain("spacing={spacingProp}")
|
|
135
|
+
expect(result).toContain("TODO: tapestry-migration (spacing)")
|
|
136
|
+
expect(result).not.toContain("gap=")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("respects an aliased StackView import", () => {
|
|
140
|
+
const input = `
|
|
141
|
+
import { StackView as Stack } from "@planningcenter/tapestry-react"
|
|
142
|
+
|
|
143
|
+
export default function Test() {
|
|
144
|
+
return <Stack spacing={2}>Test</Stack>
|
|
145
|
+
}
|
|
146
|
+
`.trim()
|
|
147
|
+
|
|
148
|
+
const result = applyTransform(input)
|
|
149
|
+
expect(result).toContain("gap={2}")
|
|
150
|
+
expect(result).not.toContain("spacing")
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it("returns null when there is no spacing to migrate", () => {
|
|
154
|
+
const input = `
|
|
155
|
+
import { StackView } from "@planningcenter/tapestry-react"
|
|
156
|
+
|
|
157
|
+
export default function Test() {
|
|
158
|
+
return <StackView>Test</StackView>
|
|
159
|
+
}
|
|
160
|
+
`.trim()
|
|
161
|
+
|
|
162
|
+
expect(applyTransform(input)).toBe(null)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it("returns null when StackView is not imported from tapestry-react", () => {
|
|
166
|
+
const input = `
|
|
167
|
+
import { StackView } from "some-other-package"
|
|
168
|
+
|
|
169
|
+
export default function Test() {
|
|
170
|
+
return <StackView spacing={2}>Test</StackView>
|
|
171
|
+
}
|
|
172
|
+
`.trim()
|
|
173
|
+
|
|
174
|
+
expect(applyTransform(input)).toBe(null)
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JSXAttribute, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addComment } from "../../shared/actions/addComment"
|
|
4
|
+
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
5
|
+
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
6
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
7
|
+
|
|
8
|
+
const VALID_GAP_VALUES = new Set([0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7])
|
|
9
|
+
|
|
10
|
+
function resolveSpacingValue(attribute: JSXAttribute): number | null {
|
|
11
|
+
const value = attribute.value
|
|
12
|
+
if (
|
|
13
|
+
value?.type === "JSXExpressionContainer" &&
|
|
14
|
+
value.expression.type === "NumericLiteral" &&
|
|
15
|
+
VALID_GAP_VALUES.has(value.expression.value)
|
|
16
|
+
) {
|
|
17
|
+
return value.expression.value
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const transform: Transform = attributeTransformFactory({
|
|
23
|
+
condition: hasAttribute("spacing"),
|
|
24
|
+
targetComponent: "StackView",
|
|
25
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
26
|
+
transform: (element, { j, source }) => {
|
|
27
|
+
const spacingAttribute = getAttribute({ element, name: "spacing" })
|
|
28
|
+
if (!spacingAttribute) return false
|
|
29
|
+
|
|
30
|
+
const spacingValue = resolveSpacingValue(spacingAttribute)
|
|
31
|
+
|
|
32
|
+
if (spacingValue === null) {
|
|
33
|
+
addComment({
|
|
34
|
+
element,
|
|
35
|
+
j,
|
|
36
|
+
scope: "spacing",
|
|
37
|
+
source,
|
|
38
|
+
text: "could not migrate spacing to gap automatically; use a Tapestry spacing scale number (0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7) for gap, or for ReactNode spacers add explicit margin to the spacer element instead",
|
|
39
|
+
})
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
spacingAttribute.name.name = "gap"
|
|
44
|
+
|
|
45
|
+
return true
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export default transform
|
|
@@ -10,6 +10,7 @@ import mapChildrenToOptions from "./transforms/mapChildrenToOptions"
|
|
|
10
10
|
import mergeFieldIntoSelect from "./transforms/mergeFieldIntoSelect"
|
|
11
11
|
import mergeSelectLabel from "./transforms/mergeSelectLabel"
|
|
12
12
|
import moveSelectImport from "./transforms/moveSelectImport"
|
|
13
|
+
import onChangeSignature from "./transforms/onChangeSignature"
|
|
13
14
|
import removeDefaultProps from "./transforms/removeDefaultProps"
|
|
14
15
|
import sizeMapping from "./transforms/sizeMapping"
|
|
15
16
|
import skipMultipleSelect from "./transforms/skipMultipleSelect"
|
|
@@ -36,6 +37,7 @@ const transform: Transform = (fileInfo, api, options) => {
|
|
|
36
37
|
stateToInvalid,
|
|
37
38
|
removeDefaultProps,
|
|
38
39
|
convertStyleProps,
|
|
40
|
+
onChangeSignature,
|
|
39
41
|
unsupportedProps,
|
|
40
42
|
moveSelectImport,
|
|
41
43
|
]
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./onChangeSignature"
|
|
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("onChangeSignature transform", () => {
|
|
19
|
+
it("auto-rewrites ({ value }) => expr to (e) => e.target.value", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
function Test() {
|
|
24
|
+
return <Select onChange={({ value }) => setX(value)} emptyValue="Pick" />
|
|
25
|
+
}
|
|
26
|
+
`.trim()
|
|
27
|
+
|
|
28
|
+
const result = applyTransform(input)
|
|
29
|
+
// jscodeshift omits parens for a single identifier param: `e =>` not `(e) =>`
|
|
30
|
+
expect(result).toContain("e =>")
|
|
31
|
+
expect(result).toContain("e.target.value")
|
|
32
|
+
expect(result).not.toContain("({ value })")
|
|
33
|
+
expect(result).not.toContain("TODO")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("replaces all value references in the expression body", () => {
|
|
37
|
+
const input = `
|
|
38
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
39
|
+
|
|
40
|
+
function Test() {
|
|
41
|
+
return <Select onChange={({ value }) => fn(value, value)} emptyValue="Pick" />
|
|
42
|
+
}
|
|
43
|
+
`.trim()
|
|
44
|
+
|
|
45
|
+
const result = applyTransform(input)
|
|
46
|
+
expect(result).toContain("fn(e.target.value, e.target.value)")
|
|
47
|
+
expect(result).not.toContain("({ value })")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("adds a TODO comment for a function reference", () => {
|
|
51
|
+
const input = `
|
|
52
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
53
|
+
|
|
54
|
+
function Test() {
|
|
55
|
+
return <Select onChange={handler} emptyValue="Pick" />
|
|
56
|
+
}
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
const result = applyTransform(input)
|
|
60
|
+
expect(result).toContain("onChange={handler}")
|
|
61
|
+
expect(result).toContain("TODO: tapestry-migration (onChange)")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it("adds a TODO comment when ({ selectedValue }) is destructured", () => {
|
|
65
|
+
const input = `
|
|
66
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
67
|
+
|
|
68
|
+
function Test() {
|
|
69
|
+
return <Select onChange={({ selectedValue }) => setX(selectedValue)} emptyValue="Pick" />
|
|
70
|
+
}
|
|
71
|
+
`.trim()
|
|
72
|
+
|
|
73
|
+
const result = applyTransform(input)
|
|
74
|
+
expect(result).toContain("({ selectedValue })")
|
|
75
|
+
expect(result).toContain("TODO: tapestry-migration (onChange)")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("adds a TODO comment for a block-body arrow (too complex to auto-rewrite)", () => {
|
|
79
|
+
const input = `
|
|
80
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
81
|
+
|
|
82
|
+
function Test() {
|
|
83
|
+
return <Select onChange={({ value }) => { setX(value) }} emptyValue="Pick" />
|
|
84
|
+
}
|
|
85
|
+
`.trim()
|
|
86
|
+
|
|
87
|
+
const result = applyTransform(input)
|
|
88
|
+
expect(result).toContain("{ setX(value) }")
|
|
89
|
+
expect(result).toContain("TODO: tapestry-migration (onChange)")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it("expands object-literal shorthand return ({ value }) to ({ value: e.target.value })", () => {
|
|
93
|
+
const input = `
|
|
94
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
95
|
+
|
|
96
|
+
function Test() {
|
|
97
|
+
return <Select onChange={({ value }) => ({ value })} emptyValue="Pick" />
|
|
98
|
+
}
|
|
99
|
+
`.trim()
|
|
100
|
+
|
|
101
|
+
const result = applyTransform(input)
|
|
102
|
+
expect(result).toContain("value: e.target.value")
|
|
103
|
+
expect(result).not.toContain("TODO")
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it("leaves member-property `value` access untouched (obj.value and obj?.value)", () => {
|
|
107
|
+
const dot = `
|
|
108
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
109
|
+
|
|
110
|
+
function Test() {
|
|
111
|
+
return <Select onChange={({ value }) => setX(obj.value)} emptyValue="Pick" />
|
|
112
|
+
}
|
|
113
|
+
`.trim()
|
|
114
|
+
const dotResult = applyTransform(dot)
|
|
115
|
+
expect(dotResult).toContain("setX(obj.value)")
|
|
116
|
+
expect(dotResult).not.toContain("e.target.value")
|
|
117
|
+
|
|
118
|
+
const optional = `
|
|
119
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
120
|
+
|
|
121
|
+
function Test() {
|
|
122
|
+
return <Select onChange={({ value }) => setX(obj?.value)} emptyValue="Pick" />
|
|
123
|
+
}
|
|
124
|
+
`.trim()
|
|
125
|
+
const optionalResult = applyTransform(optional)
|
|
126
|
+
expect(optionalResult).toContain("obj?.value")
|
|
127
|
+
expect(optionalResult).not.toContain("obj?.e.target.value")
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("adds a TODO when a nested callback shadows `value` (would corrupt the binding)", () => {
|
|
131
|
+
const input = `
|
|
132
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
133
|
+
|
|
134
|
+
function Test() {
|
|
135
|
+
return <Select onChange={({ value }) => list.map(value => value)} emptyValue="Pick" />
|
|
136
|
+
}
|
|
137
|
+
`.trim()
|
|
138
|
+
|
|
139
|
+
const result = applyTransform(input)
|
|
140
|
+
expect(result).toContain("list.map(value => value)")
|
|
141
|
+
expect(result).toContain("TODO: tapestry-migration (onChange)")
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("adds a TODO when a nested callback destructures its own `value`", () => {
|
|
145
|
+
const input = `
|
|
146
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
147
|
+
|
|
148
|
+
function Test() {
|
|
149
|
+
return <Select onChange={({ value }) => list.map(({ value }) => value)} emptyValue="Pick" />
|
|
150
|
+
}
|
|
151
|
+
`.trim()
|
|
152
|
+
|
|
153
|
+
const result = applyTransform(input)
|
|
154
|
+
expect(result).toContain("TODO: tapestry-migration (onChange)")
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("rewrites references inside a nested callback that does NOT shadow `value`", () => {
|
|
158
|
+
const input = `
|
|
159
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
160
|
+
|
|
161
|
+
function Test() {
|
|
162
|
+
return <Select onChange={({ value }) => arr.filter(x => x === value)} emptyValue="Pick" />
|
|
163
|
+
}
|
|
164
|
+
`.trim()
|
|
165
|
+
|
|
166
|
+
const result = applyTransform(input)
|
|
167
|
+
expect(result).toContain("x === e.target.value")
|
|
168
|
+
expect(result).not.toContain("TODO")
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it("does not transform Select with no onChange", () => {
|
|
172
|
+
const input = `
|
|
173
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
174
|
+
|
|
175
|
+
function Test() {
|
|
176
|
+
return <Select emptyValue="Pick" />
|
|
177
|
+
}
|
|
178
|
+
`.trim()
|
|
179
|
+
|
|
180
|
+
const result = applyTransform(input)
|
|
181
|
+
expect(result).toBe(input)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
it("does not transform other components", () => {
|
|
185
|
+
const input = `
|
|
186
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
187
|
+
|
|
188
|
+
function Test() {
|
|
189
|
+
return <Button onChange={({ value }) => setX(value)}>Click</Button>
|
|
190
|
+
}
|
|
191
|
+
`.trim()
|
|
192
|
+
|
|
193
|
+
const result = applyTransform(input)
|
|
194
|
+
expect(result).toBe(input)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it("does not transform a multiple Select (skipped by transformableSelect)", () => {
|
|
198
|
+
const input = `
|
|
199
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
200
|
+
|
|
201
|
+
function Test() {
|
|
202
|
+
return <Select multiple onChange={({ value }) => setX(value)} emptyValue="Pick" />
|
|
203
|
+
}
|
|
204
|
+
`.trim()
|
|
205
|
+
|
|
206
|
+
const result = applyTransform(input)
|
|
207
|
+
expect(result).toBe(input)
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it("preserves other props when auto-rewriting", () => {
|
|
211
|
+
const input = `
|
|
212
|
+
import { Select } from "@planningcenter/tapestry-react"
|
|
213
|
+
|
|
214
|
+
function Test() {
|
|
215
|
+
return <Select disabled emptyValue="Pick" onChange={({ value }) => setX(value)} />
|
|
216
|
+
}
|
|
217
|
+
`.trim()
|
|
218
|
+
|
|
219
|
+
const result = applyTransform(input)
|
|
220
|
+
expect(result).toContain("disabled")
|
|
221
|
+
expect(result).toContain("emptyValue")
|
|
222
|
+
expect(result).toContain("e.target.value")
|
|
223
|
+
})
|
|
224
|
+
})
|