@planningcenter/tapestry-migration-cli 3.4.1-rc.9 → 3.5.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 +22 -0
- package/dist/tapestry-react-shim.cjs +5 -1
- package/package.json +3 -3
- package/src/availableComponents.ts +13 -0
- package/src/components/dropdown/index.test.ts +244 -4
- package/src/components/dropdown/index.ts +28 -2
- package/src/components/dropdown/transforms/auditSpreadProps.test.ts +110 -0
- package/src/components/dropdown/transforms/auditSpreadProps.ts +10 -0
- package/src/components/dropdown/transforms/dividerToSeparator.test.ts +134 -0
- package/src/components/dropdown/transforms/dividerToSeparator.ts +67 -0
- package/src/components/dropdown/transforms/itemToAction.test.ts +19 -1
- package/src/components/dropdown/transforms/itemToAction.ts +14 -2
- package/src/components/dropdown/transforms/linkToLink.test.ts +19 -1
- package/src/components/dropdown/transforms/linkToLink.ts +13 -2
- package/src/components/dropdown/transforms/moveDropdownImport.test.ts +93 -0
- package/src/components/dropdown/transforms/moveDropdownImport.ts +13 -0
- package/src/components/dropdown/transforms/placementIdToMenu.test.ts +140 -0
- package/src/components/dropdown/transforms/placementIdToMenu.ts +96 -0
- package/src/components/dropdown/transforms/unsupportedProps.test.ts +145 -0
- package/src/components/dropdown/transforms/unsupportedProps.ts +12 -0
- package/src/components/dropdown/transforms/unsupportedPropsDivider.test.ts +143 -0
- package/src/components/dropdown/transforms/unsupportedPropsDivider.ts +26 -0
- package/src/components/dropdown/transforms/unsupportedPropsItem.test.ts +123 -0
- package/src/components/dropdown/transforms/unsupportedPropsItem.ts +12 -0
- package/src/components/dropdown/transforms/unsupportedPropsLink.test.ts +107 -0
- package/src/components/dropdown/transforms/unsupportedPropsLink.ts +12 -0
- 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/dropdown/transforms/wrapMenu.test.ts +153 -0
- package/src/components/dropdown/transforms/wrapMenu.ts +54 -0
- package/src/components/dropdown/transforms/wrapTrigger.test.ts +283 -0
- package/src/components/dropdown/transforms/wrapTrigger.ts +98 -0
- package/src/components/input/transforms/unsupportedProps.test.ts +14 -13
- package/src/components/shared/conditions/isChildOf.test.ts +89 -0
- package/src/components/shared/conditions/isChildOf.ts +43 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +36 -0
- package/src/index.ts +5 -18
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./wrapMenu"
|
|
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("wrapMenu transform", () => {
|
|
18
|
+
describe("wrapping children", () => {
|
|
19
|
+
it("wraps Dropdown children in DropdownMenu", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
export default function Test() {
|
|
24
|
+
return (
|
|
25
|
+
<Dropdown onClose={handleClose}>
|
|
26
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
27
|
+
</Dropdown>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
`.trim()
|
|
31
|
+
|
|
32
|
+
const result = applyTransform(input)
|
|
33
|
+
expect(result).toContain("<DropdownMenu>")
|
|
34
|
+
expect(result).toContain("</DropdownMenu>")
|
|
35
|
+
expect(result).toContain("Dropdown.Item")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("preserves children inside DropdownMenu", () => {
|
|
39
|
+
const input = `
|
|
40
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
41
|
+
|
|
42
|
+
export default function Test() {
|
|
43
|
+
return (
|
|
44
|
+
<Dropdown onClose={handleClose}>
|
|
45
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
46
|
+
<Dropdown.Item onSelect={fn2}>Delete</Dropdown.Item>
|
|
47
|
+
</Dropdown>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
`.trim()
|
|
51
|
+
|
|
52
|
+
const result = applyTransform(input)
|
|
53
|
+
const itemCount = (result?.match(/<Dropdown\.Item/g) || []).length
|
|
54
|
+
expect(itemCount).toBe(2)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("adds DropdownMenu import from tapestry", () => {
|
|
58
|
+
const input = `
|
|
59
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
60
|
+
|
|
61
|
+
export default function Test() {
|
|
62
|
+
return (
|
|
63
|
+
<Dropdown onClose={handleClose}>
|
|
64
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
65
|
+
</Dropdown>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
`.trim()
|
|
69
|
+
|
|
70
|
+
const result = applyTransform(input)
|
|
71
|
+
expect(result).toContain('from "@planningcenter/tapestry"')
|
|
72
|
+
expect(result).toContain("DropdownMenu")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("does not touch the tapestry-react import statement", () => {
|
|
76
|
+
const input = `
|
|
77
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
78
|
+
|
|
79
|
+
export default function Test() {
|
|
80
|
+
return (
|
|
81
|
+
<Dropdown onClose={handleClose}>
|
|
82
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
83
|
+
</Dropdown>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
`.trim()
|
|
87
|
+
|
|
88
|
+
const result = applyTransform(input)
|
|
89
|
+
expect(result).toContain(
|
|
90
|
+
'import { Dropdown } from "@planningcenter/tapestry-react"'
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe("no changes scenarios", () => {
|
|
96
|
+
it("returns null when DropdownMenu already exists", () => {
|
|
97
|
+
const input = `
|
|
98
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
99
|
+
|
|
100
|
+
export default function Test() {
|
|
101
|
+
return (
|
|
102
|
+
<Dropdown>
|
|
103
|
+
<DropdownTrigger><DropdownButton label="Actions" /></DropdownTrigger>
|
|
104
|
+
<DropdownMenu>
|
|
105
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
106
|
+
</DropdownMenu>
|
|
107
|
+
</Dropdown>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
`.trim()
|
|
111
|
+
|
|
112
|
+
const result = applyTransform(input)
|
|
113
|
+
expect(result).toBe(null)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it("wraps complex children in DropdownMenu and adds a TODO comment", () => {
|
|
117
|
+
const input = `
|
|
118
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
119
|
+
|
|
120
|
+
export default function Test({ children }) {
|
|
121
|
+
return <Dropdown onClose={handleClose}>{children}</Dropdown>
|
|
122
|
+
}
|
|
123
|
+
`.trim()
|
|
124
|
+
|
|
125
|
+
const result = applyTransform(input)
|
|
126
|
+
expect(result).toContain("<DropdownMenu>")
|
|
127
|
+
expect(result).toContain(
|
|
128
|
+
"TODO: tapestry-migration (children): complex children cannot be automatically converted to DropdownAction/DropdownLink"
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it("returns null when Dropdown is not imported from tapestry-react", () => {
|
|
133
|
+
const input = `
|
|
134
|
+
import { Dropdown } from "some-other-library"
|
|
135
|
+
|
|
136
|
+
export default function Test() {
|
|
137
|
+
return (
|
|
138
|
+
<Dropdown>
|
|
139
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
140
|
+
</Dropdown>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
`.trim()
|
|
144
|
+
|
|
145
|
+
const result = applyTransform(input)
|
|
146
|
+
expect(result).toBe(null)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it("returns null for empty file", () => {
|
|
150
|
+
expect(applyTransform("")).toBe(null)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
})
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { JSXElement, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addComment } from "../../shared/actions/addComment"
|
|
4
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
5
|
+
import { addImport } from "../../shared/transformFactories/helpers/manageImports"
|
|
6
|
+
|
|
7
|
+
const transform: Transform = attributeTransformFactory({
|
|
8
|
+
targetComponent: "Dropdown",
|
|
9
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
10
|
+
transform: (element, { j, source }) => {
|
|
11
|
+
const children = element.children ?? []
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
children.some(
|
|
15
|
+
(child): child is JSXElement =>
|
|
16
|
+
child.type === "JSXElement" &&
|
|
17
|
+
child.openingElement.name.type === "JSXIdentifier" &&
|
|
18
|
+
child.openingElement.name.name === "DropdownMenu"
|
|
19
|
+
)
|
|
20
|
+
) {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!children.some((child) => child.type === "JSXElement")) {
|
|
25
|
+
addComment({
|
|
26
|
+
element,
|
|
27
|
+
j,
|
|
28
|
+
scope: "children",
|
|
29
|
+
source,
|
|
30
|
+
text: "complex children cannot be automatically converted to DropdownAction/DropdownLink — verify contents",
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
element.children = [
|
|
35
|
+
j.jsxElement(
|
|
36
|
+
j.jsxOpeningElement(j.jsxIdentifier("DropdownMenu"), [], false),
|
|
37
|
+
j.jsxClosingElement(j.jsxIdentifier("DropdownMenu")),
|
|
38
|
+
children
|
|
39
|
+
),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
addImport({
|
|
43
|
+
component: "DropdownMenu",
|
|
44
|
+
fromPackage: "@planningcenter/tapestry-react",
|
|
45
|
+
j,
|
|
46
|
+
pkg: "@planningcenter/tapestry",
|
|
47
|
+
source,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return true
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export default transform
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./wrapTrigger"
|
|
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("wrapTrigger transform", () => {
|
|
18
|
+
describe("title prop", () => {
|
|
19
|
+
it("converts title string to DropdownTrigger + DropdownButton", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
export default function Test() {
|
|
24
|
+
return (
|
|
25
|
+
<Dropdown title="Actions">
|
|
26
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
27
|
+
</Dropdown>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
`.trim()
|
|
31
|
+
|
|
32
|
+
const result = applyTransform(input)
|
|
33
|
+
expect(result).toContain("<DropdownTrigger>")
|
|
34
|
+
expect(result).toContain("</DropdownTrigger>")
|
|
35
|
+
expect(result).toContain('<DropdownButton label="Actions"')
|
|
36
|
+
expect(result).not.toContain('title="Actions"')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it("converts dynamic title expression to DropdownButton label", () => {
|
|
40
|
+
const input = `
|
|
41
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
42
|
+
|
|
43
|
+
export default function Test() {
|
|
44
|
+
return (
|
|
45
|
+
<Dropdown title={menuTitle}>
|
|
46
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
47
|
+
</Dropdown>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
`.trim()
|
|
51
|
+
|
|
52
|
+
const result = applyTransform(input)
|
|
53
|
+
expect(result).toContain("DropdownButton")
|
|
54
|
+
expect(result).toContain("label={menuTitle}")
|
|
55
|
+
expect(result).not.toContain("title=")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it("preserves existing Dropdown children after trigger insertion", () => {
|
|
59
|
+
const input = `
|
|
60
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
61
|
+
|
|
62
|
+
export default function Test() {
|
|
63
|
+
return (
|
|
64
|
+
<Dropdown title="Actions">
|
|
65
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
66
|
+
<Dropdown.Item onSelect={fn2}>Delete</Dropdown.Item>
|
|
67
|
+
</Dropdown>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
`.trim()
|
|
71
|
+
|
|
72
|
+
const result = applyTransform(input)
|
|
73
|
+
expect(result).toContain("<DropdownTrigger>")
|
|
74
|
+
expect(result).toContain("Dropdown.Item")
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe("triggerElement prop", () => {
|
|
79
|
+
it("wraps triggerElement JSX value in DropdownTrigger", () => {
|
|
80
|
+
const input = `
|
|
81
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
82
|
+
|
|
83
|
+
export default function Test() {
|
|
84
|
+
return (
|
|
85
|
+
<Dropdown triggerElement={<DropdownButton label="Actions" />}>
|
|
86
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
87
|
+
</Dropdown>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
`.trim()
|
|
91
|
+
|
|
92
|
+
const result = applyTransform(input)
|
|
93
|
+
expect(result).toContain("<DropdownTrigger>")
|
|
94
|
+
expect(result).toContain("</DropdownTrigger>")
|
|
95
|
+
expect(result).toContain("DropdownButton")
|
|
96
|
+
expect(result).not.toContain("triggerElement=")
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("triggerElement takes precedence over title", () => {
|
|
100
|
+
const input = `
|
|
101
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
102
|
+
|
|
103
|
+
export default function Test() {
|
|
104
|
+
return (
|
|
105
|
+
<Dropdown title="Ignored" triggerElement={<DropdownButton label="Used" />}>
|
|
106
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
107
|
+
</Dropdown>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
`.trim()
|
|
111
|
+
|
|
112
|
+
const result = applyTransform(input)
|
|
113
|
+
expect(result).toContain('label="Used"')
|
|
114
|
+
expect(result).not.toContain("title=")
|
|
115
|
+
expect(result).not.toContain("triggerElement=")
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe("attribute preservation", () => {
|
|
120
|
+
it("preserves other Dropdown props", () => {
|
|
121
|
+
const input = `
|
|
122
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
123
|
+
|
|
124
|
+
export default function Test() {
|
|
125
|
+
return (
|
|
126
|
+
<Dropdown title="Actions" onClose={handleClose} placement="bottom-end">
|
|
127
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
128
|
+
</Dropdown>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
`.trim()
|
|
132
|
+
|
|
133
|
+
const result = applyTransform(input)
|
|
134
|
+
expect(result).toContain("onClose={handleClose}")
|
|
135
|
+
expect(result).toContain('placement="bottom-end"')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it("does not touch the tapestry-react import statement", () => {
|
|
139
|
+
const input = `
|
|
140
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
141
|
+
|
|
142
|
+
export default function Test() {
|
|
143
|
+
return (
|
|
144
|
+
<Dropdown title="Actions">
|
|
145
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
146
|
+
</Dropdown>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
`.trim()
|
|
150
|
+
|
|
151
|
+
const result = applyTransform(input)
|
|
152
|
+
expect(result).toContain(
|
|
153
|
+
'import { Dropdown } from "@planningcenter/tapestry-react"'
|
|
154
|
+
)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("adds DropdownTrigger and DropdownButton imports from tapestry when title is used", () => {
|
|
158
|
+
const input = `
|
|
159
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
160
|
+
|
|
161
|
+
export default function Test() {
|
|
162
|
+
return (
|
|
163
|
+
<Dropdown title="Actions">
|
|
164
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
165
|
+
</Dropdown>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
`.trim()
|
|
169
|
+
|
|
170
|
+
const result = applyTransform(input)
|
|
171
|
+
expect(result).toContain('from "@planningcenter/tapestry"')
|
|
172
|
+
expect(result).toContain("DropdownTrigger")
|
|
173
|
+
expect(result).toContain("DropdownButton")
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it("adds only DropdownTrigger import when triggerElement is provided", () => {
|
|
177
|
+
const input = `
|
|
178
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
179
|
+
|
|
180
|
+
export default function Test() {
|
|
181
|
+
return (
|
|
182
|
+
<Dropdown triggerElement={<MyButton />}>
|
|
183
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
184
|
+
</Dropdown>
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
`.trim()
|
|
188
|
+
|
|
189
|
+
const result = applyTransform(input)
|
|
190
|
+
const tapestryImport =
|
|
191
|
+
result?.match(/import {[^}]+} from "@planningcenter\/tapestry"/)?.[0] ??
|
|
192
|
+
""
|
|
193
|
+
expect(tapestryImport).toContain("DropdownTrigger")
|
|
194
|
+
expect(tapestryImport).not.toContain("DropdownButton")
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
describe("multiple dropdowns", () => {
|
|
199
|
+
it("transforms all Dropdown elements in the file", () => {
|
|
200
|
+
const input = `
|
|
201
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
202
|
+
|
|
203
|
+
export default function Test() {
|
|
204
|
+
return (
|
|
205
|
+
<div>
|
|
206
|
+
<Dropdown title="First">
|
|
207
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
208
|
+
</Dropdown>
|
|
209
|
+
<Dropdown title="Second">
|
|
210
|
+
<Dropdown.Item onSelect={fn2}>Delete</Dropdown.Item>
|
|
211
|
+
</Dropdown>
|
|
212
|
+
</div>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
`.trim()
|
|
216
|
+
|
|
217
|
+
const result = applyTransform(input)
|
|
218
|
+
const count = (result?.match(/<DropdownTrigger>/g) || []).length
|
|
219
|
+
expect(count).toBe(2)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe("no changes scenarios", () => {
|
|
224
|
+
it("returns null when Dropdown has no title or triggerElement", () => {
|
|
225
|
+
const input = `
|
|
226
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
227
|
+
|
|
228
|
+
export default function Test() {
|
|
229
|
+
return (
|
|
230
|
+
<Dropdown onClose={handleClose}>
|
|
231
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
232
|
+
</Dropdown>
|
|
233
|
+
)
|
|
234
|
+
}
|
|
235
|
+
`.trim()
|
|
236
|
+
|
|
237
|
+
const result = applyTransform(input)
|
|
238
|
+
expect(result).toBe(null)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it("returns null when Dropdown is not imported from tapestry-react", () => {
|
|
242
|
+
const input = `
|
|
243
|
+
import { Dropdown } from "some-other-library"
|
|
244
|
+
|
|
245
|
+
export default function Test() {
|
|
246
|
+
return (
|
|
247
|
+
<Dropdown title="Actions">
|
|
248
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
249
|
+
</Dropdown>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
`.trim()
|
|
253
|
+
|
|
254
|
+
const result = applyTransform(input)
|
|
255
|
+
expect(result).toBe(null)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it("returns null for empty file", () => {
|
|
259
|
+
expect(applyTransform("")).toBe(null)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("wraps non-JSX triggerElement expression as a child with a TODO comment", () => {
|
|
263
|
+
const input = `
|
|
264
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
265
|
+
|
|
266
|
+
export default function Test() {
|
|
267
|
+
return (
|
|
268
|
+
<Dropdown triggerElement={myTrigger}>
|
|
269
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
270
|
+
</Dropdown>
|
|
271
|
+
)
|
|
272
|
+
}
|
|
273
|
+
`.trim()
|
|
274
|
+
|
|
275
|
+
const result = applyTransform(input)
|
|
276
|
+
expect(result).toContain("<DropdownTrigger>")
|
|
277
|
+
expect(result).toContain("{myTrigger}")
|
|
278
|
+
expect(result).not.toContain("triggerElement=")
|
|
279
|
+
expect(result).toContain("TODO")
|
|
280
|
+
expect(result).toContain("triggerElement")
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
JSXAttribute,
|
|
3
|
+
JSXElement,
|
|
4
|
+
JSXExpressionContainer,
|
|
5
|
+
Transform,
|
|
6
|
+
} from "jscodeshift"
|
|
7
|
+
|
|
8
|
+
import { addComment } from "../../shared/actions/addComment"
|
|
9
|
+
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
10
|
+
import { removeAttribute } from "../../shared/actions/removeAttribute"
|
|
11
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
12
|
+
import { addImport } from "../../shared/transformFactories/helpers/manageImports"
|
|
13
|
+
|
|
14
|
+
const transform: Transform = attributeTransformFactory({
|
|
15
|
+
targetComponent: "Dropdown",
|
|
16
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
17
|
+
transform: (element, { j, source }) => {
|
|
18
|
+
const titleAttr = getAttribute({ element, name: "title" })
|
|
19
|
+
const triggerElementAttr = getAttribute({ element, name: "triggerElement" })
|
|
20
|
+
|
|
21
|
+
if (!titleAttr && !triggerElementAttr) return false
|
|
22
|
+
|
|
23
|
+
let triggerChild: JSXElement | JSXExpressionContainer | null = null
|
|
24
|
+
|
|
25
|
+
if (triggerElementAttr) {
|
|
26
|
+
const attrValue = triggerElementAttr.value
|
|
27
|
+
if (
|
|
28
|
+
attrValue?.type === "JSXExpressionContainer" &&
|
|
29
|
+
attrValue.expression.type !== "JSXEmptyExpression"
|
|
30
|
+
) {
|
|
31
|
+
if (attrValue.expression.type === "JSXElement") {
|
|
32
|
+
triggerChild = attrValue.expression
|
|
33
|
+
} else {
|
|
34
|
+
triggerChild = j.jsxExpressionContainer(attrValue.expression)
|
|
35
|
+
addComment({
|
|
36
|
+
element,
|
|
37
|
+
j,
|
|
38
|
+
scope: "triggerElement",
|
|
39
|
+
source,
|
|
40
|
+
text: "triggerElement was not a JSX element — verify this renders correctly inside DropdownTrigger",
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!triggerChild && titleAttr) {
|
|
47
|
+
const labelValue = titleAttr.value
|
|
48
|
+
if (!labelValue) return false
|
|
49
|
+
|
|
50
|
+
const labelAttr: JSXAttribute = j.jsxAttribute(
|
|
51
|
+
j.jsxIdentifier("label"),
|
|
52
|
+
labelValue
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
triggerChild = j.jsxElement(
|
|
56
|
+
j.jsxOpeningElement(
|
|
57
|
+
j.jsxIdentifier("DropdownButton"),
|
|
58
|
+
[labelAttr],
|
|
59
|
+
true
|
|
60
|
+
),
|
|
61
|
+
null,
|
|
62
|
+
[]
|
|
63
|
+
)
|
|
64
|
+
addImport({
|
|
65
|
+
component: "DropdownButton",
|
|
66
|
+
fromPackage: "@planningcenter/tapestry-react",
|
|
67
|
+
j,
|
|
68
|
+
pkg: "@planningcenter/tapestry",
|
|
69
|
+
source,
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!triggerChild) return false
|
|
74
|
+
|
|
75
|
+
const triggerWrapper = j.jsxElement(
|
|
76
|
+
j.jsxOpeningElement(j.jsxIdentifier("DropdownTrigger"), [], false),
|
|
77
|
+
j.jsxClosingElement(j.jsxIdentifier("DropdownTrigger")),
|
|
78
|
+
[triggerChild]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
removeAttribute("title", { element, j, source })
|
|
82
|
+
removeAttribute("triggerElement", { element, j, source })
|
|
83
|
+
|
|
84
|
+
element.children = [triggerWrapper, ...(element.children ?? [])]
|
|
85
|
+
|
|
86
|
+
addImport({
|
|
87
|
+
component: "DropdownTrigger",
|
|
88
|
+
fromPackage: "@planningcenter/tapestry-react",
|
|
89
|
+
j,
|
|
90
|
+
pkg: "@planningcenter/tapestry",
|
|
91
|
+
source,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return true
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
export default transform
|
|
@@ -156,19 +156,6 @@ function Test() {
|
|
|
156
156
|
expect(result).toContain("TODO: tapestry-migration (hover)")
|
|
157
157
|
})
|
|
158
158
|
|
|
159
|
-
it("should flag autoFocus", () => {
|
|
160
|
-
const input = `
|
|
161
|
-
import { Input } from "@planningcenter/tapestry-react"
|
|
162
|
-
|
|
163
|
-
function Test() {
|
|
164
|
-
return <Input autoFocus label="Name" />
|
|
165
|
-
}
|
|
166
|
-
`.trim()
|
|
167
|
-
|
|
168
|
-
const result = applyTransform(input)
|
|
169
|
-
expect(result).toContain("TODO: tapestry-migration (autoFocus)")
|
|
170
|
-
})
|
|
171
|
-
|
|
172
159
|
it("should flag readOnlyBackgroundColor", () => {
|
|
173
160
|
const input = `
|
|
174
161
|
import { Input } from "@planningcenter/tapestry-react"
|
|
@@ -231,6 +218,20 @@ function Test() {
|
|
|
231
218
|
expect(result).not.toContain("TODO: tapestry-migration")
|
|
232
219
|
})
|
|
233
220
|
|
|
221
|
+
it("should not flag autoFocus", () => {
|
|
222
|
+
const input = `
|
|
223
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
224
|
+
|
|
225
|
+
function Test() {
|
|
226
|
+
return <Input autoFocus label="Name" />
|
|
227
|
+
}
|
|
228
|
+
`.trim()
|
|
229
|
+
|
|
230
|
+
const result = applyTransform(input)
|
|
231
|
+
expect(result).not.toContain("TODO: tapestry-migration")
|
|
232
|
+
expect(result).toContain("autoFocus")
|
|
233
|
+
})
|
|
234
|
+
|
|
234
235
|
it("should not flag standard HTML attributes", () => {
|
|
235
236
|
const input = `
|
|
236
237
|
import { Input } from "@planningcenter/tapestry-react"
|