@planningcenter/tapestry-migration-cli 2.4.0-rc.16 → 2.4.0-rc.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "2.4.0-rc.16",
3
+ "version": "2.4.0-rc.18",
4
4
  "description": "CLI tool for Tapestry migrations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -51,5 +51,5 @@
51
51
  "publishConfig": {
52
52
  "access": "public"
53
53
  },
54
- "gitHead": "0e96826315b0c9ea498251b38bfaee707774599a"
54
+ "gitHead": "922acdf4288f78cac0cc72aecfe179e40810cc01"
55
55
  }
@@ -9,7 +9,6 @@ import iconLeftToPrefix from "./transforms/iconLeftToPrefix"
9
9
  import iconRightToSuffix from "./transforms/iconRightToSuffix"
10
10
  import iconToIconButton from "./transforms/iconToIconButton"
11
11
  import innerRefToRef from "./transforms/innerRefToRef"
12
- import linkToButton from "./transforms/linkToButton"
13
12
  import moveButtonImport from "./transforms/moveButtonImport"
14
13
  import removeAsButton from "./transforms/removeAsButton"
15
14
  import removeDuplicateKeys from "./transforms/removeDuplicateKeys"
@@ -48,7 +47,6 @@ const transform: Transform = async (fileInfo, api, options) => {
48
47
  }
49
48
 
50
49
  const transforms = [
51
- linkToButton,
52
50
  tooltipToWrapper,
53
51
  iconRightToSuffix,
54
52
  iconLeftToPrefix,
@@ -1,5 +1,6 @@
1
1
  import { Transform } from "jscodeshift"
2
2
 
3
+ import auditSpreadProps from "./transforms/auditSpreadProps"
3
4
  import childrenToLabel from "./transforms/childrenToLabel"
4
5
  import convertStyleProps from "./transforms/convertStyleProps"
5
6
  import inlineMemberToKind from "./transforms/inlineMemberToKind"
@@ -26,6 +27,7 @@ const transform: Transform = (fileInfo, api, options) => {
26
27
  innerRefToRef,
27
28
  removeAs,
28
29
  childrenToLabel,
30
+ auditSpreadProps,
29
31
  reviewStyles,
30
32
  convertStyleProps,
31
33
  unsupportedProps,
@@ -0,0 +1,351 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./auditSpreadProps"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ const AUDIT_COMMENT =
9
+ "TODO: tapestry-migration (spreadAttribute): Spread props can contain unsupported props, please explore usages and migrate as needed."
10
+
11
+ function applyTransform(source: string) {
12
+ const fileInfo = { path: "test.tsx", source }
13
+ return transform(
14
+ fileInfo,
15
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
16
+ {}
17
+ ) as string | null
18
+ }
19
+
20
+ describe("auditSpreadProps transform", () => {
21
+ describe("basic transformations", () => {
22
+ it("should add comment to Link with single spread prop", () => {
23
+ const input = `
24
+ import { Link } from "@planningcenter/tapestry-react"
25
+
26
+ export default function Test() {
27
+ const props = { href: "/test" }
28
+ return <Link {...props}>Go</Link>
29
+ }
30
+ `.trim()
31
+
32
+ const result = applyTransform(input)
33
+ expect(result).toContain(AUDIT_COMMENT)
34
+ expect(result).toContain("{...props}")
35
+ })
36
+
37
+ it("should add comment to Link with multiple spread props", () => {
38
+ const input = `
39
+ import { Link } from "@planningcenter/tapestry-react"
40
+
41
+ export default function Test() {
42
+ const baseProps = { href: "/test" }
43
+ const styleProps = { className: "link" }
44
+ return <Link {...baseProps} {...styleProps}>Go</Link>
45
+ }
46
+ `.trim()
47
+
48
+ const result = applyTransform(input)
49
+ expect(result).toContain(AUDIT_COMMENT)
50
+ expect(result).toContain("{...baseProps}")
51
+ expect(result).toContain("{...styleProps}")
52
+ })
53
+
54
+ it("should handle Link with spread props and regular attributes", () => {
55
+ const input = `
56
+ import { Link } from "@planningcenter/tapestry-react"
57
+
58
+ export default function Test() {
59
+ const props = { href: "/test" }
60
+ return <Link label="Go" {...props} external />
61
+ }
62
+ `.trim()
63
+
64
+ const result = applyTransform(input)
65
+ expect(result).toContain(AUDIT_COMMENT)
66
+ expect(result).toContain('label="Go"')
67
+ expect(result).toContain("{...props}")
68
+ expect(result).toContain("external")
69
+ })
70
+
71
+ it("should handle multiple Link components with spread props", () => {
72
+ const input = `
73
+ import { Link } from "@planningcenter/tapestry-react"
74
+
75
+ export default function Test() {
76
+ const props1 = { href: "/test1" }
77
+ const props2 = { href: "/test2" }
78
+ return (
79
+ <div>
80
+ <Link {...props1}>Go 1</Link>
81
+ <Link {...props2}>Go 2</Link>
82
+ </div>
83
+ )
84
+ }
85
+ `.trim()
86
+
87
+ const result = applyTransform(input)
88
+ expect(result).toContain(AUDIT_COMMENT)
89
+ expect(result).toContain("{...props1}")
90
+ expect(result).toContain("{...props2}")
91
+ })
92
+ })
93
+
94
+ describe("edge cases", () => {
95
+ it("should not transform Link without spread props", () => {
96
+ const input = `
97
+ import { Link } from "@planningcenter/tapestry-react"
98
+
99
+ export default function Test() {
100
+ return <Link href="/test">Go</Link>
101
+ }
102
+ `.trim()
103
+
104
+ const result = applyTransform(input)
105
+ expect(result).toBe(null)
106
+ })
107
+
108
+ it("should not transform if Link is not imported from @planningcenter/tapestry-react", () => {
109
+ const input = `
110
+ import { Link } from "other-library"
111
+
112
+ export default function Test() {
113
+ const props = { href: "/test" }
114
+ return <Link {...props}>Go</Link>
115
+ }
116
+ `.trim()
117
+
118
+ const result = applyTransform(input)
119
+ expect(result).toBe(null)
120
+ })
121
+
122
+ it("should handle Link with alias import", () => {
123
+ const input = `
124
+ import { Link as TapestryLink } from "@planningcenter/tapestry-react"
125
+
126
+ export default function Test() {
127
+ const props = { href: "/test" }
128
+ return <TapestryLink {...props}>Go</TapestryLink>
129
+ }
130
+ `.trim()
131
+
132
+ const result = applyTransform(input)
133
+ expect(result).toContain(AUDIT_COMMENT)
134
+ expect(result).toContain("{...props}")
135
+ })
136
+
137
+ it("should handle mixed Link components (with and without spread props)", () => {
138
+ const input = `
139
+ import { Link } from "@planningcenter/tapestry-react"
140
+
141
+ export default function Test() {
142
+ const props = { href: "/test" }
143
+ return (
144
+ <div>
145
+ <Link {...props}>Go</Link>
146
+ <Link href="/static">Static</Link>
147
+ <Link href="/dynamic" external>External</Link>
148
+ </div>
149
+ )
150
+ }
151
+ `.trim()
152
+
153
+ const result = applyTransform(input)
154
+ expect(result).toContain(AUDIT_COMMENT)
155
+ expect(result).toContain("{...props}")
156
+ expect(result).toContain('<Link href="/static">Static</Link>')
157
+ expect(result).toContain('href="/dynamic"')
158
+ expect(result).toContain("external")
159
+ })
160
+ })
161
+
162
+ describe("complex spread prop scenarios", () => {
163
+ it("should handle Link with complex spread expression", () => {
164
+ const input = `
165
+ import { Link } from "@planningcenter/tapestry-react"
166
+
167
+ export default function Test() {
168
+ const baseProps = { className: "link" }
169
+ const additionalProps = isExternal ? { external: true } : {}
170
+ return <Link {...baseProps} {...additionalProps} href="/test">Go</Link>
171
+ }
172
+ `.trim()
173
+
174
+ const result = applyTransform(input)
175
+ expect(result).toContain(AUDIT_COMMENT)
176
+ expect(result).toContain("{...baseProps}")
177
+ expect(result).toContain("{...additionalProps}")
178
+ })
179
+
180
+ it("should handle Link with spread props in different positions", () => {
181
+ const input = `
182
+ import { Link } from "@planningcenter/tapestry-react"
183
+
184
+ export default function Test() {
185
+ const props = { className: "link" }
186
+ return (
187
+ <Link
188
+ label="Go"
189
+ {...props}
190
+ href="/test"
191
+ external={false}
192
+ />
193
+ )
194
+ }
195
+ `.trim()
196
+
197
+ const result = applyTransform(input)
198
+ expect(result).toContain(AUDIT_COMMENT)
199
+ expect(result).toContain('label="Go"')
200
+ expect(result).toContain("{...props}")
201
+ expect(result).toContain('href="/test"')
202
+ expect(result).toContain("external={false}")
203
+ })
204
+
205
+ it("should handle spread props with object expressions", () => {
206
+ const input = `
207
+ import { Link } from "@planningcenter/tapestry-react"
208
+
209
+ export default function Test() {
210
+ return <Link {...{ href: "/test", className: "link" }}>Go</Link>
211
+ }
212
+ `.trim()
213
+
214
+ const result = applyTransform(input)
215
+ expect(result).toContain(AUDIT_COMMENT)
216
+ expect(result).toContain('{...{ href: "/test", className: "link" }}')
217
+ })
218
+
219
+ it("should handle spread props with function calls", () => {
220
+ const input = `
221
+ import { Link } from "@planningcenter/tapestry-react"
222
+
223
+ export default function Test() {
224
+ return <Link {...getLinkProps()} {...getStyleProps()}>Go</Link>
225
+ }
226
+ `.trim()
227
+
228
+ const result = applyTransform(input)
229
+ expect(result).toContain(AUDIT_COMMENT)
230
+ expect(result).toContain("{...getLinkProps()}")
231
+ expect(result).toContain("{...getStyleProps()}")
232
+ })
233
+ })
234
+
235
+ describe("self-closing Link components", () => {
236
+ it("should handle self-closing Link with spread props", () => {
237
+ const input = `
238
+ import { Link } from "@planningcenter/tapestry-react"
239
+
240
+ export default function Test() {
241
+ const props = { label: "Go", href: "/test" }
242
+ return <Link {...props} />
243
+ }
244
+ `.trim()
245
+
246
+ const result = applyTransform(input)
247
+ expect(result).toContain(AUDIT_COMMENT)
248
+ expect(result).toContain("{...props}")
249
+ })
250
+
251
+ it("should handle self-closing Link with multiple spread props and attributes", () => {
252
+ const input = `
253
+ import { Link } from "@planningcenter/tapestry-react"
254
+
255
+ export default function Test() {
256
+ const baseProps = { href: "/test" }
257
+ const styleProps = { className: "link" }
258
+ return <Link label="Go" {...baseProps} {...styleProps} external />
259
+ }
260
+ `.trim()
261
+
262
+ const result = applyTransform(input)
263
+ expect(result).toContain(AUDIT_COMMENT)
264
+ expect(result).toContain('label="Go"')
265
+ expect(result).toContain("{...baseProps}")
266
+ expect(result).toContain("{...styleProps}")
267
+ expect(result).toContain("external")
268
+ })
269
+ })
270
+
271
+ describe("no changes scenarios", () => {
272
+ it("should return null when no Link components have spread props", () => {
273
+ const input = `
274
+ import { Link } from "@planningcenter/tapestry-react"
275
+
276
+ export default function Test() {
277
+ return (
278
+ <div>
279
+ <Link href="/test">Go</Link>
280
+ <Link label="Static" href="/static" />
281
+ <Link href="/dynamic" external>External</Link>
282
+ </div>
283
+ )
284
+ }
285
+ `.trim()
286
+
287
+ const result = applyTransform(input)
288
+ expect(result).toBe(null)
289
+ })
290
+
291
+ it("should return null when no Link imports exist", () => {
292
+ const input = `
293
+ import { Button } from "@planningcenter/tapestry-react"
294
+
295
+ export default function Test() {
296
+ const props = { onClick: handleClick }
297
+ return <Button {...props}>Save</Button>
298
+ }
299
+ `.trim()
300
+
301
+ const result = applyTransform(input)
302
+ expect(result).toBe(null)
303
+ })
304
+
305
+ it("should return null for empty file", () => {
306
+ const result = applyTransform("")
307
+ expect(result).toBe(null)
308
+ })
309
+
310
+ it("should return null when file has no JSX", () => {
311
+ const input = `
312
+ export function handleClick() {
313
+ console.log("clicked")
314
+ }
315
+ `.trim()
316
+
317
+ const result = applyTransform(input)
318
+ expect(result).toBe(null)
319
+ })
320
+ })
321
+
322
+ describe("different import scenarios", () => {
323
+ it("should handle default import with spread props", () => {
324
+ const input = `
325
+ import Link from "@planningcenter/tapestry-react"
326
+
327
+ export default function Test() {
328
+ const props = { href: "/test" }
329
+ return <Link {...props}>Go</Link>
330
+ }
331
+ `.trim()
332
+
333
+ const result = applyTransform(input)
334
+ expect(result).toBe(null)
335
+ })
336
+
337
+ it("should handle namespace import", () => {
338
+ const input = `
339
+ import * as Tapestry from "@planningcenter/tapestry-react"
340
+
341
+ export default function Test() {
342
+ const props = { href: "/test" }
343
+ return <Tapestry.Link {...props}>Go</Tapestry.Link>
344
+ }
345
+ `.trim()
346
+
347
+ const result = applyTransform(input)
348
+ expect(result).toBe(null)
349
+ })
350
+ })
351
+ })
@@ -0,0 +1,24 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
4
+ import { getSpreadProps } from "../../shared/actions/getSpreadProps"
5
+ import { hasSpreadProps } from "../../shared/actions/hasSpreadProps"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+
8
+ const COMMENT =
9
+ "Spread props can contain unsupported props, please explore usages and migrate as needed."
10
+
11
+ const transform: Transform = attributeTransformFactory({
12
+ condition: hasSpreadProps,
13
+ targetComponent: "Link",
14
+ targetPackage: "@planningcenter/tapestry-react",
15
+ transform: (element, { j }) => {
16
+ const spreadProps = getSpreadProps(element)
17
+ spreadProps.forEach((prop) =>
18
+ addCommentToAttribute({ attribute: prop, j, text: COMMENT })
19
+ )
20
+ return spreadProps.length > 0
21
+ },
22
+ })
23
+
24
+ export default transform
@@ -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,15 +0,0 @@
1
- import { Transform } from "jscodeshift"
2
-
3
- import { hasAttribute } from "../../shared/conditions/hasAttribute"
4
- import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
5
-
6
- const transform: Transform = componentTransformFactory({
7
- condition: hasAttribute("onClick"),
8
- conflictAlias: "TRButton",
9
- fromComponent: "Link",
10
- fromPackage: "@planningcenter/tapestry-react",
11
- toComponent: "Button",
12
- toPackage: "@planningcenter/tapestry-react",
13
- })
14
-
15
- export default transform