@planningcenter/tapestry-migration-cli 2.4.0-rc.8 → 2.4.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 +100 -6
- package/dist/tapestry-react-shim.cjs +1 -1
- package/package.json +2 -2
- package/src/components/button/index.ts +3 -3
- package/src/components/button/transforms/innerRefToRef.test.ts +170 -0
- package/src/components/button/transforms/innerRefToRef.ts +14 -0
- package/src/components/button/transforms/unsupportedProps.ts +8 -31
- package/src/components/link/index.ts +14 -4
- package/src/components/link/transforms/auditSpreadProps.test.ts +351 -0
- package/src/components/link/transforms/auditSpreadProps.ts +24 -0
- package/src/components/link/transforms/innerRefToRef.test.ts +170 -0
- package/src/components/link/transforms/innerRefToRef.ts +14 -0
- package/src/components/link/transforms/moveLinkImport.test.ts +295 -0
- package/src/components/{button/transforms/linkToButton.ts → link/transforms/moveLinkImport.ts} +4 -5
- package/src/components/link/transforms/{inlineMemberToKind.test.ts → removeInlineMember.test.ts} +13 -28
- package/src/components/link/transforms/removeInlineMember.ts +11 -0
- package/src/components/link/transforms/{inlinePropToKind.test.ts → removeInlineProp.test.ts} +12 -29
- package/src/components/link/transforms/{inlinePropToKind.ts → removeInlineProp.ts} +0 -9
- package/src/components/link/transforms/tooltipToWrapper.test.ts +392 -0
- package/src/components/link/transforms/tooltipToWrapper.ts +35 -0
- package/src/components/link/transforms/unsupportedProps.test.ts +265 -0
- package/src/components/link/transforms/unsupportedProps.ts +58 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +35 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +1 -0
- package/src/index.ts +18 -7
- package/src/components/button/transforms/linkToButton.test.ts +0 -426
- package/src/components/link/transforms/inlineMemberToKind.ts +0 -49
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { JSXAttribute, Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { addCommentToUnsupportedProps } from "../../shared/actions/addCommentToUnsupportedProps"
|
|
4
|
+
import { SUPPORTED_PROPS_BASE } from "../../shared/helpers/unsupportedPropsHelpers"
|
|
5
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
6
|
+
|
|
7
|
+
// Note: 'target' and 'rel' are NOT included because they are handled by targetBlankToExternal transform
|
|
8
|
+
const LINK_SPECIFIC_PROPS = [
|
|
9
|
+
"download",
|
|
10
|
+
"external",
|
|
11
|
+
"href",
|
|
12
|
+
"hrefLang",
|
|
13
|
+
"media",
|
|
14
|
+
"ping",
|
|
15
|
+
"referrerPolicy",
|
|
16
|
+
"type",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const SUPPORTED_PROPS = [...SUPPORTED_PROPS_BASE, ...LINK_SPECIFIC_PROPS]
|
|
20
|
+
|
|
21
|
+
const transform: Transform = attributeTransformFactory({
|
|
22
|
+
targetComponent: "Link",
|
|
23
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
24
|
+
transform: (element, { j }) => {
|
|
25
|
+
const UNSUPPORTED_PROPS = (element.openingElement.attributes || [])
|
|
26
|
+
.filter(
|
|
27
|
+
(attr) =>
|
|
28
|
+
attr.type === "JSXAttribute" &&
|
|
29
|
+
!SUPPORTED_PROPS.includes(attr.name.name as string) &&
|
|
30
|
+
!(attr.name.name as string).startsWith("aria-") &&
|
|
31
|
+
!(attr.name.name as string).startsWith("data-")
|
|
32
|
+
)
|
|
33
|
+
.map((attr) => (attr as JSXAttribute).name.name as string)
|
|
34
|
+
|
|
35
|
+
return addCommentToUnsupportedProps({
|
|
36
|
+
element,
|
|
37
|
+
j,
|
|
38
|
+
messageSuffix: (prop) => {
|
|
39
|
+
if (prop === "css") {
|
|
40
|
+
return "\n * Use 'className' prop with CSS classes instead of the css prop.\n"
|
|
41
|
+
}
|
|
42
|
+
if (prop === "disabled") {
|
|
43
|
+
return "\n * Links do not support the disabled prop. Consider using a button, hiding the link, or using CSS to style it as disabled.\n"
|
|
44
|
+
}
|
|
45
|
+
if (prop === "mediaQueries") {
|
|
46
|
+
return "\n * It is recommended to use CSS media queries in a class that you apply to the component.\n"
|
|
47
|
+
}
|
|
48
|
+
if (prop === "hover" || prop === "focus" || prop === "active") {
|
|
49
|
+
return "\n * State-based styles (hover, focus, active) should be handled with CSS class selectors.\n"
|
|
50
|
+
}
|
|
51
|
+
return ""
|
|
52
|
+
},
|
|
53
|
+
props: UNSUPPORTED_PROPS,
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export default transform
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { stylePropNames } from "../../../../dist/tapestry-react-shim.cjs"
|
|
2
|
+
|
|
3
|
+
export const STYLE_PROP_NAMES_WITHOUT_CSS = stylePropNames.filter(
|
|
4
|
+
(prop: string) => prop !== "css"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
export const COMMON_PROPS = [
|
|
8
|
+
"className",
|
|
9
|
+
"id",
|
|
10
|
+
"key",
|
|
11
|
+
"kind",
|
|
12
|
+
"label",
|
|
13
|
+
"onBlur",
|
|
14
|
+
"onFocus",
|
|
15
|
+
"onClick",
|
|
16
|
+
"onKeyDown",
|
|
17
|
+
"onKeyUp",
|
|
18
|
+
"onMouseDown",
|
|
19
|
+
"onMouseOut",
|
|
20
|
+
"onMouseOver",
|
|
21
|
+
"onMouseUp",
|
|
22
|
+
"prefix",
|
|
23
|
+
"ref",
|
|
24
|
+
"role",
|
|
25
|
+
"size",
|
|
26
|
+
"style",
|
|
27
|
+
"suffix",
|
|
28
|
+
"tabIndex",
|
|
29
|
+
"title",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export const SUPPORTED_PROPS_BASE = [
|
|
33
|
+
...STYLE_PROP_NAMES_WITHOUT_CSS,
|
|
34
|
+
...COMMON_PROPS,
|
|
35
|
+
]
|
package/src/index.ts
CHANGED
|
@@ -15,9 +15,15 @@ const COMPONENTS = ["button", "link"]
|
|
|
15
15
|
program
|
|
16
16
|
.command("run")
|
|
17
17
|
.description("Run a migration of a component from Tapestry React to Tapestry")
|
|
18
|
-
.argument(
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
.argument(
|
|
19
|
+
"<component-name>",
|
|
20
|
+
"The name of the component to migrate (button, link)"
|
|
21
|
+
)
|
|
22
|
+
.requiredOption(
|
|
23
|
+
"-p, --path <path>",
|
|
24
|
+
"REQUIRED: The path to the folder/file to migrate"
|
|
25
|
+
)
|
|
26
|
+
.option("-f, --fix", "Write the changes to files (default: dry run)")
|
|
21
27
|
.option("-v, --verbose", "Verbose output")
|
|
22
28
|
.option(
|
|
23
29
|
"-j, --js-theme <path>",
|
|
@@ -29,16 +35,21 @@ program
|
|
|
29
35
|
"MIGRATION_REPORT.md"
|
|
30
36
|
)
|
|
31
37
|
.action((componentName, options) => {
|
|
32
|
-
console.log("Hello from Tapestry Migration CLI! 🎨")
|
|
33
|
-
console.log(`Component: ${componentName}`)
|
|
34
38
|
const key = componentName.toLowerCase()
|
|
35
39
|
|
|
36
40
|
if (COMPONENTS.includes(key)) {
|
|
41
|
+
console.log(`🎨 Migrating ${componentName} components...`)
|
|
42
|
+
console.log(`📁 Target: ${options.path}`)
|
|
43
|
+
console.log(
|
|
44
|
+
`🔧 Mode: ${options.fix ? "Apply changes" : "Dry run (preview only)"}`
|
|
45
|
+
)
|
|
37
46
|
runTransforms(key, options)
|
|
38
47
|
} else {
|
|
39
|
-
console.
|
|
40
|
-
|
|
48
|
+
console.error(`❌ Invalid component: "${componentName}"`)
|
|
49
|
+
console.error(
|
|
50
|
+
`✅ Available components: ${COMPONENTS.map((c) => c.charAt(0).toUpperCase() + c.slice(1)).join(", ")}`
|
|
41
51
|
)
|
|
52
|
+
process.exit(1)
|
|
42
53
|
}
|
|
43
54
|
})
|
|
44
55
|
|
|
@@ -1,426 +0,0 @@
|
|
|
1
|
-
import jscodeshift from "jscodeshift"
|
|
2
|
-
import { describe, expect, it } from "vitest"
|
|
3
|
-
|
|
4
|
-
import transform from "./linkToButton"
|
|
5
|
-
|
|
6
|
-
const j = jscodeshift.withParser("tsx")
|
|
7
|
-
|
|
8
|
-
// Helper to run transform and get result
|
|
9
|
-
function runTransform(input: string): string | null {
|
|
10
|
-
const fileInfo = { path: "test.tsx", source: input }
|
|
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("linkToButton transform", () => {
|
|
22
|
-
describe("basic transformation", () => {
|
|
23
|
-
it("should transform Link with onClick to Button", () => {
|
|
24
|
-
const input = `
|
|
25
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
26
|
-
|
|
27
|
-
function Component() {
|
|
28
|
-
return <Link onClick={handleClick}>Click me</Link>
|
|
29
|
-
}
|
|
30
|
-
`
|
|
31
|
-
|
|
32
|
-
const result = runTransform(input)
|
|
33
|
-
|
|
34
|
-
expect(result).toContain(
|
|
35
|
-
"<Button onClick={handleClick}>Click me</Button>"
|
|
36
|
-
)
|
|
37
|
-
expect(result).toContain(
|
|
38
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
39
|
-
)
|
|
40
|
-
expect(result).not.toContain("Link")
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it("should preserve Link without onClick", () => {
|
|
44
|
-
const input = `
|
|
45
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
46
|
-
|
|
47
|
-
function Component() {
|
|
48
|
-
return <Link href="/test">Go to test</Link>
|
|
49
|
-
}
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
const result = runTransform(input)
|
|
53
|
-
|
|
54
|
-
expect(result).toBeNull()
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it("should handle mixed Link usage", () => {
|
|
58
|
-
const input = `
|
|
59
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
60
|
-
|
|
61
|
-
function Component() {
|
|
62
|
-
return (
|
|
63
|
-
<div>
|
|
64
|
-
<Link onClick={handleClick}>Click me</Link>
|
|
65
|
-
<Link href="/home">Home</Link>
|
|
66
|
-
<Link onClick={handleOther}>Other action</Link>
|
|
67
|
-
</div>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
const result = runTransform(input)
|
|
73
|
-
|
|
74
|
-
expect(result).toContain(
|
|
75
|
-
"<Button onClick={handleClick}>Click me</Button>"
|
|
76
|
-
)
|
|
77
|
-
expect(result).toContain('<Link href="/home">Home</Link>')
|
|
78
|
-
expect(result).toContain(
|
|
79
|
-
"<Button onClick={handleOther}>Other action</Button>"
|
|
80
|
-
)
|
|
81
|
-
expect(result).toContain(
|
|
82
|
-
'import { Link, Button } from "@planningcenter/tapestry-react"'
|
|
83
|
-
)
|
|
84
|
-
})
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
describe("import management", () => {
|
|
88
|
-
it("should remove Link import when no longer used", () => {
|
|
89
|
-
const input = `
|
|
90
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
91
|
-
|
|
92
|
-
function Component() {
|
|
93
|
-
return <Link onClick={handleClick}>Button</Link>
|
|
94
|
-
}
|
|
95
|
-
`
|
|
96
|
-
|
|
97
|
-
const result = runTransform(input)
|
|
98
|
-
|
|
99
|
-
expect(result).toContain(
|
|
100
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
101
|
-
)
|
|
102
|
-
expect(result).not.toContain("Link")
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
it("should keep Link import when still used", () => {
|
|
106
|
-
const input = `
|
|
107
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
108
|
-
|
|
109
|
-
function Component() {
|
|
110
|
-
return (
|
|
111
|
-
<div>
|
|
112
|
-
<Link onClick={handleClick}>Button</Link>
|
|
113
|
-
<Link href="/test">Link</Link>
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
`
|
|
118
|
-
|
|
119
|
-
const result = runTransform(input)
|
|
120
|
-
|
|
121
|
-
expect(result).toContain(
|
|
122
|
-
'import { Link, Button } from "@planningcenter/tapestry-react"'
|
|
123
|
-
)
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it("should add Button to existing imports", () => {
|
|
127
|
-
const input = `
|
|
128
|
-
import { Link, Input } from "@planningcenter/tapestry-react"
|
|
129
|
-
|
|
130
|
-
function Component() {
|
|
131
|
-
return (
|
|
132
|
-
<div>
|
|
133
|
-
<Input />
|
|
134
|
-
<Link onClick={handleClick}>Button</Link>
|
|
135
|
-
</div>
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
`
|
|
139
|
-
|
|
140
|
-
const result = runTransform(input)
|
|
141
|
-
|
|
142
|
-
expect(result).toContain(
|
|
143
|
-
'import { Input, Button } from "@planningcenter/tapestry-react"'
|
|
144
|
-
)
|
|
145
|
-
expect(result).not.toContain("Link")
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
it("should handle existing Button import", () => {
|
|
149
|
-
const input = `
|
|
150
|
-
import { Link, Button } from "@planningcenter/tapestry-react"
|
|
151
|
-
|
|
152
|
-
function Component() {
|
|
153
|
-
return (
|
|
154
|
-
<div>
|
|
155
|
-
<Button type="submit">Submit</Button>
|
|
156
|
-
<Link onClick={handleClick}>Clickable</Link>
|
|
157
|
-
</div>
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
const result = runTransform(input)
|
|
163
|
-
|
|
164
|
-
expect(result).toContain('<Button type="submit">Submit</Button>')
|
|
165
|
-
expect(result).toContain(
|
|
166
|
-
"<Button onClick={handleClick}>Clickable</Button>"
|
|
167
|
-
)
|
|
168
|
-
expect(result).toContain(
|
|
169
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
170
|
-
)
|
|
171
|
-
expect(result).not.toContain("Link")
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
describe("component attributes", () => {
|
|
176
|
-
it("should preserve all attributes", () => {
|
|
177
|
-
const input = `
|
|
178
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
179
|
-
|
|
180
|
-
function Component() {
|
|
181
|
-
return (
|
|
182
|
-
<Link
|
|
183
|
-
onClick={handleClick}
|
|
184
|
-
className="link-button"
|
|
185
|
-
disabled
|
|
186
|
-
data-testid="clickable-link"
|
|
187
|
-
role="button"
|
|
188
|
-
>
|
|
189
|
-
Click me
|
|
190
|
-
</Link>
|
|
191
|
-
)
|
|
192
|
-
}
|
|
193
|
-
`
|
|
194
|
-
|
|
195
|
-
const result = runTransform(input)
|
|
196
|
-
|
|
197
|
-
expect(result).toContain("onClick={handleClick}")
|
|
198
|
-
expect(result).toContain('className="link-button"')
|
|
199
|
-
expect(result).toContain("disabled")
|
|
200
|
-
expect(result).toContain('data-testid="clickable-link"')
|
|
201
|
-
expect(result).toContain('role="button"')
|
|
202
|
-
expect(result).toContain("<Button")
|
|
203
|
-
expect(result).toContain("</Button>")
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
it("should handle self-closing tags", () => {
|
|
207
|
-
const input = `
|
|
208
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
209
|
-
|
|
210
|
-
function Component() {
|
|
211
|
-
return <Link onClick={handleClick} />
|
|
212
|
-
}
|
|
213
|
-
`
|
|
214
|
-
|
|
215
|
-
const result = runTransform(input)
|
|
216
|
-
|
|
217
|
-
expect(result).toContain("<Button onClick={handleClick} />")
|
|
218
|
-
expect(result).not.toContain("Link")
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it("should handle complex onClick expressions", () => {
|
|
222
|
-
const input = `
|
|
223
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
224
|
-
|
|
225
|
-
function Component() {
|
|
226
|
-
return <Link onClick={() => handleAction(id, 'delete')}>Delete</Link>
|
|
227
|
-
}
|
|
228
|
-
`
|
|
229
|
-
|
|
230
|
-
const result = runTransform(input)
|
|
231
|
-
|
|
232
|
-
expect(result).toContain("onClick={() => handleAction(id, 'delete')}")
|
|
233
|
-
expect(result).toContain("<Button")
|
|
234
|
-
expect(result).toContain("Delete</Button>")
|
|
235
|
-
})
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
describe("edge cases", () => {
|
|
239
|
-
it("should return null when no Link imported from tapestry-react", () => {
|
|
240
|
-
const input = `
|
|
241
|
-
import { Link } from "react-router-dom"
|
|
242
|
-
|
|
243
|
-
function Component() {
|
|
244
|
-
return <Link onClick={handleClick}>Button</Link>
|
|
245
|
-
}
|
|
246
|
-
`
|
|
247
|
-
|
|
248
|
-
const result = runTransform(input)
|
|
249
|
-
|
|
250
|
-
expect(result).toBeNull()
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it("should return null when no Link with onClick found", () => {
|
|
254
|
-
const input = `
|
|
255
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
256
|
-
|
|
257
|
-
function Component() {
|
|
258
|
-
return <Link href="/test">Go to test</Link>
|
|
259
|
-
}
|
|
260
|
-
`
|
|
261
|
-
|
|
262
|
-
const result = runTransform(input)
|
|
263
|
-
|
|
264
|
-
expect(result).toBeNull()
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
it("should handle aliased Link import", () => {
|
|
268
|
-
const input = `
|
|
269
|
-
import { Link as TapestryLink } from "@planningcenter/tapestry-react"
|
|
270
|
-
|
|
271
|
-
function Component() {
|
|
272
|
-
return <TapestryLink onClick={handleClick}>Button</TapestryLink>
|
|
273
|
-
}
|
|
274
|
-
`
|
|
275
|
-
|
|
276
|
-
const result = runTransform(input)
|
|
277
|
-
|
|
278
|
-
expect(result).toContain("<Button onClick={handleClick}>Button</Button>")
|
|
279
|
-
expect(result).toContain(
|
|
280
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
281
|
-
)
|
|
282
|
-
expect(result).not.toContain("TapestryLink")
|
|
283
|
-
expect(result).not.toContain("Link")
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
it("should handle nested JSX structures", () => {
|
|
287
|
-
const input = `
|
|
288
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
289
|
-
|
|
290
|
-
function Component() {
|
|
291
|
-
return (
|
|
292
|
-
<div className="container">
|
|
293
|
-
<Link onClick={handleDelete}>
|
|
294
|
-
<span className="icon">🗑️</span>
|
|
295
|
-
<span>Delete</span>
|
|
296
|
-
</Link>
|
|
297
|
-
</div>
|
|
298
|
-
)
|
|
299
|
-
}
|
|
300
|
-
`
|
|
301
|
-
|
|
302
|
-
const result = runTransform(input)
|
|
303
|
-
|
|
304
|
-
expect(result).toContain("<Button onClick={handleDelete}>")
|
|
305
|
-
expect(result).toContain('<span className="icon">🗑️</span>')
|
|
306
|
-
expect(result).toContain("<span>Delete</span>")
|
|
307
|
-
expect(result).toContain("</Button>")
|
|
308
|
-
expect(result).not.toContain("Link")
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
it("should handle multiple onClick links in same component", () => {
|
|
312
|
-
const input = `
|
|
313
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
314
|
-
|
|
315
|
-
function Actions() {
|
|
316
|
-
return (
|
|
317
|
-
<div>
|
|
318
|
-
<Link onClick={handleSave}>Save</Link>
|
|
319
|
-
<Link onClick={handleCancel}>Cancel</Link>
|
|
320
|
-
<Link onClick={handleDelete}>Delete</Link>
|
|
321
|
-
</div>
|
|
322
|
-
)
|
|
323
|
-
}
|
|
324
|
-
`
|
|
325
|
-
|
|
326
|
-
const result = runTransform(input)
|
|
327
|
-
|
|
328
|
-
expect(result).toContain("<Button onClick={handleSave}>Save</Button>")
|
|
329
|
-
expect(result).toContain("<Button onClick={handleCancel}>Cancel</Button>")
|
|
330
|
-
expect(result).toContain("<Button onClick={handleDelete}>Delete</Button>")
|
|
331
|
-
expect(result).toContain(
|
|
332
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
333
|
-
)
|
|
334
|
-
expect(result).not.toContain("Link")
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
it("should handle Link with both href and onClick (should transform)", () => {
|
|
338
|
-
const input = `
|
|
339
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
340
|
-
|
|
341
|
-
function Component() {
|
|
342
|
-
return <Link href="/test" onClick={handleClick}>Link with both</Link>
|
|
343
|
-
}
|
|
344
|
-
`
|
|
345
|
-
|
|
346
|
-
const result = runTransform(input)
|
|
347
|
-
|
|
348
|
-
// Should transform because it has onClick (href is ignored for this transform)
|
|
349
|
-
expect(result).toContain(
|
|
350
|
-
'<Button href="/test" onClick={handleClick}>Link with both</Button>'
|
|
351
|
-
)
|
|
352
|
-
expect(result).toContain(
|
|
353
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
354
|
-
)
|
|
355
|
-
expect(result).not.toContain("import { Link }")
|
|
356
|
-
expect(result).not.toContain("<Link")
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
it("should not transform Link without onClick even if it has other handlers", () => {
|
|
360
|
-
const input = `
|
|
361
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
362
|
-
|
|
363
|
-
function Component() {
|
|
364
|
-
return <Link href="/test" onMouseOver={handleHover}>Hover me</Link>
|
|
365
|
-
}
|
|
366
|
-
`
|
|
367
|
-
|
|
368
|
-
const result = runTransform(input)
|
|
369
|
-
|
|
370
|
-
expect(result).toBeNull()
|
|
371
|
-
})
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
describe("conflict resolution", () => {
|
|
375
|
-
it("should use TRButton alias when Button conflicts with existing import", () => {
|
|
376
|
-
const input = `
|
|
377
|
-
import { Link } from "@planningcenter/tapestry-react"
|
|
378
|
-
import { Button } from "other-ui-lib"
|
|
379
|
-
|
|
380
|
-
function Component() {
|
|
381
|
-
return (
|
|
382
|
-
<div>
|
|
383
|
-
<Link onClick={handleClick}>Tapestry Button</Link>
|
|
384
|
-
<Button variant="primary">Other Button</Button>
|
|
385
|
-
</div>
|
|
386
|
-
)
|
|
387
|
-
}
|
|
388
|
-
`
|
|
389
|
-
|
|
390
|
-
const result = runTransform(input)
|
|
391
|
-
|
|
392
|
-
expect(result).toContain(
|
|
393
|
-
"<TRButton onClick={handleClick}>Tapestry Button</TRButton>"
|
|
394
|
-
)
|
|
395
|
-
expect(result).toContain(
|
|
396
|
-
'<Button variant="primary">Other Button</Button>'
|
|
397
|
-
)
|
|
398
|
-
expect(result).toContain('import { Button } from "other-ui-lib"')
|
|
399
|
-
expect(result).toContain(
|
|
400
|
-
'import { Button as TRButton } from "@planningcenter/tapestry-react"'
|
|
401
|
-
)
|
|
402
|
-
expect(result).not.toContain("import { Link }")
|
|
403
|
-
})
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
describe("detecting renamed import of Link", () => {
|
|
407
|
-
it("should handle renamed Link import", () => {
|
|
408
|
-
const input = `
|
|
409
|
-
import { Link as TapestryLink } from "@planningcenter/tapestry-react"
|
|
410
|
-
|
|
411
|
-
function Component() {
|
|
412
|
-
return <TapestryLink onClick={handleClick}>Button</TapestryLink>
|
|
413
|
-
}
|
|
414
|
-
`
|
|
415
|
-
|
|
416
|
-
const result = runTransform(input)
|
|
417
|
-
|
|
418
|
-
expect(result).toContain("<Button onClick={handleClick}>Button</Button>")
|
|
419
|
-
expect(result).toContain(
|
|
420
|
-
'import { Button } from "@planningcenter/tapestry-react"'
|
|
421
|
-
)
|
|
422
|
-
expect(result).not.toContain("TapestryLink")
|
|
423
|
-
expect(result).not.toContain("Link")
|
|
424
|
-
})
|
|
425
|
-
})
|
|
426
|
-
})
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Transform } from "jscodeshift"
|
|
2
|
-
|
|
3
|
-
import { addAttribute } from "../../shared/actions/addAttribute"
|
|
4
|
-
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
5
|
-
import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
|
|
6
|
-
|
|
7
|
-
const addKindAttribute = attributeTransformFactory({
|
|
8
|
-
condition: () => true, // Add to all Link.Inline elements
|
|
9
|
-
targetComponent: "Link.Inline",
|
|
10
|
-
targetPackage: "@planningcenter/tapestry-react",
|
|
11
|
-
transform: (element, { j }) => {
|
|
12
|
-
addAttribute({
|
|
13
|
-
element,
|
|
14
|
-
j,
|
|
15
|
-
name: "kind",
|
|
16
|
-
value: "inline-text",
|
|
17
|
-
})
|
|
18
|
-
return true
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
const transformComponent = componentTransformFactory({
|
|
23
|
-
condition: () => true, // Transform all Link.Inline elements
|
|
24
|
-
fromComponent: "Link.Inline",
|
|
25
|
-
fromPackage: "@planningcenter/tapestry-react",
|
|
26
|
-
toComponent: "Link",
|
|
27
|
-
toPackage: "@planningcenter/tapestry-react",
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// Combined transform that runs both steps
|
|
31
|
-
const transform: Transform = (fileInfo, api, options) => {
|
|
32
|
-
// Step 1: Add kind attribute to Link.Inline elements
|
|
33
|
-
const result1 = addKindAttribute(fileInfo, api, options)
|
|
34
|
-
|
|
35
|
-
if (!result1) {
|
|
36
|
-
return null
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Step 2: Transform Link.Inline to Link
|
|
40
|
-
const result2 = transformComponent(
|
|
41
|
-
{ ...fileInfo, source: result1 as string },
|
|
42
|
-
api,
|
|
43
|
-
options
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
return result2 || result1
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default transform
|