@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/tapestry-migration-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "CLI tool for Tapestry migrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@emotion/react": "^11.14.0",
|
|
33
|
-
"@planningcenter/tapestry": "^3.
|
|
33
|
+
"@planningcenter/tapestry": "^3.6.0",
|
|
34
34
|
"@planningcenter/tapestry-react": "^4.11.5",
|
|
35
35
|
"@types/jscodeshift": "^17.3.0",
|
|
36
36
|
"@types/node": "^20.0.0",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "0009d96d1c39de5978d2cdea8334ca5120974137"
|
|
54
54
|
}
|
|
@@ -84,9 +84,9 @@ export default function Test() {
|
|
|
84
84
|
|
|
85
85
|
expect(result).toContain('<Tooltip {...{title: "Help"}}')
|
|
86
86
|
expect(result).toContain("refAsInnerRef={false}")
|
|
87
|
-
expect(result).toContain(
|
|
88
|
-
|
|
89
|
-
)
|
|
87
|
+
expect(result).toContain('variant="primary"')
|
|
88
|
+
expect(result).toContain("onClick={handler}")
|
|
89
|
+
expect(result).toContain("disabled>Save</Button>")
|
|
90
90
|
expect(result).not.toContain("tooltip=")
|
|
91
91
|
})
|
|
92
92
|
|
|
@@ -323,6 +323,53 @@ export default function Test({ items }) {
|
|
|
323
323
|
expect(result).not.toContain("tooltip=")
|
|
324
324
|
})
|
|
325
325
|
|
|
326
|
+
it("should add a TODO comment when the Button is disabled", () => {
|
|
327
|
+
const input = `
|
|
328
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
329
|
+
|
|
330
|
+
export default function Test() {
|
|
331
|
+
return <Button tooltip={{ title: "Save" }} disabled>Save</Button>
|
|
332
|
+
}
|
|
333
|
+
`.trim()
|
|
334
|
+
|
|
335
|
+
const result = applyTransform(input)
|
|
336
|
+
|
|
337
|
+
expect(result).toContain("TODO: tapestry-migration (disabled):")
|
|
338
|
+
expect(result).toContain("<Tooltip")
|
|
339
|
+
expect(result).toContain("disabled>Save</Button>")
|
|
340
|
+
expect(result).not.toContain("tooltip=")
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
it("should add a TODO comment when disabled has an expression value", () => {
|
|
344
|
+
const input = `
|
|
345
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
346
|
+
|
|
347
|
+
export default function Test({ isDisabled }) {
|
|
348
|
+
return <Button tooltip={{ title: "Save" }} disabled={isDisabled}>Save</Button>
|
|
349
|
+
}
|
|
350
|
+
`.trim()
|
|
351
|
+
|
|
352
|
+
const result = applyTransform(input)
|
|
353
|
+
|
|
354
|
+
expect(result).toContain("TODO: tapestry-migration (disabled):")
|
|
355
|
+
expect(result).toContain("disabled={isDisabled}")
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it("should not add a disabled TODO comment when the Button is not disabled", () => {
|
|
359
|
+
const input = `
|
|
360
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
361
|
+
|
|
362
|
+
export default function Test() {
|
|
363
|
+
return <Button tooltip={{ title: "Save" }}>Save</Button>
|
|
364
|
+
}
|
|
365
|
+
`.trim()
|
|
366
|
+
|
|
367
|
+
const result = applyTransform(input)
|
|
368
|
+
|
|
369
|
+
expect(result).toContain("<Tooltip")
|
|
370
|
+
expect(result).not.toContain("TODO: tapestry-migration (disabled):")
|
|
371
|
+
})
|
|
372
|
+
|
|
326
373
|
it("should handle empty tooltip attribute", () => {
|
|
327
374
|
const input = `
|
|
328
375
|
import { Button } from "@planningcenter/tapestry-react"
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { addAttribute } from "../../shared/actions/addAttribute"
|
|
2
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
2
3
|
import { createWrapper } from "../../shared/actions/createWrapper"
|
|
4
|
+
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
3
5
|
import { getAttributeValueAsProps } from "../../shared/actions/getAttributeValueAsProps"
|
|
4
6
|
import { removeAttribute } from "../../shared/actions/removeAttribute"
|
|
5
7
|
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
@@ -19,6 +21,11 @@ const transform = attributeTransformFactory({
|
|
|
19
21
|
|
|
20
22
|
removeAttribute("tooltip", { element, j, source })
|
|
21
23
|
|
|
24
|
+
// Capture the original Button's `disabled` attribute before `createWrapper`
|
|
25
|
+
// replaces `element` in the AST, so the comment below clearly attaches to
|
|
26
|
+
// the original Button attribute node.
|
|
27
|
+
const disabledAttribute = getAttribute({ element, name: "disabled" })
|
|
28
|
+
|
|
22
29
|
const wrapperElement = createWrapper({
|
|
23
30
|
conflictAlias: "TRTooltip",
|
|
24
31
|
element,
|
|
@@ -36,6 +43,17 @@ const transform = attributeTransformFactory({
|
|
|
36
43
|
value: false,
|
|
37
44
|
})
|
|
38
45
|
|
|
46
|
+
if (disabledAttribute) {
|
|
47
|
+
addCommentToAttribute({
|
|
48
|
+
attribute: disabledAttribute,
|
|
49
|
+
j,
|
|
50
|
+
text:
|
|
51
|
+
"This Button is disabled. Disabled buttons don't emit pointer or focus events, " +
|
|
52
|
+
"so the wrapping Tooltip will not appear. Keep the button interactive (e.g. handle " +
|
|
53
|
+
"the disabled state without the native `disabled` attribute) so the Tooltip can trigger.",
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
return true
|
|
40
58
|
},
|
|
41
59
|
})
|
|
@@ -200,6 +200,73 @@ export default function Test() {
|
|
|
200
200
|
expect(result).toContain("TODO: tapestry-migration (variant)")
|
|
201
201
|
})
|
|
202
202
|
|
|
203
|
+
it("applies wrapIconTrigger through the chain", () => {
|
|
204
|
+
const input = `
|
|
205
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
206
|
+
|
|
207
|
+
export default function Test() {
|
|
208
|
+
return (
|
|
209
|
+
<Dropdown icon={{ name: "general.pencil" }} title="Edit options">
|
|
210
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
211
|
+
</Dropdown>
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
`.trim()
|
|
215
|
+
|
|
216
|
+
const result = applyTransform(input)
|
|
217
|
+
expect(result).toContain("<DropdownTrigger>")
|
|
218
|
+
expect(result).toContain("<DropdownIconButton")
|
|
219
|
+
expect(result).toContain('aria-label="Edit options"')
|
|
220
|
+
expect(result).toContain("#pencil")
|
|
221
|
+
expect(result).not.toContain("icon={{ name:")
|
|
222
|
+
expect(result).not.toContain('title="Edit options"')
|
|
223
|
+
expect(result).not.toContain('from "@planningcenter/tapestry-react"')
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it("applies variantToKind through the chain (variant)", () => {
|
|
227
|
+
const input = `
|
|
228
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
229
|
+
|
|
230
|
+
export default function Test() {
|
|
231
|
+
return (
|
|
232
|
+
<Dropdown title="Actions" variant="outline">
|
|
233
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
234
|
+
</Dropdown>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
`.trim()
|
|
238
|
+
|
|
239
|
+
const result = applyTransform(input)
|
|
240
|
+
expect(result).toContain('kind="secondary"')
|
|
241
|
+
expect(result).not.toContain('variant="outline"')
|
|
242
|
+
const dropdownButtonLine = result
|
|
243
|
+
?.split("\n")
|
|
244
|
+
.find((l) => l.includes("<DropdownButton"))
|
|
245
|
+
expect(dropdownButtonLine).toContain("kind")
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it("applies variantToKind through the chain (theme)", () => {
|
|
249
|
+
const input = `
|
|
250
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
251
|
+
|
|
252
|
+
export default function Test() {
|
|
253
|
+
return (
|
|
254
|
+
<Dropdown title="Actions" theme="error">
|
|
255
|
+
<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>
|
|
256
|
+
</Dropdown>
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
`.trim()
|
|
260
|
+
|
|
261
|
+
const result = applyTransform(input)
|
|
262
|
+
expect(result).toContain('kind="delete"')
|
|
263
|
+
expect(result).not.toContain('theme="error"')
|
|
264
|
+
const dropdownButtonLine = result
|
|
265
|
+
?.split("\n")
|
|
266
|
+
.find((l) => l.includes("<DropdownButton"))
|
|
267
|
+
expect(dropdownButtonLine).toContain("kind")
|
|
268
|
+
})
|
|
269
|
+
|
|
203
270
|
it("applies moveDropdownImport through the chain", () => {
|
|
204
271
|
const input = `
|
|
205
272
|
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
@@ -10,6 +10,8 @@ import unsupportedProps from "./transforms/unsupportedProps"
|
|
|
10
10
|
import unsupportedPropsDivider from "./transforms/unsupportedPropsDivider"
|
|
11
11
|
import unsupportedPropsItem from "./transforms/unsupportedPropsItem"
|
|
12
12
|
import unsupportedPropsLink from "./transforms/unsupportedPropsLink"
|
|
13
|
+
import variantToKind from "./transforms/variantToKind"
|
|
14
|
+
import wrapIconTrigger from "./transforms/wrapIconTrigger"
|
|
13
15
|
import wrapMenu from "./transforms/wrapMenu"
|
|
14
16
|
import wrapTrigger from "./transforms/wrapTrigger"
|
|
15
17
|
|
|
@@ -25,7 +27,9 @@ const transform: Transform = (fileInfo, api, options) => {
|
|
|
25
27
|
linkToLink,
|
|
26
28
|
wrapMenu,
|
|
27
29
|
placementIdToMenu,
|
|
30
|
+
wrapIconTrigger,
|
|
28
31
|
wrapTrigger,
|
|
32
|
+
variantToKind,
|
|
29
33
|
dividerToSeparator,
|
|
30
34
|
auditSpreadProps,
|
|
31
35
|
unsupportedProps,
|
|
@@ -50,12 +50,17 @@ describe("placementIdToMenu transform", () => {
|
|
|
50
50
|
expect(result).toContain('<DropdownMenu placement={"bottom end"}')
|
|
51
51
|
})
|
|
52
52
|
|
|
53
|
-
it("moves dynamic placement expression and adds TODO comment", () => {
|
|
53
|
+
it("moves dynamic placement expression and adds TODO comment inside Dropdown before DropdownMenu", () => {
|
|
54
54
|
const result = applyTransform(withMenu("placement={myPlacement}"))
|
|
55
55
|
expect(result).toContain("<DropdownMenu placement={myPlacement}")
|
|
56
56
|
expect(result).toContain(
|
|
57
57
|
"TODO: tapestry-migration (placement): placement is a dynamic expression"
|
|
58
58
|
)
|
|
59
|
+
const todoIndex = result!.indexOf("TODO: tapestry-migration (placement)")
|
|
60
|
+
const dropdownOpenIndex = result!.indexOf("<Dropdown ")
|
|
61
|
+
const dropdownMenuIndex = result!.indexOf("<DropdownMenu")
|
|
62
|
+
expect(todoIndex).toBeGreaterThan(dropdownOpenIndex)
|
|
63
|
+
expect(todoIndex).toBeLessThan(dropdownMenuIndex)
|
|
59
64
|
})
|
|
60
65
|
})
|
|
61
66
|
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./variantToKind"
|
|
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
|
+
function wrappedInput(
|
|
18
|
+
attrs: string,
|
|
19
|
+
buttonElement = '<DropdownButton label="Actions" />'
|
|
20
|
+
) {
|
|
21
|
+
return `
|
|
22
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
23
|
+
import { DropdownButton, DropdownMenu, DropdownTrigger } from "@planningcenter/tapestry"
|
|
24
|
+
|
|
25
|
+
export default function Test() {
|
|
26
|
+
return (
|
|
27
|
+
<Dropdown ${attrs} onClose={fn}>
|
|
28
|
+
<DropdownTrigger>
|
|
29
|
+
${buttonElement}
|
|
30
|
+
</DropdownTrigger>
|
|
31
|
+
<DropdownMenu>
|
|
32
|
+
<DropdownAction id="edit" onAction={fn}>Edit</DropdownAction>
|
|
33
|
+
</DropdownMenu>
|
|
34
|
+
</Dropdown>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
`.trim()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe("variantToKind transform", () => {
|
|
41
|
+
describe("variant-only mappings (default theme)", () => {
|
|
42
|
+
it('maps variant="outline" to kind="secondary"', () => {
|
|
43
|
+
const result = applyTransform(wrappedInput('variant="outline"'))
|
|
44
|
+
expect(result).toContain('kind="secondary"')
|
|
45
|
+
expect(result).not.toContain('variant="outline"')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('maps variant="fill" to kind="neutral"', () => {
|
|
49
|
+
const result = applyTransform(wrappedInput('variant="fill"'))
|
|
50
|
+
expect(result).toContain('kind="neutral"')
|
|
51
|
+
expect(result).not.toContain('variant="fill"')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('maps variant="naked" to kind="ghost"', () => {
|
|
55
|
+
const result = applyTransform(wrappedInput('variant="naked"'))
|
|
56
|
+
expect(result).toContain('kind="ghost"')
|
|
57
|
+
expect(result).not.toContain('variant="naked"')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('normalizes variant="ghost" to kind="ghost"', () => {
|
|
61
|
+
const result = applyTransform(wrappedInput('variant="ghost"'))
|
|
62
|
+
expect(result).toContain('kind="ghost"')
|
|
63
|
+
expect(result).not.toContain('variant="ghost"')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('normalizes variant="solid" to kind="neutral" (solid→fill→neutral)', () => {
|
|
67
|
+
const result = applyTransform(wrappedInput('variant="solid"'))
|
|
68
|
+
expect(result).toContain('kind="neutral"')
|
|
69
|
+
expect(result).not.toContain('variant="solid"')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe("theme-only mappings (default variant)", () => {
|
|
74
|
+
it('maps theme="error" to kind="delete"', () => {
|
|
75
|
+
const result = applyTransform(wrappedInput('theme="error"'))
|
|
76
|
+
expect(result).toContain('kind="delete"')
|
|
77
|
+
expect(result).not.toContain('theme="error"')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('maps theme="primary" to kind="primary"', () => {
|
|
81
|
+
const result = applyTransform(wrappedInput('theme="primary"'))
|
|
82
|
+
expect(result).toContain('kind="primary"')
|
|
83
|
+
expect(result).not.toContain('theme="primary"')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('normalizes theme="destroy" to kind="delete"', () => {
|
|
87
|
+
const result = applyTransform(wrappedInput('theme="destroy"'))
|
|
88
|
+
expect(result).toContain('kind="delete"')
|
|
89
|
+
expect(result).not.toContain('theme="destroy"')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('normalizes theme="interaction" to kind="primary"', () => {
|
|
93
|
+
const result = applyTransform(wrappedInput('theme="interaction"'))
|
|
94
|
+
expect(result).toContain('kind="primary"')
|
|
95
|
+
expect(result).not.toContain('theme="interaction"')
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
describe("theme + variant combined", () => {
|
|
100
|
+
it('maps theme="error" variant="outline" to kind="secondary-delete"', () => {
|
|
101
|
+
const result = applyTransform(
|
|
102
|
+
wrappedInput('theme="error" variant="outline"')
|
|
103
|
+
)
|
|
104
|
+
expect(result).toContain('kind="secondary-delete"')
|
|
105
|
+
expect(result).not.toContain('theme="error"')
|
|
106
|
+
expect(result).not.toContain('variant="outline"')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('maps theme="error" variant="naked" to kind="ghost-delete"', () => {
|
|
110
|
+
const result = applyTransform(
|
|
111
|
+
wrappedInput('theme="error" variant="naked"')
|
|
112
|
+
)
|
|
113
|
+
expect(result).toContain('kind="ghost-delete"')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('maps theme="primary" variant="outline" to kind="secondary-interaction"', () => {
|
|
117
|
+
const result = applyTransform(
|
|
118
|
+
wrappedInput('theme="primary" variant="outline"')
|
|
119
|
+
)
|
|
120
|
+
expect(result).toContain('kind="secondary-interaction"')
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
describe("kind placement", () => {
|
|
125
|
+
it("adds kind to the DropdownButton, not to Dropdown", () => {
|
|
126
|
+
const result = applyTransform(wrappedInput('variant="outline"'))
|
|
127
|
+
const dropdownButtonLine = result
|
|
128
|
+
?.split("\n")
|
|
129
|
+
.find((l) => l.includes("<DropdownButton"))
|
|
130
|
+
expect(dropdownButtonLine).toContain("kind")
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it("emits TODO and does not duplicate kind when button already has kind", () => {
|
|
134
|
+
const result = applyTransform(
|
|
135
|
+
wrappedInput(
|
|
136
|
+
'variant="outline"',
|
|
137
|
+
'<DropdownButton label="Actions" kind="primary" />'
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
expect(result).not.toContain('variant="outline"')
|
|
141
|
+
expect(result).toContain("TODO: tapestry-migration (variant):")
|
|
142
|
+
expect(result).toContain("secondary")
|
|
143
|
+
const buttonLine = result
|
|
144
|
+
?.split("\n")
|
|
145
|
+
.find((l) => l.includes("<DropdownButton"))
|
|
146
|
+
expect(buttonLine).toContain('kind="primary"')
|
|
147
|
+
expect(buttonLine).not.toContain('kind="secondary"')
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('maps variant="outline" to kind="secondary" on DropdownIconButton', () => {
|
|
151
|
+
const result = applyTransform(
|
|
152
|
+
wrappedInput(
|
|
153
|
+
'variant="outline"',
|
|
154
|
+
'<DropdownIconButton aria-label="Edit" icon={icon} />'
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
expect(result).toContain('kind="secondary"')
|
|
158
|
+
expect(result).not.toContain('variant="outline"')
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
describe("complex/custom trigger child", () => {
|
|
163
|
+
it("removes variant and emits TODO for unknown trigger button", () => {
|
|
164
|
+
const input = `
|
|
165
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
166
|
+
import { DropdownMenu, DropdownTrigger } from "@planningcenter/tapestry"
|
|
167
|
+
|
|
168
|
+
export default function Test() {
|
|
169
|
+
return (
|
|
170
|
+
<Dropdown variant="outline" onClose={fn}>
|
|
171
|
+
<DropdownTrigger>
|
|
172
|
+
<MyCustomButton label="Actions" />
|
|
173
|
+
</DropdownTrigger>
|
|
174
|
+
<DropdownMenu />
|
|
175
|
+
</Dropdown>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
`.trim()
|
|
179
|
+
|
|
180
|
+
const result = applyTransform(input)
|
|
181
|
+
expect(result).not.toContain('variant="outline"')
|
|
182
|
+
expect(result).toContain("TODO: tapestry-migration (variant):")
|
|
183
|
+
expect(result).toContain("secondary")
|
|
184
|
+
const customButtonLine = result
|
|
185
|
+
?.split("\n")
|
|
186
|
+
.find((l) => l.includes("<MyCustomButton"))
|
|
187
|
+
expect(customButtonLine).not.toContain("kind")
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it("uses scope=theme when only theme is present on custom trigger", () => {
|
|
191
|
+
const input = `
|
|
192
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
193
|
+
import { DropdownMenu, DropdownTrigger } from "@planningcenter/tapestry"
|
|
194
|
+
|
|
195
|
+
export default function Test() {
|
|
196
|
+
return (
|
|
197
|
+
<Dropdown theme="error" onClose={fn}>
|
|
198
|
+
<DropdownTrigger>
|
|
199
|
+
<MyCustomButton label="Actions" />
|
|
200
|
+
</DropdownTrigger>
|
|
201
|
+
<DropdownMenu />
|
|
202
|
+
</Dropdown>
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
`.trim()
|
|
206
|
+
|
|
207
|
+
const result = applyTransform(input)
|
|
208
|
+
expect(result).toContain("TODO: tapestry-migration (theme):")
|
|
209
|
+
expect(result).not.toContain("TODO: tapestry-migration (variant):")
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it("uses scope=variant/theme when both are present on custom trigger", () => {
|
|
213
|
+
const input = `
|
|
214
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
215
|
+
import { DropdownMenu, DropdownTrigger } from "@planningcenter/tapestry"
|
|
216
|
+
|
|
217
|
+
export default function Test() {
|
|
218
|
+
return (
|
|
219
|
+
<Dropdown theme="error" variant="outline" onClose={fn}>
|
|
220
|
+
<DropdownTrigger>
|
|
221
|
+
<MyCustomButton label="Actions" />
|
|
222
|
+
</DropdownTrigger>
|
|
223
|
+
<DropdownMenu />
|
|
224
|
+
</Dropdown>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
`.trim()
|
|
228
|
+
|
|
229
|
+
const result = applyTransform(input)
|
|
230
|
+
expect(result).toContain("TODO: tapestry-migration (variant/theme):")
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
describe("no changes scenarios", () => {
|
|
235
|
+
it("returns null for dynamic variant expression", () => {
|
|
236
|
+
expect(applyTransform(wrappedInput("variant={buttonVariant}"))).toBe(null)
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it("returns null for dynamic theme expression", () => {
|
|
240
|
+
expect(applyTransform(wrappedInput("theme={buttonTheme}"))).toBe(null)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it("returns null for an unmapped variant value", () => {
|
|
244
|
+
expect(applyTransform(wrappedInput('variant="extra-large"'))).toBe(null)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it("returns null for an unmapped theme value", () => {
|
|
248
|
+
expect(applyTransform(wrappedInput('theme="unknown-theme"'))).toBe(null)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it("returns null when no DropdownTrigger child exists", () => {
|
|
252
|
+
const input = `
|
|
253
|
+
import { Dropdown } from "@planningcenter/tapestry-react"
|
|
254
|
+
|
|
255
|
+
export default function Test() {
|
|
256
|
+
return (
|
|
257
|
+
<Dropdown variant="outline" onClose={fn}>
|
|
258
|
+
<DropdownMenu />
|
|
259
|
+
</Dropdown>
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
`.trim()
|
|
263
|
+
|
|
264
|
+
expect(applyTransform(input)).toBe(null)
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it("returns null when neither variant nor theme is present", () => {
|
|
268
|
+
expect(applyTransform(wrappedInput(""))).toBe(null)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it("returns null when Dropdown is not imported from tapestry-react", () => {
|
|
272
|
+
const input = `
|
|
273
|
+
import { Dropdown } from "@planningcenter/tapestry"
|
|
274
|
+
|
|
275
|
+
export default function Test() {
|
|
276
|
+
return (
|
|
277
|
+
<Dropdown variant="outline">
|
|
278
|
+
<DropdownTrigger><DropdownButton label="Actions" /></DropdownTrigger>
|
|
279
|
+
<DropdownMenu />
|
|
280
|
+
</Dropdown>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
`.trim()
|
|
284
|
+
|
|
285
|
+
expect(applyTransform(input)).toBe(null)
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it("returns null for empty file", () => {
|
|
289
|
+
expect(applyTransform("")).toBe(null)
|
|
290
|
+
})
|
|
291
|
+
})
|
|
292
|
+
})
|