@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,291 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./index"
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("flex orchestrator", () => {
18
+ it("applies setDefaultAxis through the chain", () => {
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('<Flex direction="column">Test</Flex>')
29
+ expect(result).not.toContain("axis")
30
+ })
31
+
32
+ it("applies axisToDirection through the chain", () => {
33
+ const input = `
34
+ import { StackView } from "@planningcenter/tapestry-react"
35
+
36
+ export default function Test() {
37
+ return <StackView axis="horizontal">Test</StackView>
38
+ }
39
+ `.trim()
40
+
41
+ const result = applyTransform(input)
42
+ expect(result).toContain('<Flex direction="row">Test</Flex>')
43
+ expect(result).not.toContain("axis")
44
+ })
45
+
46
+ it("applies alignmentToAlign through the chain", () => {
47
+ const input = `
48
+ import { StackView } from "@planningcenter/tapestry-react"
49
+
50
+ export default function Test() {
51
+ return <StackView axis="horizontal" alignment="center">Test</StackView>
52
+ }
53
+ `.trim()
54
+
55
+ const result = applyTransform(input)
56
+ expect(result).toContain('align="center"')
57
+ expect(result).not.toContain("alignment")
58
+ })
59
+
60
+ it("applies distributionToJustify through the chain", () => {
61
+ const input = `
62
+ import { StackView } from "@planningcenter/tapestry-react"
63
+
64
+ export default function Test() {
65
+ return <StackView axis="horizontal" distribution="center">Test</StackView>
66
+ }
67
+ `.trim()
68
+
69
+ const result = applyTransform(input)
70
+ expect(result).toContain('justify="center"')
71
+ expect(result).not.toContain("distribution")
72
+ })
73
+
74
+ it("applies spacingToGap through the chain", () => {
75
+ const input = `
76
+ import { StackView } from "@planningcenter/tapestry-react"
77
+
78
+ export default function Test() {
79
+ return <StackView axis="horizontal" spacing={2}>Test</StackView>
80
+ }
81
+ `.trim()
82
+
83
+ const result = applyTransform(input)
84
+ expect(result).toContain("gap={2}")
85
+ expect(result).not.toContain("spacing")
86
+ })
87
+
88
+ it("applies innerRefToRef through the chain", () => {
89
+ const input = `
90
+ import { StackView } from "@planningcenter/tapestry-react"
91
+
92
+ export default function Test() {
93
+ return <StackView axis="horizontal" innerRef={myRef}>Test</StackView>
94
+ }
95
+ `.trim()
96
+
97
+ const result = applyTransform(input)
98
+ expect(result).toContain("ref={myRef}")
99
+ expect(result).not.toContain("innerRef")
100
+ })
101
+
102
+ it("applies paddingPropsToFlex through the chain", () => {
103
+ const input = `
104
+ import { StackView } from "@planningcenter/tapestry-react"
105
+
106
+ export default function Test() {
107
+ return <StackView axis="horizontal" padding="8px" paddingHorizontal="16px">Test</StackView>
108
+ }
109
+ `.trim()
110
+
111
+ const result = applyTransform(input)
112
+ expect(result).toContain("padding={1}")
113
+ expect(result).toContain("paddingInline={2}")
114
+ expect(result).not.toContain('padding="8px"')
115
+ expect(result).not.toContain("paddingHorizontal")
116
+ })
117
+
118
+ it("applies flexPropToExpand through the chain — flex={1} becomes expand", () => {
119
+ const input = `
120
+ import { StackView } from "@planningcenter/tapestry-react"
121
+
122
+ export default function Test() {
123
+ return <StackView flex={1}>Test</StackView>
124
+ }
125
+ `.trim()
126
+
127
+ const result = applyTransform(input)
128
+ expect(result).toContain("expand")
129
+ expect(result).not.toContain("flex")
130
+ })
131
+
132
+ it('applies flexPropToExpand through the chain — flex="1 0 0" becomes grow/shrink/basis', () => {
133
+ const input = `
134
+ import { StackView } from "@planningcenter/tapestry-react"
135
+
136
+ export default function Test() {
137
+ return <StackView flex="1 0 0">Test</StackView>
138
+ }
139
+ `.trim()
140
+
141
+ const result = applyTransform(input)
142
+ expect(result).toContain("grow={1}")
143
+ expect(result).toContain("shrink={0}")
144
+ expect(result).toContain('basis="0"')
145
+ expect(result).not.toContain("flex=")
146
+ })
147
+
148
+ it("applies mediaQueriesToResponsive through the chain", () => {
149
+ const input = `
150
+ import { StackView } from "@planningcenter/tapestry-react"
151
+
152
+ export default function Test() {
153
+ return <StackView axis="horizontal" mediaQueries={{ md: { axis: "vertical" } }}>Test</StackView>
154
+ }
155
+ `.trim()
156
+
157
+ const result = applyTransform(input)
158
+ expect(result).toContain("responsive=")
159
+ expect(result).toContain('direction: "column"')
160
+ expect(result).not.toContain("mediaQueries=")
161
+ })
162
+
163
+ it("applies cssFlexAliasesToProps through the chain — flexDirection does not produce a double direction prop", () => {
164
+ const input = `
165
+ import { StackView } from "@planningcenter/tapestry-react"
166
+
167
+ export default function Test() {
168
+ return <StackView flexDirection="row">Test</StackView>
169
+ }
170
+ `.trim()
171
+
172
+ const result = applyTransform(input)
173
+ expect(result).toContain('direction="row"')
174
+ expect(result?.match(/direction=/g)).toHaveLength(1)
175
+ })
176
+
177
+ it("applies cssFlexAliasesToProps through the chain — unmappable justifyContent stays as JSX prop with TODO, not in style={{}}", () => {
178
+ const input = `
179
+ import { StackView } from "@planningcenter/tapestry-react"
180
+
181
+ export default function Test() {
182
+ return <StackView justifyContent="stretch">Test</StackView>
183
+ }
184
+ `.trim()
185
+
186
+ const result = applyTransform(input)
187
+ expect(result).toContain("TODO")
188
+ expect(result).toContain("justifyContent")
189
+ expect(result).not.toContain("style={{")
190
+ })
191
+
192
+ it("applies cssFlexAliasesToProps through the chain — flexGrow/flexShrink/flexBasis become Flex props", () => {
193
+ const input = `
194
+ import { StackView } from "@planningcenter/tapestry-react"
195
+
196
+ export default function Test() {
197
+ return <StackView flexGrow={1} flexShrink={0} flexBasis="auto">Test</StackView>
198
+ }
199
+ `.trim()
200
+
201
+ const result = applyTransform(input)
202
+ expect(result).toContain("grow={1}")
203
+ expect(result).toContain("shrink={0}")
204
+ expect(result).toContain('basis="auto"')
205
+ expect(result).not.toContain("flexGrow")
206
+ expect(result).not.toContain("flexShrink")
207
+ expect(result).not.toContain("flexBasis")
208
+ expect(result).not.toContain("style={{")
209
+ })
210
+
211
+ it("applies convertStyleProps through the chain", () => {
212
+ const input = `
213
+ import { StackView } from "@planningcenter/tapestry-react"
214
+
215
+ export default function Test() {
216
+ return <StackView axis="horizontal" marginTop={2}>Test</StackView>
217
+ }
218
+ `.trim()
219
+
220
+ const result = applyTransform(input)
221
+ expect(result).toContain("style={{")
222
+ expect(result).toContain("marginTop:")
223
+ expect(result).not.toContain("marginTop={2}")
224
+ })
225
+
226
+ it("applies unsupportedProps through the chain", () => {
227
+ const input = `
228
+ import { StackView } from "@planningcenter/tapestry-react"
229
+
230
+ export default function Test() {
231
+ return <StackView axis="horizontal" visible={isOpen}>Test</StackView>
232
+ }
233
+ `.trim()
234
+
235
+ const result = applyTransform(input)
236
+ expect(result).toContain("TODO: tapestry-migration (visible)")
237
+ })
238
+
239
+ it("applies auditSpreadProps through the chain", () => {
240
+ const input = `
241
+ import { StackView } from "@planningcenter/tapestry-react"
242
+
243
+ export default function Test() {
244
+ return <StackView axis="horizontal" {...props}>Test</StackView>
245
+ }
246
+ `.trim()
247
+
248
+ const result = applyTransform(input)
249
+ expect(result).toContain("TODO: tapestry-migration (spreadAttribute)")
250
+ expect(result).toContain("{...props}")
251
+ })
252
+
253
+ it("applies moveFlexImport through the chain", () => {
254
+ const input = `
255
+ import { StackView } from "@planningcenter/tapestry-react"
256
+
257
+ export default function Test() {
258
+ return <StackView axis="horizontal">Test</StackView>
259
+ }
260
+ `.trim()
261
+
262
+ const result = applyTransform(input)
263
+ expect(result).toContain('import { Flex } from "@planningcenter/tapestry"')
264
+ expect(result).not.toContain("StackView")
265
+ expect(result).not.toContain('from "@planningcenter/tapestry-react"')
266
+ })
267
+
268
+ it("returns null when there is nothing to transform", () => {
269
+ const input = `
270
+ import { Flex } from "@planningcenter/tapestry"
271
+
272
+ export default function Test() {
273
+ return <Flex>Test</Flex>
274
+ }
275
+ `.trim()
276
+
277
+ expect(applyTransform(input)).toBe(null)
278
+ })
279
+
280
+ it("returns null for an empty file", () => {
281
+ expect(applyTransform("")).toBe(null)
282
+ })
283
+
284
+ it("does not throw for arbitrary source", () => {
285
+ expect(() =>
286
+ applyTransform(
287
+ `import React from "react"\nexport default function Noop() { return <div /> }`
288
+ )
289
+ ).not.toThrow()
290
+ })
291
+ })
@@ -0,0 +1,54 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import alignmentToAlign from "./transforms/alignmentToAlign"
4
+ import auditSpreadProps from "./transforms/auditSpreadProps"
5
+ import axisToDirection from "./transforms/axisToDirection"
6
+ import convertStyleProps from "./transforms/convertStyleProps"
7
+ import cssFlexAliasesToProps from "./transforms/cssFlexAliasesToProps"
8
+ import distributionToJustify from "./transforms/distributionToJustify"
9
+ import flexPropToExpand from "./transforms/flexPropToExpand"
10
+ import innerRefToRef from "./transforms/innerRefToRef"
11
+ import mediaQueriesToResponsive from "./transforms/mediaQueriesToResponsive"
12
+ import moveFlexImport from "./transforms/moveFlexImport"
13
+ import paddingPropsToFlex from "./transforms/paddingPropsToFlex"
14
+ import setDefaultAxis from "./transforms/setDefaultAxis"
15
+ import spacingToGap from "./transforms/spacingToGap"
16
+ import unsupportedProps from "./transforms/unsupportedProps"
17
+
18
+ const transform: Transform = (fileInfo, api, options) => {
19
+ let currentSource = fileInfo.source
20
+ let hasAnyChanges = false
21
+
22
+ const transforms = [
23
+ cssFlexAliasesToProps,
24
+ setDefaultAxis,
25
+ paddingPropsToFlex,
26
+ flexPropToExpand,
27
+ axisToDirection,
28
+ alignmentToAlign,
29
+ distributionToJustify,
30
+ spacingToGap,
31
+ innerRefToRef,
32
+ mediaQueriesToResponsive,
33
+ convertStyleProps,
34
+ unsupportedProps,
35
+ auditSpreadProps,
36
+ moveFlexImport,
37
+ ]
38
+
39
+ for (const individualTransform of transforms) {
40
+ const result = individualTransform(
41
+ { ...fileInfo, source: currentSource },
42
+ api,
43
+ options
44
+ )
45
+ if (result && result !== currentSource) {
46
+ currentSource = result as string
47
+ hasAnyChanges = true
48
+ }
49
+ }
50
+
51
+ return hasAnyChanges ? currentSource : null
52
+ }
53
+
54
+ export default transform
@@ -0,0 +1,185 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./alignmentToAlign"
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("alignmentToAlign transform", () => {
18
+ it("renames alignment to align", () => {
19
+ const input = `
20
+ import { StackView } from "@planningcenter/tapestry-react"
21
+
22
+ export default function Test() {
23
+ return <StackView alignment="center">Test</StackView>
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(input)
28
+ expect(result).toContain('align="center"')
29
+ expect(result).not.toContain("alignment")
30
+ })
31
+
32
+ it("resolves StackView.CENTER constant to align", () => {
33
+ const input = `
34
+ import { StackView } from "@planningcenter/tapestry-react"
35
+
36
+ export default function Test() {
37
+ return <StackView alignment={StackView.CENTER}>Test</StackView>
38
+ }
39
+ `.trim()
40
+
41
+ const result = applyTransform(input)
42
+ expect(result).toContain('align="center"')
43
+ expect(result).not.toContain("alignment")
44
+ expect(result).not.toContain("StackView.CENTER")
45
+ })
46
+
47
+ it("resolves StackView.START constant to align", () => {
48
+ const input = `
49
+ import { StackView } from "@planningcenter/tapestry-react"
50
+
51
+ export default function Test() {
52
+ return <StackView alignment={StackView.START}>Test</StackView>
53
+ }
54
+ `.trim()
55
+
56
+ const result = applyTransform(input)
57
+ expect(result).toContain('align="start"')
58
+ expect(result).not.toContain("StackView.START")
59
+ })
60
+
61
+ it("resolves StackView.END constant to align", () => {
62
+ const input = `
63
+ import { StackView } from "@planningcenter/tapestry-react"
64
+
65
+ export default function Test() {
66
+ return <StackView alignment={StackView.END}>Test</StackView>
67
+ }
68
+ `.trim()
69
+
70
+ const result = applyTransform(input)
71
+ expect(result).toContain('align="end"')
72
+ expect(result).not.toContain("StackView.END")
73
+ })
74
+
75
+ it("resolves StackView.STRETCH constant to align", () => {
76
+ const input = `
77
+ import { StackView } from "@planningcenter/tapestry-react"
78
+
79
+ export default function Test() {
80
+ return <StackView alignment={StackView.STRETCH}>Test</StackView>
81
+ }
82
+ `.trim()
83
+
84
+ const result = applyTransform(input)
85
+ expect(result).toContain('align="stretch"')
86
+ expect(result).not.toContain("StackView.STRETCH")
87
+ })
88
+
89
+ it("resolves StackView.BASELINE constant to align", () => {
90
+ const input = `
91
+ import { StackView } from "@planningcenter/tapestry-react"
92
+
93
+ export default function Test() {
94
+ return <StackView alignment={StackView.BASELINE}>Test</StackView>
95
+ }
96
+ `.trim()
97
+
98
+ const result = applyTransform(input)
99
+ expect(result).toContain('align="baseline"')
100
+ expect(result).not.toContain("StackView.BASELINE")
101
+ })
102
+
103
+ it("flags alignment={StackView.FILL} with a TODO and leaves it in place", () => {
104
+ const input = `
105
+ import { StackView } from "@planningcenter/tapestry-react"
106
+
107
+ export default function Test() {
108
+ return <StackView alignment={StackView.FILL}>Test</StackView>
109
+ }
110
+ `.trim()
111
+
112
+ const result = applyTransform(input)
113
+ expect(result).toContain("alignment={StackView.FILL}")
114
+ expect(result).toContain("TODO: tapestry-migration (alignment)")
115
+ expect(result).not.toContain('align="')
116
+ })
117
+
118
+ it('flags alignment="fill" with a TODO and leaves it in place', () => {
119
+ const input = `
120
+ import { StackView } from "@planningcenter/tapestry-react"
121
+
122
+ export default function Test() {
123
+ return <StackView alignment="fill">Test</StackView>
124
+ }
125
+ `.trim()
126
+
127
+ const result = applyTransform(input)
128
+ expect(result).toContain('alignment="fill"')
129
+ expect(result).toContain("TODO: tapestry-migration (alignment)")
130
+ expect(result).not.toContain('align="')
131
+ })
132
+
133
+ it("flags a dynamic alignment value with a TODO", () => {
134
+ const input = `
135
+ import { StackView } from "@planningcenter/tapestry-react"
136
+
137
+ export default function Test({ alignProp }) {
138
+ return <StackView alignment={alignProp}>Test</StackView>
139
+ }
140
+ `.trim()
141
+
142
+ const result = applyTransform(input)
143
+ expect(result).toContain("alignment={alignProp}")
144
+ expect(result).toContain("TODO: tapestry-migration (alignment)")
145
+ expect(result).not.toContain('align="')
146
+ })
147
+
148
+ it("respects an aliased StackView import", () => {
149
+ const input = `
150
+ import { StackView as Stack } from "@planningcenter/tapestry-react"
151
+
152
+ export default function Test() {
153
+ return <Stack alignment="center">Test</Stack>
154
+ }
155
+ `.trim()
156
+
157
+ const result = applyTransform(input)
158
+ expect(result).toContain('align="center"')
159
+ expect(result).not.toContain("alignment")
160
+ })
161
+
162
+ it("returns null when there is no alignment to migrate", () => {
163
+ const input = `
164
+ import { StackView } from "@planningcenter/tapestry-react"
165
+
166
+ export default function Test() {
167
+ return <StackView>Test</StackView>
168
+ }
169
+ `.trim()
170
+
171
+ expect(applyTransform(input)).toBe(null)
172
+ })
173
+
174
+ it("returns null when StackView is not imported from tapestry-react", () => {
175
+ const input = `
176
+ import { StackView } from "some-other-package"
177
+
178
+ export default function Test() {
179
+ return <StackView alignment="center">Test</StackView>
180
+ }
181
+ `.trim()
182
+
183
+ expect(applyTransform(input)).toBe(null)
184
+ })
185
+ })
@@ -0,0 +1,73 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { addComment } from "../../shared/actions/addComment"
4
+ import { getAttribute } from "../../shared/actions/getAttribute"
5
+ import { resolveConstantAttributeValue } from "../../shared/actions/resolveConstantAttributeValue"
6
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
7
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
8
+
9
+ const VALID_ALIGN_VALUES = new Set([
10
+ "start",
11
+ "center",
12
+ "end",
13
+ "stretch",
14
+ "baseline",
15
+ ])
16
+
17
+ const CONSTANT_ALIGN_MAP: Record<string, string> = {
18
+ BASELINE: "baseline",
19
+ CENTER: "center",
20
+ END: "end",
21
+ FILL: "fill",
22
+ START: "start",
23
+ STRETCH: "stretch",
24
+ }
25
+
26
+ const transform: Transform = attributeTransformFactory({
27
+ condition: hasAttribute("alignment"),
28
+ targetComponent: "StackView",
29
+ targetPackage: "@planningcenter/tapestry-react",
30
+ transform: (element, { j, source }) => {
31
+ const alignmentAttribute = getAttribute({ element, name: "alignment" })
32
+ if (!alignmentAttribute) return false
33
+
34
+ const resolved = resolveConstantAttributeValue({
35
+ attribute: alignmentAttribute,
36
+ constantMap: CONSTANT_ALIGN_MAP,
37
+ })
38
+ const alignValue =
39
+ resolved !== null &&
40
+ (VALID_ALIGN_VALUES.has(resolved) || resolved === "fill")
41
+ ? resolved
42
+ : null
43
+
44
+ if (alignValue === null) {
45
+ addComment({
46
+ element,
47
+ j,
48
+ scope: "alignment",
49
+ source,
50
+ text: "could not migrate alignment to align automatically; convert this value to start, center, end, stretch, or baseline manually",
51
+ })
52
+ return true
53
+ }
54
+
55
+ if (alignValue === "fill") {
56
+ addComment({
57
+ element,
58
+ j,
59
+ scope: "alignment",
60
+ source,
61
+ text: 'alignment="fill" has no Flex equivalent; remove this prop and use expand on the child element instead',
62
+ })
63
+ return true
64
+ }
65
+
66
+ alignmentAttribute.name.name = "align"
67
+ alignmentAttribute.value = j.stringLiteral(alignValue)
68
+
69
+ return true
70
+ },
71
+ })
72
+
73
+ export default transform
@@ -0,0 +1,93 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./auditSpreadProps"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ const AUDIT_COMMENT =
9
+ "TODO: tapestry-migration (spreadAttribute): Spread props can contain unsupported props, please explore usages and migrate as needed."
10
+
11
+ function applyTransform(source: string): string | null {
12
+ const fileInfo = { path: "test.tsx", source }
13
+ return transform(
14
+ fileInfo,
15
+ { j, jscodeshift: j, report: () => {}, stats: () => {} },
16
+ {}
17
+ ) as string | null
18
+ }
19
+
20
+ describe("auditSpreadProps transform", () => {
21
+ it("adds a comment to StackView with spread props", () => {
22
+ const input = `
23
+ import { StackView } from "@planningcenter/tapestry-react"
24
+
25
+ export default function Test() {
26
+ return <StackView {...props}>Test</StackView>
27
+ }
28
+ `.trim()
29
+
30
+ const result = applyTransform(input)
31
+ expect(result).toContain(AUDIT_COMMENT)
32
+ expect(result).toContain("{...props}")
33
+ })
34
+
35
+ it("adds a comment for each spread", () => {
36
+ const input = `
37
+ import { StackView } from "@planningcenter/tapestry-react"
38
+
39
+ export default function Test() {
40
+ return <StackView {...baseProps} {...extraProps}>Test</StackView>
41
+ }
42
+ `.trim()
43
+
44
+ const result = applyTransform(input)
45
+ const commentCount = (result?.match(/spreadAttribute/g) || []).length
46
+ expect(commentCount).toBe(2)
47
+ })
48
+
49
+ it("preserves all other attributes", () => {
50
+ const input = `
51
+ import { StackView } from "@planningcenter/tapestry-react"
52
+
53
+ export default function Test() {
54
+ return <StackView axis="horizontal" spacing={2} {...props}>Test</StackView>
55
+ }
56
+ `.trim()
57
+
58
+ const result = applyTransform(input)
59
+ expect(result).toContain('axis="horizontal"')
60
+ expect(result).toContain("spacing={2}")
61
+ expect(result).toContain("{...props}")
62
+ })
63
+
64
+ describe("no changes scenarios", () => {
65
+ it("returns null when no spread props", () => {
66
+ const input = `
67
+ import { StackView } from "@planningcenter/tapestry-react"
68
+
69
+ export default function Test() {
70
+ return <StackView axis="horizontal">Test</StackView>
71
+ }
72
+ `.trim()
73
+
74
+ expect(applyTransform(input)).toBe(null)
75
+ })
76
+
77
+ it("returns null when not imported from tapestry-react", () => {
78
+ const input = `
79
+ import { StackView } from "some-other-library"
80
+
81
+ export default function Test() {
82
+ return <StackView {...props}>Test</StackView>
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,6 @@
1
+ import { commentOnSpreadPropsFactory } from "../../shared/transformFactories/commentOnSpreadPropsFactory"
2
+
3
+ export default commentOnSpreadPropsFactory({
4
+ targetComponent: "StackView",
5
+ targetPackage: "@planningcenter/tapestry-react",
6
+ })