@planningcenter/tapestry-migration-cli 3.4.1-rc.9 → 3.4.1

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.
Files changed (31) hide show
  1. package/package.json +3 -3
  2. package/src/components/dropdown/index.test.ts +177 -4
  3. package/src/components/dropdown/index.ts +24 -2
  4. package/src/components/dropdown/transforms/auditSpreadProps.test.ts +110 -0
  5. package/src/components/dropdown/transforms/auditSpreadProps.ts +10 -0
  6. package/src/components/dropdown/transforms/dividerToSeparator.test.ts +134 -0
  7. package/src/components/dropdown/transforms/dividerToSeparator.ts +67 -0
  8. package/src/components/dropdown/transforms/itemToAction.test.ts +19 -1
  9. package/src/components/dropdown/transforms/itemToAction.ts +14 -2
  10. package/src/components/dropdown/transforms/linkToLink.test.ts +19 -1
  11. package/src/components/dropdown/transforms/linkToLink.ts +13 -2
  12. package/src/components/dropdown/transforms/moveDropdownImport.test.ts +93 -0
  13. package/src/components/dropdown/transforms/moveDropdownImport.ts +13 -0
  14. package/src/components/dropdown/transforms/placementIdToMenu.test.ts +135 -0
  15. package/src/components/dropdown/transforms/placementIdToMenu.ts +96 -0
  16. package/src/components/dropdown/transforms/unsupportedProps.test.ts +145 -0
  17. package/src/components/dropdown/transforms/unsupportedProps.ts +12 -0
  18. package/src/components/dropdown/transforms/unsupportedPropsDivider.test.ts +143 -0
  19. package/src/components/dropdown/transforms/unsupportedPropsDivider.ts +26 -0
  20. package/src/components/dropdown/transforms/unsupportedPropsItem.test.ts +123 -0
  21. package/src/components/dropdown/transforms/unsupportedPropsItem.ts +12 -0
  22. package/src/components/dropdown/transforms/unsupportedPropsLink.test.ts +107 -0
  23. package/src/components/dropdown/transforms/unsupportedPropsLink.ts +12 -0
  24. package/src/components/dropdown/transforms/wrapMenu.test.ts +153 -0
  25. package/src/components/dropdown/transforms/wrapMenu.ts +54 -0
  26. package/src/components/dropdown/transforms/wrapTrigger.test.ts +283 -0
  27. package/src/components/dropdown/transforms/wrapTrigger.ts +98 -0
  28. package/src/components/input/transforms/unsupportedProps.test.ts +14 -13
  29. package/src/components/shared/conditions/isChildOf.test.ts +89 -0
  30. package/src/components/shared/conditions/isChildOf.ts +43 -0
  31. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +36 -0
@@ -0,0 +1,26 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { isChildOf } from "../../shared/conditions/isChildOf"
4
+ import { getImportName } from "../../shared/transformFactories/helpers/manageImports"
5
+ import { unsupportedPropsFactory } from "../../shared/transformFactories/unsupportedPropsFactory"
6
+
7
+ const transform: Transform = (fileInfo, api, options) => {
8
+ const j = api.jscodeshift
9
+ const source = j(fileInfo.source)
10
+
11
+ const dropdownLocalName = getImportName(
12
+ "Dropdown",
13
+ "@planningcenter/tapestry-react",
14
+ { j, source }
15
+ )
16
+ if (!dropdownLocalName) return null
17
+
18
+ return unsupportedPropsFactory({
19
+ condition: isChildOf(dropdownLocalName, source, j),
20
+ supportedProps: [],
21
+ targetComponent: "Divider",
22
+ targetPackage: "@planningcenter/tapestry-react",
23
+ })(fileInfo, api, options)
24
+ }
25
+
26
+ export default transform
@@ -0,0 +1,123 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./unsupportedPropsItem"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ return (
11
+ (transform(
12
+ fileInfo,
13
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
14
+ {}
15
+ ) as string | null) || source
16
+ )
17
+ }
18
+
19
+ describe("unsupportedPropsItem transform", () => {
20
+ describe("flags unsupported props", () => {
21
+ it("flags as prop (polymorphism)", () => {
22
+ const input = `
23
+ import { Dropdown } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return (
27
+ <Dropdown title="Actions">
28
+ <Dropdown.Item as={Link} to="/edit" onSelect={fn}>Edit</Dropdown.Item>
29
+ </Dropdown>
30
+ )
31
+ }
32
+ `.trim()
33
+
34
+ const result = applyTransform(input)
35
+ expect(result).toContain("TODO: tapestry-migration (as)")
36
+ })
37
+
38
+ it("flags custom unsupported prop", () => {
39
+ const input = `
40
+ import { Dropdown } from "@planningcenter/tapestry-react"
41
+
42
+ export default function Test() {
43
+ return (
44
+ <Dropdown title="Actions">
45
+ <Dropdown.Item onSelect={fn} customProp="value">Edit</Dropdown.Item>
46
+ </Dropdown>
47
+ )
48
+ }
49
+ `.trim()
50
+
51
+ const result = applyTransform(input)
52
+ expect(result).toContain("TODO: tapestry-migration (customProp)")
53
+ })
54
+ })
55
+
56
+ describe("does not flag supported props", () => {
57
+ it("does not flag onSelect, value, text, disabled, textValue", () => {
58
+ const input = `
59
+ import { Dropdown } from "@planningcenter/tapestry-react"
60
+
61
+ export default function Test() {
62
+ return (
63
+ <Dropdown title="Actions">
64
+ <Dropdown.Item onSelect={fn} value="edit" text="Edit" disabled textValue="Edit">Edit</Dropdown.Item>
65
+ </Dropdown>
66
+ )
67
+ }
68
+ `.trim()
69
+
70
+ expect(applyTransform(input)).toBe(input)
71
+ })
72
+
73
+ it("does not flag destructive", () => {
74
+ const input = `
75
+ import { Dropdown } from "@planningcenter/tapestry-react"
76
+
77
+ export default function Test() {
78
+ return (
79
+ <Dropdown title="Actions">
80
+ <Dropdown.Item onSelect={fn} destructive>Delete</Dropdown.Item>
81
+ </Dropdown>
82
+ )
83
+ }
84
+ `.trim()
85
+
86
+ expect(applyTransform(input)).toBe(input)
87
+ })
88
+
89
+ it("does not flag aria- and data- attributes", () => {
90
+ const input = `
91
+ import { Dropdown } from "@planningcenter/tapestry-react"
92
+
93
+ export default function Test() {
94
+ return (
95
+ <Dropdown title="Actions">
96
+ <Dropdown.Item onSelect={fn} aria-label="Edit" data-pendo="edit-item">Edit</Dropdown.Item>
97
+ </Dropdown>
98
+ )
99
+ }
100
+ `.trim()
101
+
102
+ expect(applyTransform(input)).toBe(input)
103
+ })
104
+ })
105
+
106
+ describe("no changes scenarios", () => {
107
+ it("returns source unchanged when not from tapestry-react", () => {
108
+ const input = `
109
+ import { Dropdown } from "some-other-library"
110
+
111
+ export default function Test() {
112
+ return <Dropdown.Item as={Link}>Edit</Dropdown.Item>
113
+ }
114
+ `.trim()
115
+
116
+ expect(applyTransform(input)).toBe(input)
117
+ })
118
+
119
+ it("returns source unchanged for empty file", () => {
120
+ expect(applyTransform("")).toBe("")
121
+ })
122
+ })
123
+ })
@@ -0,0 +1,12 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { DROPDOWN_ITEM_SUPPORTED_PROPS } from "../../shared/helpers/unsupportedPropsHelpers"
4
+ import { unsupportedPropsFactory } from "../../shared/transformFactories/unsupportedPropsFactory"
5
+
6
+ const transform: Transform = unsupportedPropsFactory({
7
+ supportedProps: DROPDOWN_ITEM_SUPPORTED_PROPS,
8
+ targetComponent: "Dropdown.Item",
9
+ targetPackage: "@planningcenter/tapestry-react",
10
+ })
11
+
12
+ export default transform
@@ -0,0 +1,107 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./unsupportedPropsLink"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ return (
11
+ (transform(
12
+ fileInfo,
13
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
14
+ {}
15
+ ) as string | null) || source
16
+ )
17
+ }
18
+
19
+ describe("unsupportedPropsLink transform", () => {
20
+ describe("flags unsupported props", () => {
21
+ it("flags disabled (not supported on links)", () => {
22
+ const input = `
23
+ import { Dropdown } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return (
27
+ <Dropdown title="Actions">
28
+ <Dropdown.Link to="/edit" disabled>Edit</Dropdown.Link>
29
+ </Dropdown>
30
+ )
31
+ }
32
+ `.trim()
33
+
34
+ const result = applyTransform(input)
35
+ expect(result).toContain("TODO: tapestry-migration (disabled)")
36
+ })
37
+
38
+ it("flags custom unsupported prop", () => {
39
+ const input = `
40
+ import { Dropdown } from "@planningcenter/tapestry-react"
41
+
42
+ export default function Test() {
43
+ return (
44
+ <Dropdown title="Actions">
45
+ <Dropdown.Link to="/edit" customProp="value">Edit</Dropdown.Link>
46
+ </Dropdown>
47
+ )
48
+ }
49
+ `.trim()
50
+
51
+ const result = applyTransform(input)
52
+ expect(result).toContain("TODO: tapestry-migration (customProp)")
53
+ })
54
+ })
55
+
56
+ describe("does not flag supported props", () => {
57
+ it("does not flag to, external, href, textValue, destructive", () => {
58
+ const input = `
59
+ import { Dropdown } from "@planningcenter/tapestry-react"
60
+
61
+ export default function Test() {
62
+ return (
63
+ <Dropdown title="Actions">
64
+ <Dropdown.Link to="/docs" external href="/docs" textValue="Docs" destructive>Docs</Dropdown.Link>
65
+ </Dropdown>
66
+ )
67
+ }
68
+ `.trim()
69
+
70
+ expect(applyTransform(input)).toBe(input)
71
+ })
72
+
73
+ it("does not flag aria- and data- attributes", () => {
74
+ const input = `
75
+ import { Dropdown } from "@planningcenter/tapestry-react"
76
+
77
+ export default function Test() {
78
+ return (
79
+ <Dropdown title="Actions">
80
+ <Dropdown.Link to="/edit" aria-label="Edit" data-pendo="edit-link">Edit</Dropdown.Link>
81
+ </Dropdown>
82
+ )
83
+ }
84
+ `.trim()
85
+
86
+ expect(applyTransform(input)).toBe(input)
87
+ })
88
+ })
89
+
90
+ describe("no changes scenarios", () => {
91
+ it("returns source unchanged when not from tapestry-react", () => {
92
+ const input = `
93
+ import { Dropdown } from "some-other-library"
94
+
95
+ export default function Test() {
96
+ return <Dropdown.Link to="/edit" disabled>Edit</Dropdown.Link>
97
+ }
98
+ `.trim()
99
+
100
+ expect(applyTransform(input)).toBe(input)
101
+ })
102
+
103
+ it("returns source unchanged for empty file", () => {
104
+ expect(applyTransform("")).toBe("")
105
+ })
106
+ })
107
+ })
@@ -0,0 +1,12 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { DROPDOWN_LINK_SUPPORTED_PROPS } from "../../shared/helpers/unsupportedPropsHelpers"
4
+ import { unsupportedPropsFactory } from "../../shared/transformFactories/unsupportedPropsFactory"
5
+
6
+ const transform: Transform = unsupportedPropsFactory({
7
+ supportedProps: DROPDOWN_LINK_SUPPORTED_PROPS,
8
+ targetComponent: "Dropdown.Link",
9
+ targetPackage: "@planningcenter/tapestry-react",
10
+ })
11
+
12
+ export default transform
@@ -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