@planningcenter/tapestry-migration-cli 3.6.0 → 3.7.0-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 (34) hide show
  1. package/dist/tapestry-react-shim.cjs +195 -11
  2. package/package.json +3 -3
  3. package/src/components/flex/index.test.ts +181 -0
  4. package/src/components/flex/index.ts +16 -0
  5. package/src/components/flex/transforms/alignmentToAlign.test.ts +101 -0
  6. package/src/components/flex/transforms/alignmentToAlign.ts +74 -20
  7. package/src/components/flex/transforms/auditSpreadProps.test.ts +93 -0
  8. package/src/components/flex/transforms/auditSpreadProps.ts +6 -0
  9. package/src/components/flex/transforms/axisToDirection.test.ts +15 -0
  10. package/src/components/flex/transforms/axisToDirection.ts +26 -0
  11. package/src/components/flex/transforms/constants.ts +4 -0
  12. package/src/components/flex/transforms/convertStyleProps.test.ts +147 -0
  13. package/src/components/flex/transforms/convertStyleProps.ts +23 -0
  14. package/src/components/flex/transforms/cssFlexAliasesToProps.test.ts +202 -0
  15. package/src/components/flex/transforms/cssFlexAliasesToProps.ts +106 -0
  16. package/src/components/flex/transforms/dedupeAttributes.test.ts +120 -0
  17. package/src/components/flex/transforms/dedupeAttributes.ts +49 -0
  18. package/src/components/flex/transforms/distributionToJustify.test.ts +59 -0
  19. package/src/components/flex/transforms/distributionToJustify.ts +69 -20
  20. package/src/components/flex/transforms/flexPropToExpand.test.ts +172 -0
  21. package/src/components/flex/transforms/flexPropToExpand.ts +100 -0
  22. package/src/components/flex/transforms/mediaQueriesToResponsive.test.ts +795 -0
  23. package/src/components/flex/transforms/mediaQueriesToResponsive.ts +562 -0
  24. package/src/components/flex/transforms/paddingPropsToFlex.test.ts +252 -0
  25. package/src/components/flex/transforms/paddingPropsToFlex.ts +150 -0
  26. package/src/components/flex/transforms/setDefaultAxis.test.ts +12 -0
  27. package/src/components/flex/transforms/setDefaultAxis.ts +1 -0
  28. package/src/components/flex/transforms/spacingToGap.test.ts +87 -0
  29. package/src/components/flex/transforms/spacingToGap.ts +28 -10
  30. package/src/components/flex/transforms/unsupportedProps.test.ts +141 -0
  31. package/src/components/flex/transforms/unsupportedProps.ts +17 -0
  32. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +41 -0
  33. package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +47 -0
  34. package/src/components/shared/transformFactories/stylePropTransformFactory.ts +16 -0
@@ -0,0 +1,795 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./mediaQueriesToResponsive"
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("mediaQueriesToResponsive transform", () => {
18
+ it("renames mediaQueries to responsive", () => {
19
+ const input = `
20
+ import { StackView } from "@planningcenter/tapestry-react"
21
+
22
+ export default function Test() {
23
+ return <StackView mediaQueries={{ md: { axis: "vertical" } }}>Test</StackView>
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(input)
28
+ expect(result).toContain("responsive=")
29
+ expect(result).not.toContain("mediaQueries=")
30
+ })
31
+
32
+ it("preserves all valid breakpoint keys in the output", () => {
33
+ const input = `
34
+ import { StackView } from "@planningcenter/tapestry-react"
35
+
36
+ export default function Test() {
37
+ return <StackView mediaQueries={{ xs: { axis: "horizontal" }, sm: { axis: "horizontal" }, md: { axis: "vertical" }, lg: { axis: "vertical" }, xl: { axis: "vertical" } }}>Test</StackView>
38
+ }
39
+ `.trim()
40
+
41
+ const result = applyTransform(input)
42
+ expect(result).toContain("responsive=")
43
+ expect(result).toContain("xs:")
44
+ expect(result).toContain("sm:")
45
+ expect(result).toContain("md:")
46
+ expect(result).toContain("lg:")
47
+ expect(result).toContain("xl:")
48
+ expect(result).not.toContain("mediaQueries=")
49
+ })
50
+
51
+ it("migrates all known inner props within a breakpoint", () => {
52
+ const input = `
53
+ import { StackView } from "@planningcenter/tapestry-react"
54
+
55
+ export default function Test() {
56
+ return <StackView mediaQueries={{ md: { axis: "vertical", alignment: "center", distribution: "space-between", spacing: 2 } }}>Test</StackView>
57
+ }
58
+ `.trim()
59
+
60
+ const result = applyTransform(input)
61
+ expect(result).toContain("responsive=")
62
+ expect(result).not.toContain("mediaQueries=")
63
+ expect(result).not.toContain("axis:")
64
+ expect(result).not.toContain("alignment:")
65
+ expect(result).not.toContain("distribution:")
66
+ expect(result).not.toContain("spacing:")
67
+ expect(result).toContain("direction:")
68
+ expect(result).toContain("align:")
69
+ expect(result).toContain("justify:")
70
+ expect(result).toContain("gap:")
71
+ })
72
+
73
+ it("resolves StackView constants within breakpoint values", () => {
74
+ const input = `
75
+ import { StackView } from "@planningcenter/tapestry-react"
76
+
77
+ export default function Test() {
78
+ return <StackView mediaQueries={{ md: { axis: StackView.VERTICAL } }}>Test</StackView>
79
+ }
80
+ `.trim()
81
+
82
+ const result = applyTransform(input)
83
+ expect(result).toContain("responsive=")
84
+ expect(result).not.toContain("mediaQueries=")
85
+ expect(result).not.toContain("axis:")
86
+ expect(result).toContain("direction:")
87
+ })
88
+
89
+ it("migrates the justifyContent CSS alias to justify", () => {
90
+ const input = `
91
+ import { StackView } from "@planningcenter/tapestry-react"
92
+
93
+ export default function Test() {
94
+ return <StackView mediaQueries={{ md: { justifyContent: "center" } }}>Test</StackView>
95
+ }
96
+ `.trim()
97
+
98
+ const result = applyTransform(input)
99
+ expect(result).toContain("responsive=")
100
+ expect(result).not.toContain("justifyContent:")
101
+ expect(result).toContain("justify:")
102
+ expect(result).toContain('"center"')
103
+ expect(result).not.toContain("TODO")
104
+ })
105
+
106
+ it("normalizes justifyContent flex-start/flex-end to start/end", () => {
107
+ const input = `
108
+ import { StackView } from "@planningcenter/tapestry-react"
109
+
110
+ export default function Test() {
111
+ return <StackView mediaQueries={{ md: { justifyContent: "flex-start" } }}>Test</StackView>
112
+ }
113
+ `.trim()
114
+
115
+ const result = applyTransform(input)
116
+ expect(result).toContain("responsive=")
117
+ expect(result).not.toContain("justifyContent:")
118
+ expect(result).toContain("justify:")
119
+ expect(result).toContain('"start"')
120
+ expect(result).not.toContain("TODO")
121
+ })
122
+
123
+ it("migrates the alignItems CSS alias to align", () => {
124
+ const input = `
125
+ import { StackView } from "@planningcenter/tapestry-react"
126
+
127
+ export default function Test() {
128
+ return <StackView mediaQueries={{ md: { alignItems: "stretch" } }}>Test</StackView>
129
+ }
130
+ `.trim()
131
+
132
+ const result = applyTransform(input)
133
+ expect(result).toContain("responsive=")
134
+ expect(result).not.toContain("alignItems:")
135
+ expect(result).toContain("align:")
136
+ expect(result).toContain('"stretch"')
137
+ expect(result).not.toContain("TODO")
138
+ })
139
+
140
+ it("normalizes alignItems flex-start/flex-end to start/end", () => {
141
+ const input = `
142
+ import { StackView } from "@planningcenter/tapestry-react"
143
+
144
+ export default function Test() {
145
+ return <StackView mediaQueries={{ md: { alignItems: "flex-end" } }}>Test</StackView>
146
+ }
147
+ `.trim()
148
+
149
+ const result = applyTransform(input)
150
+ expect(result).toContain("responsive=")
151
+ expect(result).not.toContain("alignItems:")
152
+ expect(result).toContain("align:")
153
+ expect(result).toContain('"end"')
154
+ expect(result).not.toContain("TODO")
155
+ })
156
+
157
+ it("migrates the flexDirection CSS alias to direction", () => {
158
+ const input = `
159
+ import { StackView } from "@planningcenter/tapestry-react"
160
+
161
+ export default function Test() {
162
+ return <StackView mediaQueries={{ md: { flexDirection: "row" } }}>Test</StackView>
163
+ }
164
+ `.trim()
165
+
166
+ const result = applyTransform(input)
167
+ expect(result).toContain("responsive=")
168
+ expect(result).not.toContain("flexDirection:")
169
+ expect(result).toContain("direction:")
170
+ expect(result).toContain('"row"')
171
+ expect(result).not.toContain("TODO")
172
+ })
173
+
174
+ it("migrates the flexGrow CSS alias to grow", () => {
175
+ const input = `
176
+ import { StackView } from "@planningcenter/tapestry-react"
177
+
178
+ export default function Test() {
179
+ return <StackView mediaQueries={{ md: { flexGrow: 1 } }}>Test</StackView>
180
+ }
181
+ `.trim()
182
+
183
+ const result = applyTransform(input)
184
+ expect(result).toContain("responsive=")
185
+ expect(result).not.toContain("flexGrow:")
186
+ expect(result).toContain("grow:")
187
+ expect(result).not.toContain("TODO")
188
+ })
189
+
190
+ it("migrates the flexShrink CSS alias to shrink", () => {
191
+ const input = `
192
+ import { StackView } from "@planningcenter/tapestry-react"
193
+
194
+ export default function Test() {
195
+ return <StackView mediaQueries={{ md: { flexShrink: 0 } }}>Test</StackView>
196
+ }
197
+ `.trim()
198
+
199
+ const result = applyTransform(input)
200
+ expect(result).toContain("responsive=")
201
+ expect(result).not.toContain("flexShrink:")
202
+ expect(result).toContain("shrink:")
203
+ expect(result).not.toContain("TODO")
204
+ })
205
+
206
+ it("migrates the flexBasis CSS alias to basis", () => {
207
+ const input = `
208
+ import { StackView } from "@planningcenter/tapestry-react"
209
+
210
+ export default function Test() {
211
+ return <StackView mediaQueries={{ md: { flexBasis: "100%" } }}>Test</StackView>
212
+ }
213
+ `.trim()
214
+
215
+ const result = applyTransform(input)
216
+ expect(result).toContain("responsive=")
217
+ expect(result).not.toContain("flexBasis:")
218
+ expect(result).toContain("basis:")
219
+ expect(result).toContain('"100%"')
220
+ expect(result).not.toContain("TODO")
221
+ })
222
+
223
+ it("migrates the flexWrap CSS alias to wrap", () => {
224
+ const input = `
225
+ import { StackView } from "@planningcenter/tapestry-react"
226
+
227
+ export default function Test() {
228
+ return <StackView mediaQueries={{ md: { flexWrap: "wrap" } }}>Test</StackView>
229
+ }
230
+ `.trim()
231
+
232
+ const result = applyTransform(input)
233
+ expect(result).toContain("responsive=")
234
+ expect(result).not.toContain("flexWrap:")
235
+ expect(result).toContain("wrap:")
236
+ expect(result).not.toContain("TODO")
237
+ })
238
+
239
+ it("passes through already-valid Flex responsive props unchanged", () => {
240
+ const input = `
241
+ import { StackView } from "@planningcenter/tapestry-react"
242
+
243
+ export default function Test() {
244
+ return <StackView mediaQueries={{ md: { direction: "row", gap: 2, wrap: "wrap", grow: 1, shrink: 0, basis: "100%" } }}>Test</StackView>
245
+ }
246
+ `.trim()
247
+
248
+ const result = applyTransform(input)
249
+ expect(result).toContain("responsive=")
250
+ expect(result).toContain("direction:")
251
+ expect(result).toContain("gap:")
252
+ expect(result).toContain("wrap:")
253
+ expect(result).toContain("grow:")
254
+ expect(result).toContain("shrink:")
255
+ expect(result).toContain("basis:")
256
+ expect(result).not.toContain("TODO")
257
+ })
258
+
259
+ it.each(["grow", "shrink", "basis"] as const)(
260
+ "passes %s through a breakpoint object without a TODO",
261
+ (prop) => {
262
+ const value = prop === "basis" ? '"100%"' : "1"
263
+ const input = `
264
+ import { StackView } from "@planningcenter/tapestry-react"
265
+
266
+ export default function Test() {
267
+ return <StackView mediaQueries={{ md: { ${prop}: ${value} } }}>Test</StackView>
268
+ }
269
+ `.trim()
270
+
271
+ const result = applyTransform(input)
272
+ expect(result).toContain("responsive=")
273
+ expect(result).toContain(`${prop}:`)
274
+ expect(result).not.toContain("TODO")
275
+ }
276
+ )
277
+
278
+ it('expands flex="N N basis" inside a breakpoint into grow/shrink/basis', () => {
279
+ const input = `
280
+ import { StackView } from "@planningcenter/tapestry-react"
281
+
282
+ export default function Test() {
283
+ return <StackView mediaQueries={{ md: { flex: "1 0 0" } }}>Test</StackView>
284
+ }
285
+ `.trim()
286
+
287
+ const result = applyTransform(input)
288
+ expect(result).toContain("responsive=")
289
+ expect(result).toContain("grow:")
290
+ expect(result).toContain("shrink:")
291
+ expect(result).toContain("basis:")
292
+ expect(result).not.toContain("flex:")
293
+ expect(result).not.toContain("TODO")
294
+ })
295
+
296
+ it('flags flex="N N basis" inside a breakpoint with a TODO instead of duplicating an existing grow key', () => {
297
+ const input = `
298
+ import { StackView } from "@planningcenter/tapestry-react"
299
+
300
+ export default function Test() {
301
+ return <StackView mediaQueries={{ md: { flex: "1 0 0", grow: 5 } }}>Test</StackView>
302
+ }
303
+ `.trim()
304
+
305
+ const result = applyTransform(input)
306
+ expect(result).toContain("responsive=")
307
+ expect(result).toContain("TODO")
308
+ expect(result).toContain("grow: 5")
309
+ expect(result?.match(/grow:/g)).toHaveLength(1)
310
+ })
311
+
312
+ it('flags flex="inherit" inside a breakpoint with a generic TODO, not an expand-specific one', () => {
313
+ const input = `
314
+ import { StackView } from "@planningcenter/tapestry-react"
315
+
316
+ export default function Test() {
317
+ return <StackView mediaQueries={{ md: { flex: "inherit" } }}>Test</StackView>
318
+ }
319
+ `.trim()
320
+
321
+ const result = applyTransform(input)
322
+ expect(result).toContain("TODO")
323
+ expect(result).not.toContain("expand is not supported")
324
+ })
325
+
326
+ it('flags flex="1" inside a breakpoint with a TODO (expand not supported in responsive)', () => {
327
+ const input = `
328
+ import { StackView } from "@planningcenter/tapestry-react"
329
+
330
+ export default function Test() {
331
+ return <StackView mediaQueries={{ md: { flex: "1" } }}>Test</StackView>
332
+ }
333
+ `.trim()
334
+
335
+ const result = applyTransform(input)
336
+ expect(result).toContain("TODO")
337
+ expect(result).toContain("expand is not supported")
338
+ })
339
+
340
+ it("flags flex={1} inside a breakpoint with a TODO (expand not supported in responsive)", () => {
341
+ const input = `
342
+ import { StackView } from "@planningcenter/tapestry-react"
343
+
344
+ export default function Test() {
345
+ return <StackView mediaQueries={{ md: { flex: 1 } }}>Test</StackView>
346
+ }
347
+ `.trim()
348
+
349
+ const result = applyTransform(input)
350
+ expect(result).toContain("responsive=")
351
+ expect(result).toContain("TODO")
352
+ })
353
+
354
+ it("expands flex={0} inside a breakpoint into grow={0} shrink={1} basis='0'", () => {
355
+ const input = `
356
+ import { StackView } from "@planningcenter/tapestry-react"
357
+
358
+ export default function Test() {
359
+ return <StackView mediaQueries={{ md: { flex: 0 } }}>Test</StackView>
360
+ }
361
+ `.trim()
362
+
363
+ const result = applyTransform(input)
364
+ expect(result).toContain("responsive=")
365
+ expect(result).toContain("grow: 0")
366
+ expect(result).toContain("shrink: 1")
367
+ expect(result).toContain('basis: "0"')
368
+ expect(result).not.toContain("flex:")
369
+ expect(result).not.toContain("TODO")
370
+ })
371
+
372
+ it("flags flex={0} inside a breakpoint with a TODO instead of duplicating an existing grow key", () => {
373
+ const input = `
374
+ import { StackView } from "@planningcenter/tapestry-react"
375
+
376
+ export default function Test() {
377
+ return <StackView mediaQueries={{ md: { flex: 0, grow: 5 } }}>Test</StackView>
378
+ }
379
+ `.trim()
380
+
381
+ const result = applyTransform(input)
382
+ expect(result).toContain("responsive=")
383
+ expect(result).toContain("TODO")
384
+ expect(result).toContain("grow: 5")
385
+ expect(result?.match(/grow:/g)).toHaveLength(1)
386
+ })
387
+
388
+ it("flags axis inside a breakpoint with a TODO instead of duplicating an existing direction key", () => {
389
+ const input = `
390
+ import { StackView } from "@planningcenter/tapestry-react"
391
+
392
+ export default function Test() {
393
+ return <StackView mediaQueries={{ md: { axis: "horizontal", direction: "column" } }}>Test</StackView>
394
+ }
395
+ `.trim()
396
+
397
+ const result = applyTransform(input)
398
+ expect(result).toContain("responsive=")
399
+ expect(result).toContain("TODO")
400
+ expect(result).toContain('direction: "column"')
401
+ expect(result?.match(/direction:/g)).toHaveLength(1)
402
+ })
403
+
404
+ it("flags both axis and flexDirection with a TODO when they both target direction in the same breakpoint", () => {
405
+ const input = `
406
+ import { StackView } from "@planningcenter/tapestry-react"
407
+
408
+ export default function Test() {
409
+ return <StackView mediaQueries={{ md: { axis: "horizontal", flexDirection: "column" } }}>Test</StackView>
410
+ }
411
+ `.trim()
412
+
413
+ const result = applyTransform(input)
414
+ expect(result).toContain("responsive=")
415
+ expect(result).toContain("TODO")
416
+ expect(result).toContain('axis: "horizontal"')
417
+ expect(result).toContain('flexDirection: "column"')
418
+ expect(result?.match(/direction:/g)).toBeNull()
419
+ })
420
+
421
+ it("flags alignItems inside a breakpoint with a TODO instead of duplicating an existing align key", () => {
422
+ const input = `
423
+ import { StackView } from "@planningcenter/tapestry-react"
424
+
425
+ export default function Test() {
426
+ return <StackView mediaQueries={{ md: { alignItems: "center", align: "start" } }}>Test</StackView>
427
+ }
428
+ `.trim()
429
+
430
+ const result = applyTransform(input)
431
+ expect(result).toContain("responsive=")
432
+ expect(result).toContain("TODO")
433
+ expect(result).toContain('align: "start"')
434
+ expect(result?.match(/align:/g)).toHaveLength(1)
435
+ })
436
+
437
+ it("flags an unsupported breakpoint (xxs) with a TODO", () => {
438
+ const input = `
439
+ import { StackView } from "@planningcenter/tapestry-react"
440
+
441
+ export default function Test() {
442
+ return <StackView mediaQueries={{ xxs: { axis: "vertical" } }}>Test</StackView>
443
+ }
444
+ `.trim()
445
+
446
+ const result = applyTransform(input)
447
+ expect(result).toContain("mediaQueries=")
448
+ expect(result).toContain("TODO: tapestry-migration (mediaQueries)")
449
+ expect(result).not.toContain("responsive=")
450
+ })
451
+
452
+ it("flags an unsupported breakpoint (xxl) with a TODO", () => {
453
+ const input = `
454
+ import { StackView } from "@planningcenter/tapestry-react"
455
+
456
+ export default function Test() {
457
+ return <StackView mediaQueries={{ xxl: { axis: "vertical" } }}>Test</StackView>
458
+ }
459
+ `.trim()
460
+
461
+ const result = applyTransform(input)
462
+ expect(result).toContain("mediaQueries=")
463
+ expect(result).toContain("TODO: tapestry-migration (mediaQueries)")
464
+ expect(result).not.toContain("responsive=")
465
+ })
466
+
467
+ it("flags a dynamic mediaQueries value with a TODO", () => {
468
+ const input = `
469
+ import { StackView } from "@planningcenter/tapestry-react"
470
+
471
+ export default function Test({ breakpoints }) {
472
+ return <StackView mediaQueries={breakpoints}>Test</StackView>
473
+ }
474
+ `.trim()
475
+
476
+ const result = applyTransform(input)
477
+ expect(result).toContain("mediaQueries=")
478
+ expect(result).toContain("TODO: tapestry-migration (mediaQueries)")
479
+ expect(result).not.toContain("responsive=")
480
+ })
481
+
482
+ it("flags a spread in the outer object with a TODO", () => {
483
+ const input = `
484
+ import { StackView } from "@planningcenter/tapestry-react"
485
+
486
+ export default function Test({ breakpoints }) {
487
+ return <StackView mediaQueries={{ ...breakpoints, md: { axis: "vertical" } }}>Test</StackView>
488
+ }
489
+ `.trim()
490
+
491
+ const result = applyTransform(input)
492
+ expect(result).toContain("mediaQueries=")
493
+ expect(result).toContain("TODO: tapestry-migration (mediaQueries)")
494
+ expect(result).not.toContain("responsive=")
495
+ })
496
+
497
+ it("flags unknown inner props with a TODO and still renames the attribute", () => {
498
+ const input = `
499
+ import { StackView } from "@planningcenter/tapestry-react"
500
+
501
+ export default function Test() {
502
+ return <StackView mediaQueries={{ md: { visible: false } }}>Test</StackView>
503
+ }
504
+ `.trim()
505
+
506
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
507
+ "import { StackView } from "@planningcenter/tapestry-react"
508
+
509
+ export default function Test() {
510
+ return (
511
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — visible: not a valid Flex responsive prop; remove or replace manually */
512
+ responsive={{ md: { visible: false } }}>Test</StackView>
513
+ );
514
+ }"
515
+ `)
516
+ })
517
+
518
+ it("flags unresolvable inner prop values with a TODO and still renames the attribute", () => {
519
+ const offScaleInput = `
520
+ import { StackView } from "@planningcenter/tapestry-react"
521
+ export default function Test() {
522
+ return <StackView mediaQueries={{ md: { spacing: 10 } }}>Test</StackView>
523
+ }
524
+ `.trim()
525
+ const dynamicInput = `
526
+ import { StackView } from "@planningcenter/tapestry-react"
527
+ export default function Test({ ax }) {
528
+ return <StackView mediaQueries={{ md: { axis: ax } }}>Test</StackView>
529
+ }
530
+ `.trim()
531
+
532
+ expect(applyTransform(offScaleInput)).toMatchInlineSnapshot(`
533
+ "import { StackView } from "@planningcenter/tapestry-react"
534
+ export default function Test() {
535
+ return (
536
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — spacing: could not migrate to gap automatically; use a Tapestry spacing scale number (0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7) */
537
+ responsive={{ md: { spacing: 10 } }}>Test</StackView>
538
+ );
539
+ }"
540
+ `)
541
+ expect(applyTransform(dynamicInput)).toMatchInlineSnapshot(`
542
+ "import { StackView } from "@planningcenter/tapestry-react"
543
+ export default function Test({ ax }) {
544
+ return (
545
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — axis: could not migrate to direction automatically; specify "horizontal" or "vertical" */
546
+ responsive={{ md: { axis: ax } }}>Test</StackView>
547
+ );
548
+ }"
549
+ `)
550
+ })
551
+
552
+ it("partially migrates: renames cleanly-migratable props and flags others with one TODO", () => {
553
+ const input = `
554
+ import { StackView } from "@planningcenter/tapestry-react"
555
+
556
+ export default function Test() {
557
+ return <StackView mediaQueries={{ md: { axis: "vertical", distribution: "fill", visible: false } }}>Test</StackView>
558
+ }
559
+ `.trim()
560
+
561
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
562
+ "import { StackView } from "@planningcenter/tapestry-react"
563
+
564
+ export default function Test() {
565
+ return (
566
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — distribution: "fill" has no Flex equivalent; use expand on child elements instead; visible: not a valid Flex responsive prop; remove or replace manually */
567
+ responsive={{ md: { direction: "column", distribution: "fill", visible: false } }}>Test</StackView>
568
+ );
569
+ }"
570
+ `)
571
+ })
572
+
573
+ it("adds a TODO for fill inside a breakpoint while still renaming the attribute", () => {
574
+ const distributionFillInput = `
575
+ import { StackView } from "@planningcenter/tapestry-react"
576
+ export default function Test() {
577
+ return <StackView mediaQueries={{ md: { distribution: "fill" } }}>Test</StackView>
578
+ }
579
+ `.trim()
580
+ const alignmentFillInput = `
581
+ import { StackView } from "@planningcenter/tapestry-react"
582
+ export default function Test() {
583
+ return <StackView mediaQueries={{ md: { alignment: "fill" } }}>Test</StackView>
584
+ }
585
+ `.trim()
586
+
587
+ expect(applyTransform(distributionFillInput)).toMatchInlineSnapshot(`
588
+ "import { StackView } from "@planningcenter/tapestry-react"
589
+ export default function Test() {
590
+ return (
591
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — distribution: "fill" has no Flex equivalent; use expand on child elements instead */
592
+ responsive={{ md: { distribution: "fill" } }}>Test</StackView>
593
+ );
594
+ }"
595
+ `)
596
+ expect(applyTransform(alignmentFillInput)).toMatchInlineSnapshot(`
597
+ "import { StackView } from "@planningcenter/tapestry-react"
598
+ export default function Test() {
599
+ return (
600
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — alignment: "fill" has no Flex equivalent; use expand on the child element instead */
601
+ responsive={{ md: { alignment: "fill" } }}>Test</StackView>
602
+ );
603
+ }"
604
+ `)
605
+ })
606
+
607
+ it("leaves a valid scale-number padding unchanged", () => {
608
+ const input = `
609
+ import { StackView } from "@planningcenter/tapestry-react"
610
+ export default function Test() {
611
+ return <StackView mediaQueries={{ md: { padding: 2 } }}>Test</StackView>
612
+ }
613
+ `.trim()
614
+
615
+ const result = applyTransform(input)
616
+ expect(result).toContain("responsive=")
617
+ expect(result).toContain("padding: 2")
618
+ expect(result).not.toContain("TODO")
619
+ })
620
+
621
+ it("converts a pixel-string padding to a token inside a breakpoint", () => {
622
+ const input = `
623
+ import { StackView } from "@planningcenter/tapestry-react"
624
+ export default function Test() {
625
+ return <StackView mediaQueries={{ md: { padding: "8px" } }}>Test</StackView>
626
+ }
627
+ `.trim()
628
+
629
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
630
+ "import { StackView } from "@planningcenter/tapestry-react"
631
+ export default function Test() {
632
+ return <StackView responsive={{ md: { padding: 1 } }}>Test</StackView>;
633
+ }"
634
+ `)
635
+ })
636
+
637
+ it("adds a TODO for unmappable padding inside a breakpoint", () => {
638
+ const input = `
639
+ import { StackView } from "@planningcenter/tapestry-react"
640
+ export default function Test() {
641
+ return <StackView mediaQueries={{ md: { padding: "10px" } }}>Test</StackView>
642
+ }
643
+ `.trim()
644
+
645
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
646
+ "import { StackView } from "@planningcenter/tapestry-react"
647
+ export default function Test() {
648
+ return (
649
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — padding: could not migrate padding automatically; use a Tapestry spacing scale number (0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7) for Flex padding, or use style={{ padding: ... }} for arbitrary CSS values */
650
+ responsive={{ md: { padding: "10px" } }}>Test</StackView>
651
+ );
652
+ }"
653
+ `)
654
+ })
655
+
656
+ it("renames paddingHorizontal to paddingInline for a valid scale number", () => {
657
+ const input = `
658
+ import { StackView } from "@planningcenter/tapestry-react"
659
+ export default function Test() {
660
+ return <StackView mediaQueries={{ md: { paddingHorizontal: 1 } }}>Test</StackView>
661
+ }
662
+ `.trim()
663
+
664
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
665
+ "import { StackView } from "@planningcenter/tapestry-react"
666
+ export default function Test() {
667
+ return <StackView responsive={{ md: { paddingInline: 1 } }}>Test</StackView>;
668
+ }"
669
+ `)
670
+ })
671
+
672
+ it("renames paddingHorizontal to paddingInline and converts a pixel string", () => {
673
+ const input = `
674
+ import { StackView } from "@planningcenter/tapestry-react"
675
+ export default function Test() {
676
+ return <StackView mediaQueries={{ md: { paddingHorizontal: "16px" } }}>Test</StackView>
677
+ }
678
+ `.trim()
679
+
680
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
681
+ "import { StackView } from "@planningcenter/tapestry-react"
682
+ export default function Test() {
683
+ return <StackView responsive={{ md: { paddingInline: 2 } }}>Test</StackView>;
684
+ }"
685
+ `)
686
+ })
687
+
688
+ it("adds a TODO for unmappable paddingHorizontal inside a breakpoint", () => {
689
+ const input = `
690
+ import { StackView } from "@planningcenter/tapestry-react"
691
+ export default function Test() {
692
+ return <StackView mediaQueries={{ md: { paddingHorizontal: "10px" } }}>Test</StackView>
693
+ }
694
+ `.trim()
695
+
696
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
697
+ "import { StackView } from "@planningcenter/tapestry-react"
698
+ export default function Test() {
699
+ return (
700
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — paddingHorizontal: could not migrate padding automatically; use a Tapestry spacing scale number (0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7) for Flex padding, or use style={{ padding: ... }} for arbitrary CSS values */
701
+ responsive={{ md: { paddingHorizontal: "10px" } }}>Test</StackView>
702
+ );
703
+ }"
704
+ `)
705
+ })
706
+
707
+ it("renames paddingVertical to paddingBlock for a valid scale number", () => {
708
+ const input = `
709
+ import { StackView } from "@planningcenter/tapestry-react"
710
+ export default function Test() {
711
+ return <StackView mediaQueries={{ md: { paddingVertical: 1 } }}>Test</StackView>
712
+ }
713
+ `.trim()
714
+
715
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
716
+ "import { StackView } from "@planningcenter/tapestry-react"
717
+ export default function Test() {
718
+ return <StackView responsive={{ md: { paddingBlock: 1 } }}>Test</StackView>;
719
+ }"
720
+ `)
721
+ })
722
+
723
+ it("renames paddingVertical to paddingBlock and converts a pixel string", () => {
724
+ const input = `
725
+ import { StackView } from "@planningcenter/tapestry-react"
726
+ export default function Test() {
727
+ return <StackView mediaQueries={{ md: { paddingVertical: "16px" } }}>Test</StackView>
728
+ }
729
+ `.trim()
730
+
731
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
732
+ "import { StackView } from "@planningcenter/tapestry-react"
733
+ export default function Test() {
734
+ return <StackView responsive={{ md: { paddingBlock: 2 } }}>Test</StackView>;
735
+ }"
736
+ `)
737
+ })
738
+
739
+ it("adds a TODO for unmappable paddingVertical inside a breakpoint", () => {
740
+ const input = `
741
+ import { StackView } from "@planningcenter/tapestry-react"
742
+ export default function Test() {
743
+ return <StackView mediaQueries={{ md: { paddingVertical: "10px" } }}>Test</StackView>
744
+ }
745
+ `.trim()
746
+
747
+ expect(applyTransform(input)).toMatchInlineSnapshot(`
748
+ "import { StackView } from "@planningcenter/tapestry-react"
749
+ export default function Test() {
750
+ return (
751
+ <StackView /* TODO: tapestry-migration (responsive): could not automatically migrate all breakpoint props — paddingVertical: could not migrate padding automatically; use a Tapestry spacing scale number (0, 0.25, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7) for Flex padding, or use style={{ padding: ... }} for arbitrary CSS values */
752
+ responsive={{ md: { paddingVertical: "10px" } }}>Test</StackView>
753
+ );
754
+ }"
755
+ `)
756
+ })
757
+
758
+ it("respects an aliased StackView import", () => {
759
+ const input = `
760
+ import { StackView as Stack } from "@planningcenter/tapestry-react"
761
+
762
+ export default function Test() {
763
+ return <Stack mediaQueries={{ md: { axis: "vertical" } }}>Test</Stack>
764
+ }
765
+ `.trim()
766
+
767
+ const result = applyTransform(input)
768
+ expect(result).toContain("responsive=")
769
+ expect(result).not.toContain("mediaQueries=")
770
+ })
771
+
772
+ it("returns null when there is no mediaQueries to migrate", () => {
773
+ const input = `
774
+ import { StackView } from "@planningcenter/tapestry-react"
775
+
776
+ export default function Test() {
777
+ return <StackView>Test</StackView>
778
+ }
779
+ `.trim()
780
+
781
+ expect(applyTransform(input)).toBe(null)
782
+ })
783
+
784
+ it("returns null when StackView is not imported from tapestry-react", () => {
785
+ const input = `
786
+ import { StackView } from "some-other-package"
787
+
788
+ export default function Test() {
789
+ return <StackView mediaQueries={{ md: { axis: "vertical" } }}>Test</StackView>
790
+ }
791
+ `.trim()
792
+
793
+ expect(applyTransform(input)).toBe(null)
794
+ })
795
+ })