@planningcenter/tapestry-migration-cli 2.4.0-rc.9 → 2.4.1-rc.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.
Files changed (42) hide show
  1. package/README.md +100 -6
  2. package/dist/tapestry-react-shim.cjs +1 -1
  3. package/package.json +2 -2
  4. package/src/components/button/index.ts +3 -3
  5. package/src/components/button/transforms/innerRefToRef.test.ts +170 -0
  6. package/src/components/button/transforms/innerRefToRef.ts +14 -0
  7. package/src/components/button/transforms/unsupportedProps.ts +8 -31
  8. package/src/components/checkbox/index.ts +38 -0
  9. package/src/components/checkbox/transforms/childrenToLabel.test.ts +146 -0
  10. package/src/components/checkbox/transforms/childrenToLabel.ts +54 -0
  11. package/src/components/checkbox/transforms/convertStyleProps.test.ts +161 -0
  12. package/src/components/checkbox/transforms/convertStyleProps.ts +10 -0
  13. package/src/components/checkbox/transforms/innerRefToRef.test.ts +161 -0
  14. package/src/components/checkbox/transforms/innerRefToRef.ts +14 -0
  15. package/src/components/checkbox/transforms/moveCheckboxImport.test.ts +182 -0
  16. package/src/components/checkbox/transforms/moveCheckboxImport.ts +13 -0
  17. package/src/components/checkbox/transforms/sizeMapping.test.ts +193 -0
  18. package/src/components/checkbox/transforms/sizeMapping.ts +45 -0
  19. package/src/components/checkbox/transforms/unsupportedProps.test.ts +243 -0
  20. package/src/components/checkbox/transforms/unsupportedProps.ts +47 -0
  21. package/src/components/link/index.ts +14 -4
  22. package/src/components/link/transforms/auditSpreadProps.test.ts +351 -0
  23. package/src/components/link/transforms/auditSpreadProps.ts +24 -0
  24. package/src/components/link/transforms/innerRefToRef.test.ts +170 -0
  25. package/src/components/link/transforms/innerRefToRef.ts +14 -0
  26. package/src/components/link/transforms/moveLinkImport.test.ts +295 -0
  27. package/src/components/{button/transforms/linkToButton.ts → link/transforms/moveLinkImport.ts} +4 -5
  28. package/src/components/link/transforms/{inlineMemberToKind.test.ts → removeInlineMember.test.ts} +13 -28
  29. package/src/components/link/transforms/removeInlineMember.ts +11 -0
  30. package/src/components/link/transforms/{inlinePropToKind.test.ts → removeInlineProp.test.ts} +12 -29
  31. package/src/components/link/transforms/{inlinePropToKind.ts → removeInlineProp.ts} +0 -9
  32. package/src/components/link/transforms/tooltipToWrapper.test.ts +392 -0
  33. package/src/components/link/transforms/tooltipToWrapper.ts +35 -0
  34. package/src/components/link/transforms/unsupportedProps.test.ts +265 -0
  35. package/src/components/link/transforms/unsupportedProps.ts +58 -0
  36. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +35 -0
  37. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +1 -0
  38. package/src/index.ts +18 -7
  39. package/src/jscodeshiftRunner.ts +9 -1
  40. package/src/reportGenerator.ts +23 -2
  41. package/src/components/button/transforms/linkToButton.test.ts +0 -426
  42. package/src/components/link/transforms/inlineMemberToKind.ts +0 -49
package/README.md CHANGED
@@ -1,12 +1,106 @@
1
1
  # tapestry-migration-cli
2
2
 
3
- A simple cli tool used to help to migration Tapestry React components to formats that match Planning Center specs (usually to `@planningcenter/tapestry` components).
3
+ A CLI tool to migrate Tapestry React components to Planning Center's Tapestry format.
4
4
 
5
- ## Quick Use
5
+ ## Installation
6
6
 
7
7
  ```bash
8
- # For help
9
- npx @planningcenter/tapestry-migration-cli
10
- # Run a migration with autofixing
11
- npx @planningcenter/tapestry-migration-cli run ComponentName -p ./path/to/files -f
8
+ yarn add --dev @planningcenter/tapestry-migration-cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Recommended Migration Order
14
+
15
+ For optimal results, migrate components in this order:
16
+
17
+ 1. **Button components first**: `npx @planningcenter/tapestry-migration-cli run button -p ./src/components --fix`
18
+ 2. **Link components second**: `npx @planningcenter/tapestry-migration-cli run link -p ./src/components --fix`
19
+
20
+ This order ensures that Button components with navigation props are properly handled before Link components are migrated.
21
+
22
+ ### Basic Commands
23
+
24
+ ```bash
25
+ # Run a dry-run migration (preview changes)
26
+ npx @planningcenter/tapestry-migration-cli run button -p ./src/components
27
+
28
+ # Apply the migration changes
29
+ npx @planningcenter/tapestry-migration-cli run button -p ./src/components --fix
30
+
31
+ # Run with verbose output
32
+ npx @planningcenter/tapestry-migration-cli run button -p ./src/components --fix --verbose
33
+
34
+ # Generate a migration report
35
+ npx @planningcenter/tapestry-migration-cli run button -p ./src/components --fix --report-path ./migration-report.md
36
+ ```
37
+
38
+ ## Available Components
39
+
40
+ - `button` - Migrate Button components
41
+ - `link` - Migrate Link components
42
+
43
+ ## Required Arguments
44
+
45
+ - `-p, --path <path>` - **REQUIRED**: Path to the folder or file to migrate
46
+
47
+ ## Optional Arguments
48
+
49
+ - `-f, --fix` - Write changes to files (without this flag, it's a dry run)
50
+ - `-v, --verbose` - Show detailed output
51
+ - `-j, --js-theme <path>` - Path to JavaScript theme file
52
+ - `-r, --report-path <path>` - Path for migration report (default: MIGRATION_REPORT.md)
53
+
54
+ ## How It Works
55
+
56
+ The migration tool automatically transforms your components to match Planning Center's current Tapestry specifications. Most property and usage changes are handled automatically, including:
57
+
58
+ - Converting deprecated props to their modern equivalents
59
+ - Updating import statements and component references
60
+ - Adjusting prop names and values to match current API
61
+
62
+ ### Unsupported Props
63
+
64
+ For properties that cannot be automatically migrated, the tool will:
65
+
66
+ 1. **Keep the prop as-is** - The prop remains in your code but will not work with the new Tapestry component
67
+ 2. **Add a TODO comment** - Flagging the prop for manual migration
68
+
69
+ Example of an unsupported prop comment:
70
+
71
+ ```tsx
72
+ <Button
73
+ /* TODO: tapestry-migration (propName): 'mediaQueries' is not supported, please migrate as needed.
74
+ * It is recommended to use CSS media queries in a class that you apply to the component.
75
+ */
76
+ mediaQueries={{ mobile: { fontSize: 14 } }}
77
+ >
78
+ Click me
79
+ </Button>
80
+ ```
81
+
82
+ The comment includes guidance on how to handle the unsupported prop. Common recommendations include:
83
+
84
+ - Using CSS classes for styling instead of props
85
+ - Moving state-based styles (hover, focus, active) to CSS selectors
86
+ - Applying responsive styles through CSS media queries
87
+
88
+ ## Alternative: Local Installation
89
+
90
+ If you prefer to install it locally and use a shorter command:
91
+
92
+ ```bash
93
+ # Install locally
94
+ yarn add --dev @planningcenter/tapestry-migration-cli
95
+
96
+ # Add to package.json scripts
97
+ {
98
+ "scripts": {
99
+ "migrate": "tapestry-migration-cli"
100
+ }
101
+ }
102
+
103
+ # Then run (following recommended order)
104
+ yarn migrate run button -p app/javascript/components --fix
105
+ yarn migrate run link -p app/javascript/components --fix
12
106
  ```
@@ -2681,7 +2681,7 @@ const PageHeaderActionsDropdownButton = reactExports.forwardRef(({ needsAttentio
2681
2681
  });
2682
2682
  PageHeaderActionsDropdownButton.displayName = "PageHeaderActionsDropdownButton";
2683
2683
  const buildComponentClassName = (size, kind, className) => {
2684
- return classNames(kind && "tds-btn", size && size !== "md" && kind && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], className);
2684
+ return classNames("tds-btn", size && size !== "md" && kind && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], className);
2685
2685
  };
2686
2686
  const BaseLink = reactExports.forwardRef(({ size, prefix, suffix, kind, label, children, className, href, external = false, ...restProps }, ref) => {
2687
2687
  const combinedClassName = buildComponentClassName(size, kind, className);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "2.4.0-rc.9",
3
+ "version": "2.4.1-rc.0",
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": "354ebc1e0619c2e82f46fe9b9c981b674803af3d"
54
+ "gitHead": "c643ce32f1e79ee51403daeaaeac4940729ab69d"
55
55
  }
@@ -8,7 +8,7 @@ import convertStyleProps from "./transforms/convertStyleProps"
8
8
  import iconLeftToPrefix from "./transforms/iconLeftToPrefix"
9
9
  import iconRightToSuffix from "./transforms/iconRightToSuffix"
10
10
  import iconToIconButton from "./transforms/iconToIconButton"
11
- import linkToButton from "./transforms/linkToButton"
11
+ import innerRefToRef from "./transforms/innerRefToRef"
12
12
  import moveButtonImport from "./transforms/moveButtonImport"
13
13
  import removeAsButton from "./transforms/removeAsButton"
14
14
  import removeDuplicateKeys from "./transforms/removeDuplicateKeys"
@@ -47,7 +47,6 @@ const transform: Transform = async (fileInfo, api, options) => {
47
47
  }
48
48
 
49
49
  const transforms = [
50
- linkToButton,
51
50
  tooltipToWrapper,
52
51
  iconRightToSuffix,
53
52
  iconLeftToPrefix,
@@ -55,16 +54,17 @@ const transform: Transform = async (fileInfo, api, options) => {
55
54
  themeVariantToKind,
56
55
  titleToLabel,
57
56
  childrenToLabel,
57
+ innerRefToRef,
58
58
  removeToTransform,
59
59
  removeDuplicateKeys,
60
60
  removeAsButton,
61
61
  removeTypeButton,
62
62
  auditSpreadProps,
63
63
  reviewStyles,
64
+ spinnerToLoadingButton,
64
65
  convertStyleProps,
65
66
  unsupportedProps,
66
67
  iconToIconButton,
67
- spinnerToLoadingButton,
68
68
  moveButtonImport,
69
69
  ]
70
70
 
@@ -0,0 +1,170 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./innerRefToRef"
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("innerRefToRef transform", () => {
18
+ describe("basic transformations", () => {
19
+ it("should transform Button 'innerRef' prop to 'ref' prop", () => {
20
+ const input = `
21
+ import { Button } from "@planningcenter/tapestry-react"
22
+
23
+ export default function Test() {
24
+ return <Button innerRef={buttonRef}>Click me</Button>
25
+ }
26
+ `.trim()
27
+
28
+ const expected = `
29
+ import { Button } from "@planningcenter/tapestry-react"
30
+
31
+ export default function Test() {
32
+ return <Button ref={buttonRef}>Click me</Button>;
33
+ }
34
+ `.trim()
35
+
36
+ const result = applyTransform(input)
37
+ expect(result).toBe(expected)
38
+ })
39
+
40
+ it("should transform Button 'innerRef' prop to 'ref' prop with function", () => {
41
+ const input = `
42
+ import { Button } from "@planningcenter/tapestry-react"
43
+
44
+ export default function Test() {
45
+ return <Button innerRef={(el) => setButtonRef(el)}>Click me</Button>
46
+ }
47
+ `.trim()
48
+
49
+ const expected = `
50
+ import { Button } from "@planningcenter/tapestry-react"
51
+
52
+ export default function Test() {
53
+ return <Button ref={(el) => setButtonRef(el)}>Click me</Button>;
54
+ }
55
+ `.trim()
56
+
57
+ const result = applyTransform(input)
58
+ expect(result).toBe(expected)
59
+ })
60
+
61
+ it("should transform Button 'innerRef' prop to 'ref' prop with useRef", () => {
62
+ const input = `
63
+ import { Button } from "@planningcenter/tapestry-react"
64
+ import { useRef } from "react"
65
+
66
+ export default function Test() {
67
+ const buttonRef = useRef(null)
68
+ return <Button innerRef={buttonRef}>Click me</Button>
69
+ }
70
+ `.trim()
71
+
72
+ const expected = `
73
+ import { Button } from "@planningcenter/tapestry-react"
74
+ import { useRef } from "react"
75
+
76
+ export default function Test() {
77
+ const buttonRef = useRef(null)
78
+ return <Button ref={buttonRef}>Click me</Button>;
79
+ }
80
+ `.trim()
81
+
82
+ const result = applyTransform(input)
83
+ expect(result).toBe(expected)
84
+ })
85
+ })
86
+
87
+ describe("multiple components", () => {
88
+ it("should transform multiple Button components with innerRef", () => {
89
+ const input = `
90
+ import { Button } from "@planningcenter/tapestry-react"
91
+
92
+ export default function Test() {
93
+ return (
94
+ <div>
95
+ <Button innerRef={buttonRef1}>First</Button>
96
+ <Button innerRef={buttonRef2}>Second</Button>
97
+ </div>
98
+ )
99
+ }
100
+ `.trim()
101
+
102
+ const expected = `
103
+ import { Button } from "@planningcenter/tapestry-react"
104
+
105
+ export default function Test() {
106
+ return (
107
+ <div>
108
+ <Button ref={buttonRef1}>First</Button>
109
+ <Button ref={buttonRef2}>Second</Button>
110
+ </div>
111
+ );
112
+ }
113
+ `.trim()
114
+
115
+ const result = applyTransform(input)
116
+ expect(result).toBe(expected)
117
+ })
118
+ })
119
+
120
+ describe("edge cases", () => {
121
+ it("should not transform Button without innerRef", () => {
122
+ const input = `
123
+ import { Button } from "@planningcenter/tapestry-react"
124
+
125
+ export default function Test() {
126
+ return <Button onClick={handleClick}>Click me</Button>
127
+ }
128
+ `.trim()
129
+
130
+ const result = applyTransform(input)
131
+ expect(result).toBe(null)
132
+ })
133
+
134
+ it("should not transform other components with innerRef", () => {
135
+ const input = `
136
+ import { Link } from "@planningcenter/tapestry-react"
137
+
138
+ export default function Test() {
139
+ return <Link innerRef={linkRef}>Go somewhere</Link>
140
+ }
141
+ `.trim()
142
+
143
+ const result = applyTransform(input)
144
+ expect(result).toBe(null)
145
+ })
146
+
147
+ it("should preserve other props when transforming innerRef", () => {
148
+ const input = `
149
+ import { Button } from "@planningcenter/tapestry-react"
150
+
151
+ export default function Test() {
152
+ const buttonRef = null
153
+ return <Button innerRef={buttonRef} onClick={() => {}} disabled>Click me</Button>
154
+ }
155
+ `.trim()
156
+
157
+ const expected = `
158
+ import { Button } from "@planningcenter/tapestry-react"
159
+
160
+ export default function Test() {
161
+ const buttonRef = null
162
+ return <Button ref={buttonRef} onClick={() => {}} disabled>Click me</Button>;
163
+ }
164
+ `.trim()
165
+
166
+ const result = applyTransform(input)
167
+ expect(result).toBe(expected)
168
+ })
169
+ })
170
+ })
@@ -0,0 +1,14 @@
1
+ import { transformAttributeName } from "../../shared/actions/transformAttributeName"
2
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
3
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
4
+
5
+ const transform = attributeTransformFactory({
6
+ condition: hasAttribute("innerRef"),
7
+ targetComponent: "Button",
8
+ targetPackage: "@planningcenter/tapestry-react",
9
+ transform: (element) => {
10
+ return transformAttributeName("innerRef", "ref", { element })
11
+ },
12
+ })
13
+
14
+ export default transform
@@ -1,44 +1,21 @@
1
1
  import { JSXAttribute, Transform } from "jscodeshift"
2
2
 
3
- import { stylePropNames } from "../../../../dist/tapestry-react-shim.cjs"
4
3
  import { addCommentToUnsupportedProps } from "../../shared/actions/addCommentToUnsupportedProps"
4
+ import { SUPPORTED_PROPS_BASE } from "../../shared/helpers/unsupportedPropsHelpers"
5
5
  import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
6
 
7
- const SUPPORTED_PROPS = [
8
- ...stylePropNames,
9
- "children",
10
- "className",
7
+ const BUTTON_SPECIFIC_PROPS = [
11
8
  "disabled",
12
- "kind",
13
- "label",
14
- "onClick",
15
- "style",
16
- "type",
17
- "prefix",
18
- "suffix",
19
- "size",
20
9
  "fullWidth",
21
- "needsAttention",
22
- "key",
23
- "external",
24
- "id",
25
- "onMouseOver",
26
- "onMouseOut",
27
- "onMouseDown",
28
- "onMouseUp",
29
- "onKeyUp",
30
- "icon",
31
- "weight",
32
- "tabIndex",
33
- "onKeyDown",
34
- "onFocus",
35
- "onBlur",
36
- "role",
10
+ "loading",
11
+ "loadingAriaLabel",
37
12
  "name",
38
- "ref",
39
- "spinner", // taken care of by spinnerToLoadingButton transform
13
+ "type",
14
+ "icon",
40
15
  ]
41
16
 
17
+ const SUPPORTED_PROPS = [...SUPPORTED_PROPS_BASE, ...BUTTON_SPECIFIC_PROPS]
18
+
42
19
  const transform: Transform = attributeTransformFactory({
43
20
  targetComponent: "Button",
44
21
  targetPackage: "@planningcenter/tapestry-react",
@@ -0,0 +1,38 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import childrenToLabel from "./transforms/childrenToLabel"
4
+ import convertStyleProps from "./transforms/convertStyleProps"
5
+ import innerRefToRef from "./transforms/innerRefToRef"
6
+ import moveCheckboxImport from "./transforms/moveCheckboxImport"
7
+ import sizeMapping from "./transforms/sizeMapping"
8
+ import unsupportedProps from "./transforms/unsupportedProps"
9
+
10
+ const transform: Transform = (fileInfo, api, options) => {
11
+ let currentSource = fileInfo.source
12
+ let hasAnyChanges = false
13
+
14
+ const transforms = [
15
+ childrenToLabel,
16
+ sizeMapping,
17
+ innerRefToRef,
18
+ convertStyleProps,
19
+ unsupportedProps,
20
+ moveCheckboxImport,
21
+ ]
22
+
23
+ for (const individualTransform of transforms) {
24
+ const result = individualTransform(
25
+ { ...fileInfo, source: currentSource },
26
+ api,
27
+ options
28
+ )
29
+ if (result && result !== currentSource) {
30
+ currentSource = result as string
31
+ hasAnyChanges = true
32
+ }
33
+ }
34
+
35
+ return hasAnyChanges ? currentSource : null
36
+ }
37
+
38
+ export default transform
@@ -0,0 +1,146 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./childrenToLabel"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string): string {
9
+ const fileInfo = { path: "test.tsx", source }
10
+ const result = transform(
11
+ fileInfo,
12
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
13
+ {}
14
+ ) as string | null
15
+ return result || source
16
+ }
17
+
18
+ describe("childrenToLabel transform for Checkbox", () => {
19
+ describe("simple text conversion", () => {
20
+ it("should convert simple text children to label prop", () => {
21
+ const input = `
22
+ import { Checkbox } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <Checkbox>Accept terms</Checkbox>
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).toContain('<Checkbox label="Accept terms" />')
31
+ expect(result).not.toContain("Accept terms</Checkbox>")
32
+ })
33
+
34
+ it("should not convert when label prop already exists", () => {
35
+ const input = `
36
+ import { Checkbox } from "@planningcenter/tapestry-react"
37
+
38
+ function Test() {
39
+ return <Checkbox label="Already has label">Accept terms</Checkbox>
40
+ }
41
+ `.trim()
42
+
43
+ const result = applyTransform(input)
44
+ expect(result).toContain('label="Already has label"')
45
+ expect(result).toContain("Accept terms</Checkbox>")
46
+ })
47
+
48
+ it("should handle whitespace in text content", () => {
49
+ const input = `
50
+ import { Checkbox } from "@planningcenter/tapestry-react"
51
+
52
+ function Test() {
53
+ return <Checkbox> Accept terms </Checkbox>
54
+ }
55
+ `.trim()
56
+
57
+ const result = applyTransform(input)
58
+ expect(result).toContain('<Checkbox label="Accept terms" />')
59
+ })
60
+
61
+ it("should not convert complex children", () => {
62
+ const input = `
63
+ import { Checkbox } from "@planningcenter/tapestry-react"
64
+
65
+ function Test() {
66
+ return (
67
+ <Checkbox>
68
+ <span>Accept</span> terms
69
+ </Checkbox>
70
+ )
71
+ }
72
+ `.trim()
73
+
74
+ const result = applyTransform(input)
75
+ expect(result).not.toContain("label=")
76
+ expect(result).toContain("<span>Accept</span>terms")
77
+ expect(result).toContain(
78
+ "TODO: tapestry-migration (children): complex children cannot be converted to label prop"
79
+ )
80
+ })
81
+
82
+ it("should not convert when children contain JSX elements", () => {
83
+ const input = `
84
+ import { Checkbox } from "@planningcenter/tapestry-react"
85
+
86
+ function Test() {
87
+ return (
88
+ <Checkbox>
89
+ Accept <strong>terms</strong>
90
+ </Checkbox>
91
+ )
92
+ }
93
+ `.trim()
94
+
95
+ const result = applyTransform(input)
96
+ expect(result).not.toContain("label=")
97
+ expect(result).toContain("Accept <strong>terms</strong>")
98
+ expect(result).toContain(
99
+ "TODO: tapestry-migration (children): complex children cannot be converted to label prop"
100
+ )
101
+ })
102
+ })
103
+
104
+ describe("edge cases", () => {
105
+ it("should handle empty children", () => {
106
+ const input = `
107
+ import { Checkbox } from "@planningcenter/tapestry-react"
108
+
109
+ function Test() {
110
+ return <Checkbox></Checkbox>
111
+ }
112
+ `.trim()
113
+
114
+ const result = applyTransform(input)
115
+ expect(result).toContain("<Checkbox></Checkbox>")
116
+ })
117
+
118
+ it("should handle self-closing checkbox", () => {
119
+ const input = `
120
+ import { Checkbox } from "@planningcenter/tapestry-react"
121
+
122
+ function Test() {
123
+ return <Checkbox />
124
+ }
125
+ `.trim()
126
+
127
+ const result = applyTransform(input)
128
+ expect(result).toContain("<Checkbox />")
129
+ })
130
+
131
+ it("should handle checkbox with other props", () => {
132
+ const input = `
133
+ import { Checkbox } from "@planningcenter/tapestry-react"
134
+
135
+ function Test() {
136
+ return <Checkbox checked disabled>Accept terms</Checkbox>
137
+ }
138
+ `.trim()
139
+
140
+ const result = applyTransform(input)
141
+ expect(result).toContain(
142
+ '<Checkbox checked disabled label="Accept terms" />'
143
+ )
144
+ })
145
+ })
146
+ })
@@ -0,0 +1,54 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { addAttribute } from "../../shared/actions/addAttribute"
4
+ import { addComment } from "../../shared/actions/addComment"
5
+ import { removeChildren } from "../../shared/actions/removeChildren"
6
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
7
+ import { hasChildren } from "../../shared/conditions/hasChildren"
8
+ import {
9
+ buildComment,
10
+ extractTextContent,
11
+ } from "../../shared/helpers/childrenToLabelHelpers"
12
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
13
+
14
+ const transform: Transform = attributeTransformFactory({
15
+ condition: hasChildren,
16
+ targetComponent: "Checkbox",
17
+ targetPackage: "@planningcenter/tapestry-react",
18
+ transform: (element, { j, source }) => {
19
+ if (hasAttribute("label")(element)) {
20
+ addComment({
21
+ element,
22
+ j,
23
+ scope: "label",
24
+ source,
25
+ text: buildComment("Checkbox has both label prop and children", false),
26
+ })
27
+ return true
28
+ }
29
+
30
+ const { isSimpleText, textContent } = extractTextContent(element.children!)
31
+
32
+ if (isSimpleText && textContent) {
33
+ addAttribute({ element, j, name: "label", value: textContent })
34
+ removeChildren(element)
35
+ return true
36
+ } else if (!isSimpleText) {
37
+ addComment({
38
+ element,
39
+ j,
40
+ scope: "children",
41
+ source,
42
+ text: buildComment(
43
+ "complex children cannot be converted to label prop",
44
+ true
45
+ ),
46
+ })
47
+ return true
48
+ }
49
+
50
+ return false
51
+ },
52
+ })
53
+
54
+ export default transform