@planningcenter/tapestry-migration-cli 3.5.0 → 3.6.1-qa-1035.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 (52) hide show
  1. package/README.md +37 -0
  2. package/dist/tapestry-react-shim.cjs +195 -11
  3. package/package.json +3 -3
  4. package/src/availableComponents.ts +1 -0
  5. package/src/components/button/transforms/tooltipToWrapper.test.ts +50 -3
  6. package/src/components/button/transforms/tooltipToWrapper.ts +18 -0
  7. package/src/components/flex/index.test.ts +291 -0
  8. package/src/components/flex/index.ts +54 -0
  9. package/src/components/flex/transforms/alignmentToAlign.test.ts +185 -0
  10. package/src/components/flex/transforms/alignmentToAlign.ts +73 -0
  11. package/src/components/flex/transforms/auditSpreadProps.test.ts +93 -0
  12. package/src/components/flex/transforms/auditSpreadProps.ts +6 -0
  13. package/src/components/flex/transforms/axisToDirection.test.ts +155 -0
  14. package/src/components/flex/transforms/axisToDirection.ts +77 -0
  15. package/src/components/flex/transforms/convertStyleProps.test.ts +147 -0
  16. package/src/components/flex/transforms/convertStyleProps.ts +23 -0
  17. package/src/components/flex/transforms/cssFlexAliasesToProps.test.ts +202 -0
  18. package/src/components/flex/transforms/cssFlexAliasesToProps.ts +110 -0
  19. package/src/components/flex/transforms/distributionToJustify.test.ts +214 -0
  20. package/src/components/flex/transforms/distributionToJustify.ts +76 -0
  21. package/src/components/flex/transforms/flexPropToExpand.test.ts +156 -0
  22. package/src/components/flex/transforms/flexPropToExpand.ts +100 -0
  23. package/src/components/flex/transforms/innerRefToRef.test.ts +100 -0
  24. package/src/components/flex/transforms/innerRefToRef.ts +14 -0
  25. package/src/components/flex/transforms/mediaQueriesToResponsive.test.ts +714 -0
  26. package/src/components/flex/transforms/mediaQueriesToResponsive.ts +523 -0
  27. package/src/components/flex/transforms/moveFlexImport.test.ts +202 -0
  28. package/src/components/flex/transforms/moveFlexImport.ts +14 -0
  29. package/src/components/flex/transforms/paddingPropsToFlex.test.ts +252 -0
  30. package/src/components/flex/transforms/paddingPropsToFlex.ts +150 -0
  31. package/src/components/flex/transforms/setDefaultAxis.test.ts +126 -0
  32. package/src/components/flex/transforms/setDefaultAxis.ts +30 -0
  33. package/src/components/flex/transforms/spacingToGap.test.ts +176 -0
  34. package/src/components/flex/transforms/spacingToGap.ts +49 -0
  35. package/src/components/flex/transforms/unsupportedProps.test.ts +141 -0
  36. package/src/components/flex/transforms/unsupportedProps.ts +17 -0
  37. package/src/components/select/index.ts +2 -0
  38. package/src/components/select/transforms/onChangeSignature.test.ts +224 -0
  39. package/src/components/select/transforms/onChangeSignature.ts +172 -0
  40. package/src/components/shared/actions/resolveConstantAttributeValue.test.ts +122 -0
  41. package/src/components/shared/actions/resolveConstantAttributeValue.ts +31 -0
  42. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +41 -0
  43. package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +47 -0
  44. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +16 -0
  45. package/src/components/toggle-switch/index.ts +2 -0
  46. package/src/components/toggle-switch/transforms/onClickToOnChange.test.ts +210 -0
  47. package/src/components/toggle-switch/transforms/onClickToOnChange.ts +71 -0
  48. package/src/index.test.ts +79 -0
  49. package/src/index.ts +29 -0
  50. package/src/migrationList.ts +22 -0
  51. package/src/utils/componentLabel.test.ts +61 -0
  52. package/src/utils/componentLabel.ts +15 -0
@@ -0,0 +1,126 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./setDefaultAxis"
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("setDefaultAxis transform", () => {
18
+ it('adds axis="vertical" to a StackView with no axis', () => {
19
+ const input = `
20
+ import { StackView } from "@planningcenter/tapestry-react"
21
+
22
+ export default function Test() {
23
+ return <StackView>Test</StackView>
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(input)
28
+ expect(result).toContain('<StackView axis="vertical">Test</StackView>')
29
+ })
30
+
31
+ it("adds the default to self-closing elements", () => {
32
+ const input = `
33
+ import { StackView } from "@planningcenter/tapestry-react"
34
+
35
+ export default function Test() {
36
+ return <StackView spacing={2} />
37
+ }
38
+ `.trim()
39
+
40
+ const result = applyTransform(input)
41
+ expect(result).toContain('axis="vertical"')
42
+ expect(result).toContain("spacing={2}")
43
+ })
44
+
45
+ it("adds the default to every StackView lacking an axis", () => {
46
+ const input = `
47
+ import { StackView } from "@planningcenter/tapestry-react"
48
+
49
+ export default function Test() {
50
+ return (
51
+ <StackView>
52
+ <StackView>Inner</StackView>
53
+ </StackView>
54
+ )
55
+ }
56
+ `.trim()
57
+
58
+ const result = applyTransform(input)
59
+ expect(result?.match(/axis="vertical"/g)).toHaveLength(2)
60
+ })
61
+
62
+ it("does not change a StackView that already has an axis", () => {
63
+ const input = `
64
+ import { StackView } from "@planningcenter/tapestry-react"
65
+
66
+ export default function Test() {
67
+ return <StackView axis="horizontal">Test</StackView>
68
+ }
69
+ `.trim()
70
+
71
+ expect(applyTransform(input)).toBe(null)
72
+ })
73
+
74
+ it("does not inject axis when direction is already present", () => {
75
+ const input = `
76
+ import { StackView } from "@planningcenter/tapestry-react"
77
+
78
+ export default function Test() {
79
+ return <StackView direction="row">Test</StackView>
80
+ }
81
+ `.trim()
82
+
83
+ expect(applyTransform(input)).toBe(null)
84
+ })
85
+
86
+ it("skips elements with spread props", () => {
87
+ const input = `
88
+ import { StackView } from "@planningcenter/tapestry-react"
89
+
90
+ export default function Test(props) {
91
+ return <StackView {...props}>Test</StackView>
92
+ }
93
+ `.trim()
94
+
95
+ expect(applyTransform(input)).toBe(null)
96
+ })
97
+
98
+ it("respects an aliased StackView import", () => {
99
+ const input = `
100
+ import { StackView as Stack } from "@planningcenter/tapestry-react"
101
+
102
+ export default function Test() {
103
+ return <Stack>Test</Stack>
104
+ }
105
+ `.trim()
106
+
107
+ const result = applyTransform(input)
108
+ expect(result).toContain('<Stack axis="vertical">Test</Stack>')
109
+ })
110
+
111
+ it("returns null when StackView is not imported from tapestry-react", () => {
112
+ const input = `
113
+ import { StackView } from "some-other-package"
114
+
115
+ export default function Test() {
116
+ return <StackView>Test</StackView>
117
+ }
118
+ `.trim()
119
+
120
+ expect(applyTransform(input)).toBe(null)
121
+ })
122
+
123
+ it("returns null for an empty file", () => {
124
+ expect(applyTransform("")).toBe(null)
125
+ })
126
+ })
@@ -0,0 +1,30 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { addAttribute } from "../../shared/actions/addAttribute"
4
+ import { hasSpreadProps } from "../../shared/actions/hasSpreadProps"
5
+ import { andConditions } from "../../shared/conditions/andConditions"
6
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
7
+ import { notCondition } from "../../shared/conditions/notCondition"
8
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
9
+
10
+ const transform: Transform = attributeTransformFactory({
11
+ condition: andConditions(
12
+ notCondition(hasAttribute("axis")),
13
+ notCondition(hasAttribute("direction")),
14
+ notCondition(hasSpreadProps)
15
+ ),
16
+ targetComponent: "StackView",
17
+ targetPackage: "@planningcenter/tapestry-react",
18
+ transform: (element, { j }) => {
19
+ addAttribute({
20
+ element,
21
+ j,
22
+ name: "axis",
23
+ value: "vertical",
24
+ })
25
+
26
+ return true
27
+ },
28
+ })
29
+
30
+ export default transform
@@ -0,0 +1,176 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./spacingToGap"
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("spacingToGap transform", () => {
18
+ it("renames spacing to gap for all valid scale values", () => {
19
+ const input = `
20
+ import { StackView } from "@planningcenter/tapestry-react"
21
+
22
+ export default function Test() {
23
+ return (
24
+ <StackView spacing={0}>
25
+ <StackView spacing={0.25}>
26
+ <StackView spacing={0.5}>
27
+ <StackView spacing={1}>
28
+ <StackView spacing={1.5}>
29
+ <StackView spacing={2}>
30
+ <StackView spacing={3}>
31
+ <StackView spacing={4}>
32
+ <StackView spacing={5}>
33
+ <StackView spacing={6}>
34
+ <StackView spacing={7}>Test</StackView>
35
+ </StackView>
36
+ </StackView>
37
+ </StackView>
38
+ </StackView>
39
+ </StackView>
40
+ </StackView>
41
+ </StackView>
42
+ </StackView>
43
+ </StackView>
44
+ </StackView>
45
+ )
46
+ }
47
+ `.trim()
48
+
49
+ const result = applyTransform(input)
50
+ expect(result).toContain("gap={0}")
51
+ expect(result).toContain("gap={0.25}")
52
+ expect(result).toContain("gap={0.5}")
53
+ expect(result).toContain("gap={1}")
54
+ expect(result).toContain("gap={1.5}")
55
+ expect(result).toContain("gap={2}")
56
+ expect(result).toContain("gap={3}")
57
+ expect(result).toContain("gap={4}")
58
+ expect(result).toContain("gap={5}")
59
+ expect(result).toContain("gap={6}")
60
+ expect(result).toContain("gap={7}")
61
+ expect(result).not.toContain("spacing")
62
+ })
63
+
64
+ it("flags an off-scale number with a TODO", () => {
65
+ const input = `
66
+ import { StackView } from "@planningcenter/tapestry-react"
67
+
68
+ export default function Test() {
69
+ return <StackView spacing={10}>Test</StackView>
70
+ }
71
+ `.trim()
72
+
73
+ const result = applyTransform(input)
74
+ expect(result).toContain("spacing={10}")
75
+ expect(result).toContain("TODO: tapestry-migration (spacing)")
76
+ expect(result).not.toContain("gap=")
77
+ })
78
+
79
+ it("flags a string spacing value with a TODO", () => {
80
+ const input = `
81
+ import { StackView } from "@planningcenter/tapestry-react"
82
+
83
+ export default function Test() {
84
+ return <StackView spacing="8px">Test</StackView>
85
+ }
86
+ `.trim()
87
+
88
+ const result = applyTransform(input)
89
+ expect(result).toContain('spacing="8px"')
90
+ expect(result).toContain("TODO: tapestry-migration (spacing)")
91
+ expect(result).not.toContain("gap=")
92
+ })
93
+
94
+ it("flags a ReactNode spacer with a TODO", () => {
95
+ const input = `
96
+ import { StackView } from "@planningcenter/tapestry-react"
97
+
98
+ export default function Test() {
99
+ return <StackView spacing={<Divider />}>Test</StackView>
100
+ }
101
+ `.trim()
102
+
103
+ const result = applyTransform(input)
104
+ expect(result).toContain("spacing={<Divider />}")
105
+ expect(result).toContain("TODO: tapestry-migration (spacing)")
106
+ expect(result).not.toContain("gap=")
107
+ })
108
+
109
+ it("flags an expression-wrapped string spacing value with a TODO", () => {
110
+ const input = `
111
+ import { StackView } from "@planningcenter/tapestry-react"
112
+
113
+ export default function Test() {
114
+ return <StackView spacing={"8px"}>Test</StackView>
115
+ }
116
+ `.trim()
117
+
118
+ const result = applyTransform(input)
119
+ expect(result).toContain('spacing={"8px"}')
120
+ expect(result).toContain("TODO: tapestry-migration (spacing)")
121
+ expect(result).not.toContain("gap=")
122
+ })
123
+
124
+ it("flags a dynamic spacing value with a TODO", () => {
125
+ const input = `
126
+ import { StackView } from "@planningcenter/tapestry-react"
127
+
128
+ export default function Test({ spacingProp }) {
129
+ return <StackView spacing={spacingProp}>Test</StackView>
130
+ }
131
+ `.trim()
132
+
133
+ const result = applyTransform(input)
134
+ expect(result).toContain("spacing={spacingProp}")
135
+ expect(result).toContain("TODO: tapestry-migration (spacing)")
136
+ expect(result).not.toContain("gap=")
137
+ })
138
+
139
+ it("respects an aliased StackView import", () => {
140
+ const input = `
141
+ import { StackView as Stack } from "@planningcenter/tapestry-react"
142
+
143
+ export default function Test() {
144
+ return <Stack spacing={2}>Test</Stack>
145
+ }
146
+ `.trim()
147
+
148
+ const result = applyTransform(input)
149
+ expect(result).toContain("gap={2}")
150
+ expect(result).not.toContain("spacing")
151
+ })
152
+
153
+ it("returns null when there is no spacing to migrate", () => {
154
+ const input = `
155
+ import { StackView } from "@planningcenter/tapestry-react"
156
+
157
+ export default function Test() {
158
+ return <StackView>Test</StackView>
159
+ }
160
+ `.trim()
161
+
162
+ expect(applyTransform(input)).toBe(null)
163
+ })
164
+
165
+ it("returns null when StackView is not imported from tapestry-react", () => {
166
+ const input = `
167
+ import { StackView } from "some-other-package"
168
+
169
+ export default function Test() {
170
+ return <StackView spacing={2}>Test</StackView>
171
+ }
172
+ `.trim()
173
+
174
+ expect(applyTransform(input)).toBe(null)
175
+ })
176
+ })
@@ -0,0 +1,49 @@
1
+ import { JSXAttribute, Transform } from "jscodeshift"
2
+
3
+ import { addComment } from "../../shared/actions/addComment"
4
+ import { getAttribute } from "../../shared/actions/getAttribute"
5
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
6
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
7
+
8
+ const VALID_GAP_VALUES = new Set([0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7])
9
+
10
+ function resolveSpacingValue(attribute: JSXAttribute): number | null {
11
+ const value = attribute.value
12
+ if (
13
+ value?.type === "JSXExpressionContainer" &&
14
+ value.expression.type === "NumericLiteral" &&
15
+ VALID_GAP_VALUES.has(value.expression.value)
16
+ ) {
17
+ return value.expression.value
18
+ }
19
+ return null
20
+ }
21
+
22
+ const transform: Transform = attributeTransformFactory({
23
+ condition: hasAttribute("spacing"),
24
+ targetComponent: "StackView",
25
+ targetPackage: "@planningcenter/tapestry-react",
26
+ transform: (element, { j, source }) => {
27
+ const spacingAttribute = getAttribute({ element, name: "spacing" })
28
+ if (!spacingAttribute) return false
29
+
30
+ const spacingValue = resolveSpacingValue(spacingAttribute)
31
+
32
+ if (spacingValue === null) {
33
+ addComment({
34
+ element,
35
+ j,
36
+ scope: "spacing",
37
+ source,
38
+ text: "could not migrate spacing to gap automatically; use a Tapestry spacing scale number (0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7) for gap, or for ReactNode spacers add explicit margin to the spacer element instead",
39
+ })
40
+ return true
41
+ }
42
+
43
+ spacingAttribute.name.name = "gap"
44
+
45
+ return true
46
+ },
47
+ })
48
+
49
+ export default transform
@@ -0,0 +1,141 @@
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 visible", () => {
22
+ const input = `
23
+ import { StackView } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return <StackView visible={isOpen}>Test</StackView>
27
+ }
28
+ `.trim()
29
+
30
+ const result = applyTransform(input)
31
+ expect(result).toContain("TODO: tapestry-migration (visible)")
32
+ })
33
+
34
+ it("flags variants", () => {
35
+ const input = `
36
+ import { StackView } from "@planningcenter/tapestry-react"
37
+
38
+ export default function Test() {
39
+ return <StackView variants={["elevated"]}>Test</StackView>
40
+ }
41
+ `.trim()
42
+
43
+ const result = applyTransform(input)
44
+ expect(result).toContain("TODO: tapestry-migration (variants)")
45
+ })
46
+
47
+ it("flags multiple unsupported props", () => {
48
+ const input = `
49
+ import { StackView } from "@planningcenter/tapestry-react"
50
+
51
+ export default function Test() {
52
+ return <StackView visible={isOpen} variants={["elevated"]}>Test</StackView>
53
+ }
54
+ `.trim()
55
+
56
+ const result = applyTransform(input)
57
+ expect(result).toContain("TODO: tapestry-migration (visible)")
58
+ expect(result).toContain("TODO: tapestry-migration (variants)")
59
+ })
60
+ })
61
+
62
+ describe("does not flag supported props", () => {
63
+ it("does not flag valid Flex layout props", () => {
64
+ const input = `
65
+ import { StackView } from "@planningcenter/tapestry-react"
66
+
67
+ export default function Test() {
68
+ return <StackView direction="row" gap={2} align="center" justify="space-between" wrap="wrap">Test</StackView>
69
+ }
70
+ `.trim()
71
+
72
+ expect(applyTransform(input)).toBe(input)
73
+ })
74
+
75
+ it("does not flag Flex sizing and responsive props", () => {
76
+ const input = `
77
+ import { StackView } from "@planningcenter/tapestry-react"
78
+
79
+ export default function Test() {
80
+ return <StackView expand responsive={{ md: { direction: "row" } }} paddingBlock={2} alignSelf="center" columnGap={1}>Test</StackView>
81
+ }
82
+ `.trim()
83
+
84
+ expect(applyTransform(input)).toBe(input)
85
+ })
86
+
87
+ it("does not flag props with dedicated transforms that may remain when values cannot be resolved", () => {
88
+ const input = `
89
+ import { StackView } from "@planningcenter/tapestry-react"
90
+
91
+ export default function Test() {
92
+ return <StackView axis={someVar} alignment={a} distribution={d} spacing={s} mediaQueries={mq}>Test</StackView>
93
+ }
94
+ `.trim()
95
+
96
+ expect(applyTransform(input)).toBe(input)
97
+ })
98
+
99
+ it("does not flag className, style, ref", () => {
100
+ const input = `
101
+ import { StackView } from "@planningcenter/tapestry-react"
102
+
103
+ export default function Test() {
104
+ return <StackView className="my-stack" style={{ color: "red" }} ref={ref}>Test</StackView>
105
+ }
106
+ `.trim()
107
+
108
+ expect(applyTransform(input)).toBe(input)
109
+ })
110
+
111
+ it("does not flag aria-* and data-* attributes", () => {
112
+ const input = `
113
+ import { StackView } from "@planningcenter/tapestry-react"
114
+
115
+ export default function Test() {
116
+ return <StackView aria-label="Content" data-pendo="stack">Test</StackView>
117
+ }
118
+ `.trim()
119
+
120
+ expect(applyTransform(input)).toBe(input)
121
+ })
122
+ })
123
+
124
+ describe("no changes scenarios", () => {
125
+ it("returns source unchanged when not from tapestry-react", () => {
126
+ const input = `
127
+ import { StackView } from "some-other-library"
128
+
129
+ export default function Test() {
130
+ return <StackView visible={true}>Test</StackView>
131
+ }
132
+ `.trim()
133
+
134
+ expect(applyTransform(input)).toBe(input)
135
+ })
136
+
137
+ it("returns source unchanged for empty file", () => {
138
+ expect(applyTransform("")).toBe("")
139
+ })
140
+ })
141
+ })
@@ -0,0 +1,17 @@
1
+ import { FLEX_SUPPORTED_PROPS } from "../../shared/helpers/unsupportedPropsHelpers"
2
+ import { unsupportedPropsFactory } from "../../shared/transformFactories/unsupportedPropsFactory"
3
+
4
+ export default unsupportedPropsFactory({
5
+ commentNotes: (prop) => {
6
+ if (prop === "visible") {
7
+ return "\n * `visible` is not supported in Flex. Conditionally render the element instead.\n"
8
+ }
9
+ if (prop === "variants") {
10
+ return "\n * `variants` is not supported in Flex.\n"
11
+ }
12
+ return ""
13
+ },
14
+ supportedProps: FLEX_SUPPORTED_PROPS,
15
+ targetComponent: "StackView",
16
+ targetPackage: "@planningcenter/tapestry-react",
17
+ })
@@ -10,6 +10,7 @@ import mapChildrenToOptions from "./transforms/mapChildrenToOptions"
10
10
  import mergeFieldIntoSelect from "./transforms/mergeFieldIntoSelect"
11
11
  import mergeSelectLabel from "./transforms/mergeSelectLabel"
12
12
  import moveSelectImport from "./transforms/moveSelectImport"
13
+ import onChangeSignature from "./transforms/onChangeSignature"
13
14
  import removeDefaultProps from "./transforms/removeDefaultProps"
14
15
  import sizeMapping from "./transforms/sizeMapping"
15
16
  import skipMultipleSelect from "./transforms/skipMultipleSelect"
@@ -36,6 +37,7 @@ const transform: Transform = (fileInfo, api, options) => {
36
37
  stateToInvalid,
37
38
  removeDefaultProps,
38
39
  convertStyleProps,
40
+ onChangeSignature,
39
41
  unsupportedProps,
40
42
  moveSelectImport,
41
43
  ]