@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
@@ -3,11 +3,12 @@ import { Transform } from "jscodeshift"
3
3
  import { transformAttributeName } from "../../shared/actions/transformAttributeName"
4
4
  import { transformElementName } from "../../shared/actions/transformElementName"
5
5
  import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+ import { addImport } from "../../shared/transformFactories/helpers/manageImports"
6
7
 
7
8
  const transform: Transform = attributeTransformFactory({
8
9
  targetComponent: "Dropdown.Link",
9
10
  targetPackage: "@planningcenter/tapestry-react",
10
- transform: (element, { j, options }) => {
11
+ transform: (element, { j, options, source }) => {
11
12
  const toRenamed = transformAttributeName("to", "href", {
12
13
  element,
13
14
  j,
@@ -17,7 +18,17 @@ const transform: Transform = attributeTransformFactory({
17
18
  element,
18
19
  name: "DropdownLink",
19
20
  })
20
- return toRenamed || elementRenamed
21
+ const changed = toRenamed || elementRenamed
22
+ if (changed) {
23
+ addImport({
24
+ component: "DropdownLink",
25
+ fromPackage: "@planningcenter/tapestry-react",
26
+ j,
27
+ pkg: "@planningcenter/tapestry",
28
+ source,
29
+ })
30
+ }
31
+ return changed
21
32
  },
22
33
  })
23
34
 
@@ -0,0 +1,93 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./moveDropdownImport"
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("moveDropdownImport transform", () => {
18
+ it("moves Dropdown import from tapestry-react to tapestry", () => {
19
+ const input = `
20
+ import { Dropdown } from "@planningcenter/tapestry-react"
21
+
22
+ export default function Test() {
23
+ return <Dropdown onClose={fn}><DropdownMenu /></Dropdown>
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(input)
28
+ expect(result).toContain(
29
+ 'import { Dropdown } from "@planningcenter/tapestry"'
30
+ )
31
+ expect(result).not.toContain('from "@planningcenter/tapestry-react"')
32
+ })
33
+
34
+ it("merges Dropdown into an existing tapestry import", () => {
35
+ const input = `
36
+ import { Dropdown } from "@planningcenter/tapestry-react"
37
+ import { DropdownAction, DropdownMenu, DropdownTrigger } from "@planningcenter/tapestry"
38
+
39
+ export default function Test() {
40
+ return (
41
+ <Dropdown onClose={fn}>
42
+ <DropdownTrigger><DropdownButton label="Actions" /></DropdownTrigger>
43
+ <DropdownMenu><DropdownAction onAction={fn}>Edit</DropdownAction></DropdownMenu>
44
+ </Dropdown>
45
+ )
46
+ }
47
+ `.trim()
48
+
49
+ const result = applyTransform(input)
50
+ expect(result).toContain("Dropdown")
51
+ expect(result).toContain("DropdownAction")
52
+ expect(result).toContain("DropdownMenu")
53
+ expect(result).toContain("DropdownTrigger")
54
+ expect(result).toContain('from "@planningcenter/tapestry"')
55
+ expect(result).not.toContain('from "@planningcenter/tapestry-react"')
56
+ })
57
+
58
+ it("preserves other tapestry-react imports", () => {
59
+ const input = `
60
+ import { Dropdown, Button } from "@planningcenter/tapestry-react"
61
+
62
+ export default function Test() {
63
+ return <Dropdown onClose={fn}><DropdownMenu /></Dropdown>
64
+ }
65
+ `.trim()
66
+
67
+ const result = applyTransform(input)
68
+ expect(result).toContain(
69
+ 'import { Button } from "@planningcenter/tapestry-react"'
70
+ )
71
+ expect(result).toContain(
72
+ 'import { Dropdown } from "@planningcenter/tapestry"'
73
+ )
74
+ })
75
+
76
+ describe("no changes scenarios", () => {
77
+ it("returns null when Dropdown is not imported from tapestry-react", () => {
78
+ const input = `
79
+ import { Dropdown } from "@planningcenter/tapestry"
80
+
81
+ export default function Test() {
82
+ return <Dropdown onClose={fn}><DropdownMenu /></Dropdown>
83
+ }
84
+ `.trim()
85
+
86
+ expect(applyTransform(input)).toBe(null)
87
+ })
88
+
89
+ it("returns null for empty file", () => {
90
+ expect(applyTransform("")).toBe(null)
91
+ })
92
+ })
93
+ })
@@ -0,0 +1,13 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
4
+
5
+ const transform: Transform = componentTransformFactory({
6
+ condition: () => true,
7
+ fromComponent: "Dropdown",
8
+ fromPackage: "@planningcenter/tapestry-react",
9
+ toComponent: "Dropdown",
10
+ toPackage: "@planningcenter/tapestry",
11
+ })
12
+
13
+ export default transform
@@ -0,0 +1,135 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./placementIdToMenu"
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
+ const withMenu = (
18
+ attrs: string,
19
+ items = "<Dropdown.Item onSelect={fn}>Edit</Dropdown.Item>"
20
+ ) =>
21
+ `
22
+ import { Dropdown } from "@planningcenter/tapestry-react"
23
+
24
+ export default function Test() {
25
+ return (
26
+ <Dropdown ${attrs}>
27
+ <DropdownMenu>
28
+ ${items}
29
+ </DropdownMenu>
30
+ </Dropdown>
31
+ )
32
+ }
33
+ `.trim()
34
+
35
+ describe("placementIdToMenu transform", () => {
36
+ describe("placement", () => {
37
+ it("moves placement string literal to DropdownMenu with dash-to-space conversion", () => {
38
+ const result = applyTransform(withMenu('placement="bottom-start"'))
39
+ expect(result).toContain('<DropdownMenu placement="bottom start"')
40
+ expect(result).not.toMatch(/<Dropdown\s[^>]*placement/)
41
+ })
42
+
43
+ it("converts all dash-separated placement values", () => {
44
+ const result = applyTransform(withMenu('placement="top-end"'))
45
+ expect(result).toContain('<DropdownMenu placement="top end"')
46
+ })
47
+
48
+ it("moves placement from JSXExpressionContainer string literal", () => {
49
+ const result = applyTransform(withMenu('placement={"bottom-end"}'))
50
+ expect(result).toContain('<DropdownMenu placement={"bottom end"}')
51
+ })
52
+
53
+ it("moves dynamic placement expression and adds TODO comment", () => {
54
+ const result = applyTransform(withMenu("placement={myPlacement}"))
55
+ expect(result).toContain("<DropdownMenu placement={myPlacement}")
56
+ expect(result).toContain(
57
+ "TODO: tapestry-migration (placement): placement is a dynamic expression"
58
+ )
59
+ })
60
+ })
61
+
62
+ describe("id", () => {
63
+ it("moves id string literal to DropdownMenu", () => {
64
+ const result = applyTransform(withMenu('id="my-menu"'))
65
+ expect(result).toContain('<DropdownMenu id="my-menu"')
66
+ expect(result).not.toMatch(/<Dropdown\s[^>]*\bid=/)
67
+ })
68
+
69
+ it("moves dynamic id expression to DropdownMenu", () => {
70
+ const result = applyTransform(withMenu("id={menuId}"))
71
+ expect(result).toContain("<DropdownMenu id={menuId}")
72
+ })
73
+ })
74
+
75
+ describe("placement and id together", () => {
76
+ it("moves both placement and id to DropdownMenu", () => {
77
+ const result = applyTransform(
78
+ withMenu('placement="bottom-start" id="my-menu"')
79
+ )
80
+ expect(result).toContain("placement=")
81
+ expect(result).toContain("id=")
82
+ expect(result).toContain("<DropdownMenu")
83
+ expect(result).not.toMatch(/<Dropdown\s[^>]*placement/)
84
+ expect(result).not.toMatch(/<Dropdown\s[^>]*\bid=/)
85
+ })
86
+ })
87
+
88
+ describe("no changes scenarios", () => {
89
+ it("returns null when neither placement nor id is present", () => {
90
+ const result = applyTransform(withMenu("onClose={handleClose}"))
91
+ expect(result).toBe(null)
92
+ })
93
+
94
+ it("adds TODO when DropdownMenu child is not found", () => {
95
+ const input = `
96
+ import { Dropdown } from "@planningcenter/tapestry-react"
97
+
98
+ export default function Test() {
99
+ return <Dropdown placement="bottom-start">{children}</Dropdown>
100
+ }
101
+ `.trim()
102
+
103
+ const result = applyTransform(input)
104
+ expect(result).toContain("TODO: tapestry-migration (placement)")
105
+ expect(result).toContain("no DropdownMenu child found")
106
+ })
107
+
108
+ it("returns null when Dropdown is not imported from tapestry-react", () => {
109
+ const input = `
110
+ import { Dropdown } from "some-other-library"
111
+
112
+ export default function Test() {
113
+ return (
114
+ <Dropdown placement="bottom-start">
115
+ <DropdownMenu><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></DropdownMenu>
116
+ </Dropdown>
117
+ )
118
+ }
119
+ `.trim()
120
+
121
+ expect(applyTransform(input)).toBe(null)
122
+ })
123
+
124
+ it("does not touch the import statement", () => {
125
+ const result = applyTransform(withMenu('placement="bottom-start"'))
126
+ expect(result).toContain(
127
+ 'import { Dropdown } from "@planningcenter/tapestry-react"'
128
+ )
129
+ })
130
+
131
+ it("returns null for empty file", () => {
132
+ expect(applyTransform("")).toBe(null)
133
+ })
134
+ })
135
+ })
@@ -0,0 +1,96 @@
1
+ import { JSXAttribute, JSXElement, Transform } from "jscodeshift"
2
+
3
+ import { addComment } from "../../shared/actions/addComment"
4
+ import { getAttribute } from "../../shared/actions/getAttribute"
5
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+
8
+ function findDropdownMenu(element: JSXElement): JSXElement | undefined {
9
+ return (element.children ?? []).find(
10
+ (child): child is JSXElement =>
11
+ child.type === "JSXElement" &&
12
+ child.openingElement.name.type === "JSXIdentifier" &&
13
+ child.openingElement.name.name === "DropdownMenu"
14
+ )
15
+ }
16
+
17
+ function applyPlacementConversion(attr: JSXAttribute): { isDynamic: boolean } {
18
+ const val = attr.value
19
+ if (val?.type === "StringLiteral") {
20
+ val.value = val.value.replace(/-/g, " ")
21
+ return { isDynamic: false }
22
+ }
23
+ if (val?.type === "JSXExpressionContainer") {
24
+ if (val.expression.type === "StringLiteral") {
25
+ val.expression.value = val.expression.value.replace(/-/g, " ")
26
+ return { isDynamic: false }
27
+ }
28
+ if (val.expression.type !== "JSXEmptyExpression") {
29
+ return { isDynamic: true }
30
+ }
31
+ }
32
+ return { isDynamic: false }
33
+ }
34
+
35
+ function moveAttr(
36
+ name: string,
37
+ attr: JSXAttribute,
38
+ from: JSXElement,
39
+ to: JSXElement,
40
+ {
41
+ j,
42
+ source,
43
+ }: {
44
+ j: Parameters<typeof removeAttribute>[1]["j"]
45
+ source: Parameters<typeof removeAttribute>[1]["source"]
46
+ }
47
+ ) {
48
+ to.openingElement.attributes = [...(to.openingElement.attributes ?? []), attr]
49
+ removeAttribute(name, { element: from, j, source })
50
+ }
51
+
52
+ const transform: Transform = attributeTransformFactory({
53
+ targetComponent: "Dropdown",
54
+ targetPackage: "@planningcenter/tapestry-react",
55
+ transform: (element, { j, source }) => {
56
+ const placementAttr = getAttribute({ element, name: "placement" })
57
+ const idAttr = getAttribute({ element, name: "id" })
58
+
59
+ if (!placementAttr && !idAttr) return false
60
+
61
+ const menuChild = findDropdownMenu(element)
62
+
63
+ if (!menuChild) {
64
+ addComment({
65
+ element,
66
+ j,
67
+ scope: "placement",
68
+ source,
69
+ text: "placement/id could not be moved to DropdownMenu — no DropdownMenu child found",
70
+ })
71
+ return true
72
+ }
73
+
74
+ if (idAttr) {
75
+ moveAttr("id", idAttr, element, menuChild, { j, source })
76
+ }
77
+
78
+ if (placementAttr) {
79
+ const { isDynamic } = applyPlacementConversion(placementAttr)
80
+ moveAttr("placement", placementAttr, element, menuChild, { j, source })
81
+ if (isDynamic) {
82
+ addComment({
83
+ element,
84
+ j,
85
+ scope: "placement",
86
+ source,
87
+ text: "placement is a dynamic expression — dash-to-space conversion (e.g. 'bottom-start' → 'bottom start') must be done manually",
88
+ })
89
+ }
90
+ }
91
+
92
+ return true
93
+ },
94
+ })
95
+
96
+ export default transform
@@ -0,0 +1,145 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./unsupportedProps"
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("unsupportedProps transform", () => {
20
+ describe("flags removed props", () => {
21
+ it("flags variant", () => {
22
+ const input = `
23
+ import { Dropdown } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return <Dropdown variant="outline" onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
27
+ }
28
+ `.trim()
29
+
30
+ const result = applyTransform(input)
31
+ expect(result).toContain("TODO: tapestry-migration (variant)")
32
+ expect(result).toContain("variant")
33
+ })
34
+
35
+ it("flags theme", () => {
36
+ const input = `
37
+ import { Dropdown } from "@planningcenter/tapestry-react"
38
+
39
+ export default function Test() {
40
+ return <Dropdown theme="primary" onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
41
+ }
42
+ `.trim()
43
+
44
+ const result = applyTransform(input)
45
+ expect(result).toContain("TODO: tapestry-migration (theme)")
46
+ })
47
+
48
+ it("flags size", () => {
49
+ const input = `
50
+ import { Dropdown } from "@planningcenter/tapestry-react"
51
+
52
+ export default function Test() {
53
+ return <Dropdown size="md" onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
54
+ }
55
+ `.trim()
56
+
57
+ const result = applyTransform(input)
58
+ expect(result).toContain("TODO: tapestry-migration (size)")
59
+ })
60
+
61
+ it("flags popoverProps", () => {
62
+ const input = `
63
+ import { Dropdown } from "@planningcenter/tapestry-react"
64
+
65
+ export default function Test() {
66
+ return <Dropdown popoverProps={{ offset: 8 }} onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
67
+ }
68
+ `.trim()
69
+
70
+ const result = applyTransform(input)
71
+ expect(result).toContain("TODO: tapestry-migration (popoverProps)")
72
+ })
73
+
74
+ it("flags multiple unsupported props", () => {
75
+ const input = `
76
+ import { Dropdown } from "@planningcenter/tapestry-react"
77
+
78
+ export default function Test() {
79
+ return <Dropdown variant="outline" theme="primary" css={{ color: "red" }} onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
80
+ }
81
+ `.trim()
82
+
83
+ const result = applyTransform(input)
84
+ expect(result).toContain("TODO: tapestry-migration (variant)")
85
+ expect(result).toContain("TODO: tapestry-migration (theme)")
86
+ expect(result).toContain("TODO: tapestry-migration (css)")
87
+ })
88
+ })
89
+
90
+ describe("does not flag supported props", () => {
91
+ it("does not flag onClose or onOpen", () => {
92
+ const input = `
93
+ import { Dropdown } from "@planningcenter/tapestry-react"
94
+
95
+ export default function Test() {
96
+ return <Dropdown onClose={handleClose} onOpen={handleOpen}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
97
+ }
98
+ `.trim()
99
+
100
+ expect(applyTransform(input)).toBe(input)
101
+ })
102
+
103
+ it("does not flag className or ref", () => {
104
+ const input = `
105
+ import { Dropdown } from "@planningcenter/tapestry-react"
106
+
107
+ export default function Test() {
108
+ return <Dropdown className="my-dropdown" ref={ref} onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
109
+ }
110
+ `.trim()
111
+
112
+ expect(applyTransform(input)).toBe(input)
113
+ })
114
+
115
+ it("does not flag aria- and data- attributes", () => {
116
+ const input = `
117
+ import { Dropdown } from "@planningcenter/tapestry-react"
118
+
119
+ export default function Test() {
120
+ return <Dropdown aria-label="Options" data-pendo="dropdown" onClose={fn}><Dropdown.Item onSelect={fn}>Edit</Dropdown.Item></Dropdown>
121
+ }
122
+ `.trim()
123
+
124
+ expect(applyTransform(input)).toBe(input)
125
+ })
126
+ })
127
+
128
+ describe("no changes scenarios", () => {
129
+ it("returns source unchanged when not from tapestry-react", () => {
130
+ const input = `
131
+ import { Dropdown } from "some-other-library"
132
+
133
+ export default function Test() {
134
+ return <Dropdown variant="outline"><Dropdown.Item>Edit</Dropdown.Item></Dropdown>
135
+ }
136
+ `.trim()
137
+
138
+ expect(applyTransform(input)).toBe(input)
139
+ })
140
+
141
+ it("returns source unchanged for empty file", () => {
142
+ expect(applyTransform("")).toBe("")
143
+ })
144
+ })
145
+ })
@@ -0,0 +1,12 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { DROPDOWN_SUPPORTED_PROPS } from "../../shared/helpers/unsupportedPropsHelpers"
4
+ import { unsupportedPropsFactory } from "../../shared/transformFactories/unsupportedPropsFactory"
5
+
6
+ const transform: Transform = unsupportedPropsFactory({
7
+ supportedProps: DROPDOWN_SUPPORTED_PROPS,
8
+ targetComponent: "Dropdown",
9
+ targetPackage: "@planningcenter/tapestry-react",
10
+ })
11
+
12
+ export default transform
@@ -0,0 +1,143 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./unsupportedPropsDivider"
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("unsupportedPropsDivider transform", () => {
20
+ describe("flags Divider-specific props when inside a Dropdown", () => {
21
+ it("flags axis", () => {
22
+ const input = `
23
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return <Dropdown><Divider axis="vertical" /></Dropdown>
27
+ }
28
+ `.trim()
29
+
30
+ expect(applyTransform(input)).toContain("TODO: tapestry-migration (axis)")
31
+ })
32
+
33
+ it("flags color", () => {
34
+ const input = `
35
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
36
+
37
+ export default function Test() {
38
+ return <Dropdown><Divider color="neutral100" /></Dropdown>
39
+ }
40
+ `.trim()
41
+
42
+ expect(applyTransform(input)).toContain(
43
+ "TODO: tapestry-migration (color)"
44
+ )
45
+ })
46
+
47
+ it("flags margin", () => {
48
+ const input = `
49
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
50
+
51
+ export default function Test() {
52
+ return <Dropdown><Divider margin={8} /></Dropdown>
53
+ }
54
+ `.trim()
55
+
56
+ expect(applyTransform(input)).toContain(
57
+ "TODO: tapestry-migration (margin)"
58
+ )
59
+ })
60
+
61
+ it("flags size", () => {
62
+ const input = `
63
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
64
+
65
+ export default function Test() {
66
+ return <Dropdown><Divider size={2} /></Dropdown>
67
+ }
68
+ `.trim()
69
+
70
+ expect(applyTransform(input)).toContain("TODO: tapestry-migration (size)")
71
+ })
72
+
73
+ it("flags css (StyleProps)", () => {
74
+ const input = `
75
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
76
+
77
+ export default function Test() {
78
+ return <Dropdown><Divider css={{ marginTop: 8 }} /></Dropdown>
79
+ }
80
+ `.trim()
81
+
82
+ expect(applyTransform(input)).toContain("TODO: tapestry-migration (css)")
83
+ })
84
+
85
+ it("flags className and style (DropdownSeparator accepts no props)", () => {
86
+ const input = `
87
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
88
+
89
+ export default function Test() {
90
+ return <Dropdown><Divider className="my-divider" style={{ marginTop: 8 }} /></Dropdown>
91
+ }
92
+ `.trim()
93
+
94
+ const result = applyTransform(input)
95
+ expect(result).toContain("TODO: tapestry-migration (className)")
96
+ expect(result).toContain("TODO: tapestry-migration (style)")
97
+ })
98
+ })
99
+
100
+ describe("does not flag aria- and data- attributes", () => {
101
+ it("does not flag aria- and data- attributes inside a Dropdown", () => {
102
+ const input = `
103
+ import { Dropdown, Divider } from "@planningcenter/tapestry-react"
104
+
105
+ export default function Test() {
106
+ return <Dropdown><Divider aria-hidden="true" data-testid="divider" /></Dropdown>
107
+ }
108
+ `.trim()
109
+
110
+ expect(applyTransform(input)).toBe(input)
111
+ })
112
+ })
113
+
114
+ describe("no changes scenarios", () => {
115
+ it("returns source unchanged when Divider is not inside a Dropdown", () => {
116
+ const input = `
117
+ import { Divider } from "@planningcenter/tapestry-react"
118
+
119
+ export default function Test() {
120
+ return <Divider axis="vertical" />
121
+ }
122
+ `.trim()
123
+
124
+ expect(applyTransform(input)).toBe(input)
125
+ })
126
+
127
+ it("returns source unchanged when not from tapestry-react", () => {
128
+ const input = `
129
+ import { Divider } from "some-other-library"
130
+
131
+ export default function Test() {
132
+ return <Divider axis="vertical" />
133
+ }
134
+ `.trim()
135
+
136
+ expect(applyTransform(input)).toBe(input)
137
+ })
138
+
139
+ it("returns source unchanged for empty file", () => {
140
+ expect(applyTransform("")).toBe("")
141
+ })
142
+ })
143
+ })