@planningcenter/tapestry-migration-cli 3.2.3-rc.4 → 3.2.3-rc.6

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 (46) hide show
  1. package/package.json +3 -3
  2. package/src/components/button/transforms/unsupportedProps.ts +3 -31
  3. package/src/components/checkbox/transforms/unsupportedProps.ts +3 -28
  4. package/src/components/date-picker/index.ts +56 -0
  5. package/src/components/date-picker/transforms/auditSpreadProps.test.ts +97 -0
  6. package/src/components/date-picker/transforms/auditSpreadProps.ts +10 -0
  7. package/src/components/date-picker/transforms/convertStyleProps.test.ts +58 -0
  8. package/src/components/date-picker/transforms/convertStyleProps.ts +10 -0
  9. package/src/components/date-picker/transforms/maxDateToMax.test.ts +87 -0
  10. package/src/components/date-picker/transforms/maxDateToMax.ts +16 -0
  11. package/src/components/date-picker/transforms/mergeFieldIntoDateField.test.ts +240 -0
  12. package/src/components/date-picker/transforms/mergeFieldIntoDateField.ts +5 -0
  13. package/src/components/date-picker/transforms/minDateToMin.test.ts +87 -0
  14. package/src/components/date-picker/transforms/minDateToMin.ts +16 -0
  15. package/src/components/date-picker/transforms/momentToDateString.test.ts +240 -0
  16. package/src/components/date-picker/transforms/momentToDateString.ts +87 -0
  17. package/src/components/date-picker/transforms/moveDatePickerImport.test.ts +157 -0
  18. package/src/components/date-picker/transforms/moveDatePickerImport.ts +14 -0
  19. package/src/components/date-picker/transforms/nativeDateToString.test.ts +220 -0
  20. package/src/components/date-picker/transforms/nativeDateToString.ts +59 -0
  21. package/src/components/date-picker/transforms/removeDuplicateKeys.test.ts +120 -0
  22. package/src/components/date-picker/transforms/removeDuplicateKeys.ts +8 -0
  23. package/src/components/date-picker/transforms/removeFormatValue.test.ts +119 -0
  24. package/src/components/date-picker/transforms/removeFormatValue.ts +22 -0
  25. package/src/components/date-picker/transforms/removePlaceholder.test.ts +117 -0
  26. package/src/components/date-picker/transforms/removePlaceholder.ts +22 -0
  27. package/src/components/date-picker/transforms/sizeMapping.test.ts +173 -0
  28. package/src/components/date-picker/transforms/sizeMapping.ts +15 -0
  29. package/src/components/date-picker/transforms/stateToInvalid.test.ts +109 -0
  30. package/src/components/date-picker/transforms/stateToInvalid.ts +57 -0
  31. package/src/components/date-picker/transforms/stateToInvalidTernary.test.ts +72 -0
  32. package/src/components/date-picker/transforms/stateToInvalidTernary.ts +11 -0
  33. package/src/components/date-picker/transforms/unsupportedProps.test.ts +170 -0
  34. package/src/components/date-picker/transforms/unsupportedProps.ts +10 -0
  35. package/src/components/input/transforms/unsupportedProps.ts +2 -2
  36. package/src/components/link/transforms/unsupportedProps.test.ts +3 -1
  37. package/src/components/link/transforms/unsupportedProps.ts +9 -37
  38. package/src/components/radio/transforms/unsupportedProps.ts +3 -28
  39. package/src/components/select/transforms/unsupportedProps.ts +9 -35
  40. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +22 -0
  41. package/src/components/shared/transformFactories/unsupportedPropsFactory.test.ts +162 -0
  42. package/src/components/shared/transformFactories/unsupportedPropsFactory.ts +60 -0
  43. package/src/components/text-area/transforms/unsupportedProps.ts +3 -28
  44. package/src/components/time-field/transforms/unsupportedProps.ts +3 -30
  45. package/src/components/toggle-switch/transforms/unsupportedProps.ts +3 -28
  46. package/src/index.ts +7 -1
@@ -0,0 +1,157 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./moveDatePickerImport"
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("moveDatePickerImport transform", () => {
19
+ describe("import migration", () => {
20
+ it("should rename DateField to DatePicker and change import", () => {
21
+ const input = `
22
+ import { DateField } from "@planningcenter/tapestry-react"
23
+
24
+ function Test() {
25
+ return <DateField value={date} onChange={setDate} />
26
+ }
27
+ `.trim()
28
+
29
+ const result = applyTransform(input)
30
+ expect(result).toContain(
31
+ 'import { DatePicker } from "@planningcenter/tapestry"'
32
+ )
33
+ expect(result).not.toContain("@planningcenter/tapestry-react")
34
+ expect(result).toContain("<DatePicker")
35
+ expect(result).not.toContain("<DateField")
36
+ })
37
+
38
+ it("should only move DateField, leaving other imports in place", () => {
39
+ const input = `
40
+ import { Button } from "@planningcenter/tapestry-react"
41
+ import { DateField } from "@planningcenter/tapestry-react"
42
+
43
+ function Test() {
44
+ return (
45
+ <div>
46
+ <Button>Click</Button>
47
+ <DateField value={date} onChange={setDate} />
48
+ </div>
49
+ )
50
+ }
51
+ `.trim()
52
+
53
+ const result = applyTransform(input)
54
+ expect(result).toContain(
55
+ 'import { Button } from "@planningcenter/tapestry-react"'
56
+ )
57
+ expect(result).toContain(
58
+ 'import { DatePicker } from "@planningcenter/tapestry"'
59
+ )
60
+ expect(result).toContain("<DatePicker")
61
+ })
62
+ })
63
+
64
+ describe("import conflict handling", () => {
65
+ it("should use TdsDatePicker alias when DatePicker is already imported from another package", () => {
66
+ const input = `
67
+ import { DatePicker } from "some-other-library"
68
+ import { DateField } from "@planningcenter/tapestry-react"
69
+
70
+ function Test() {
71
+ return (
72
+ <div>
73
+ <DatePicker />
74
+ <DateField value={date} onChange={setDate} />
75
+ </div>
76
+ )
77
+ }
78
+ `.trim()
79
+
80
+ const result = applyTransform(input)
81
+ expect(result).toContain(
82
+ 'import { DatePicker } from "some-other-library"'
83
+ )
84
+ expect(result).toContain(
85
+ 'import { DatePicker as TdsDatePicker } from "@planningcenter/tapestry"'
86
+ )
87
+ expect(result).toContain("<TdsDatePicker")
88
+ expect(result).not.toContain("<DateField")
89
+ })
90
+ })
91
+
92
+ describe("edge cases", () => {
93
+ it("should not affect other components", () => {
94
+ const input = `
95
+ import { Button } from "@planningcenter/tapestry-react"
96
+
97
+ function Test() {
98
+ return <Button>Click me</Button>
99
+ }
100
+ `.trim()
101
+
102
+ const result = applyTransform(input)
103
+ expect(result).toContain(
104
+ 'import { Button } from "@planningcenter/tapestry-react"'
105
+ )
106
+ })
107
+
108
+ it("should handle no imports", () => {
109
+ const input = `
110
+ function Test() {
111
+ return <div>No imports</div>
112
+ }
113
+ `.trim()
114
+
115
+ const result = applyTransform(input)
116
+ expect(result).toBe(input)
117
+ })
118
+
119
+ it("should preserve all attributes", () => {
120
+ const input = `
121
+ import { DateField } from "@planningcenter/tapestry-react"
122
+
123
+ function Test() {
124
+ return (
125
+ <DateField
126
+ value={date}
127
+ onChange={setDate}
128
+ disabled
129
+ />
130
+ )
131
+ }
132
+ `.trim()
133
+
134
+ const result = applyTransform(input)
135
+ expect(result).toContain(
136
+ 'import { DatePicker } from "@planningcenter/tapestry"'
137
+ )
138
+ expect(result).toContain("value={date}")
139
+ expect(result).toContain("onChange={setDate}")
140
+ expect(result).toContain("disabled")
141
+ })
142
+
143
+ it("should handle alias import", () => {
144
+ const input = `
145
+ import { DateField as TapestryDateField } from "@planningcenter/tapestry-react"
146
+
147
+ function Test() {
148
+ return <TapestryDateField value={date} onChange={setDate} />
149
+ }
150
+ `.trim()
151
+
152
+ const result = applyTransform(input)
153
+ expect(result).toContain("@planningcenter/tapestry")
154
+ expect(result).not.toContain("@planningcenter/tapestry-react")
155
+ })
156
+ })
157
+ })
@@ -0,0 +1,14 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { componentTransformFactory } from "../../shared/transformFactories/componentTransformFactory"
4
+
5
+ const transform: Transform = componentTransformFactory({
6
+ condition: () => true,
7
+ conflictAlias: "TdsDatePicker",
8
+ fromComponent: "DateField",
9
+ fromPackage: "@planningcenter/tapestry-react",
10
+ toComponent: "DatePicker",
11
+ toPackage: "@planningcenter/tapestry",
12
+ })
13
+
14
+ export default transform
@@ -0,0 +1,220 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./nativeDateToString"
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("nativeDateToString transform", () => {
18
+ describe("new Date with string literal", () => {
19
+ it("converts new Date('2024-01-01') in value to string literal", () => {
20
+ const input = `
21
+ import { DateField } from "@planningcenter/tapestry-react"
22
+
23
+ function Test() {
24
+ return <DateField value={new Date("2024-01-01")} onChange={setDate} />
25
+ }
26
+ `.trim()
27
+
28
+ const result = applyTransform(input)
29
+ expect(result).not.toBeNull()
30
+ expect(result).toContain('{"2024-01-01"}')
31
+ expect(result).not.toContain("value={new Date")
32
+ expect(result).toContain("CHANGED: tapestry-migration")
33
+ expect(result).toContain("string literal")
34
+ })
35
+
36
+ it("converts new Date('2024-01-01') in minDate to string literal", () => {
37
+ const input = `
38
+ import { DateField } from "@planningcenter/tapestry-react"
39
+
40
+ function Test() {
41
+ return <DateField minDate={new Date("2024-01-01")} value={date} onChange={setDate} />
42
+ }
43
+ `.trim()
44
+
45
+ const result = applyTransform(input)
46
+ expect(result).not.toBeNull()
47
+ expect(result).toContain('{"2024-01-01"}')
48
+ expect(result).toContain("Verify the date string")
49
+ })
50
+
51
+ it("converts new Date('2024-12-31') in maxDate to string literal", () => {
52
+ const input = `
53
+ import { DateField } from "@planningcenter/tapestry-react"
54
+
55
+ function Test() {
56
+ return <DateField maxDate={new Date("2024-12-31")} value={date} onChange={setDate} />
57
+ }
58
+ `.trim()
59
+
60
+ const result = applyTransform(input)
61
+ expect(result).not.toBeNull()
62
+ expect(result).toContain('{"2024-12-31"}')
63
+ expect(result).not.toContain("maxDate={new Date")
64
+ })
65
+ })
66
+
67
+ describe("new Date with dynamic arguments", () => {
68
+ it("adds TODO for new Date() with no arguments", () => {
69
+ const input = `
70
+ import { DateField } from "@planningcenter/tapestry-react"
71
+
72
+ function Test() {
73
+ return <DateField minDate={new Date()} value={date} onChange={setDate} />
74
+ }
75
+ `.trim()
76
+
77
+ const result = applyTransform(input)
78
+ expect(result).not.toBeNull()
79
+ expect(result).toContain("TODO: tapestry-migration")
80
+ expect(result).toContain("native Date is not supported")
81
+ })
82
+
83
+ it("adds TODO for new Date(variable)", () => {
84
+ const input = `
85
+ import { DateField } from "@planningcenter/tapestry-react"
86
+
87
+ function Test() {
88
+ return <DateField value={new Date(starts)} onChange={setDate} />
89
+ }
90
+ `.trim()
91
+
92
+ const result = applyTransform(input)
93
+ expect(result).not.toBeNull()
94
+ expect(result).toContain("TODO: tapestry-migration")
95
+ expect(result).toContain("native Date is not supported")
96
+ expect(result).toContain("ISO date string")
97
+ })
98
+
99
+ it("adds TODO for new Date with numeric arguments", () => {
100
+ const input = `
101
+ import { DateField } from "@planningcenter/tapestry-react"
102
+
103
+ function Test() {
104
+ return <DateField value={new Date(2024, 0, 1)} onChange={setDate} />
105
+ }
106
+ `.trim()
107
+
108
+ const result = applyTransform(input)
109
+ expect(result).not.toBeNull()
110
+ expect(result).toContain("TODO: tapestry-migration")
111
+ expect(result).toContain("native Date is not supported")
112
+ })
113
+
114
+ it("adds TODO for new Date in maxDate prop", () => {
115
+ const input = `
116
+ import { DateField } from "@planningcenter/tapestry-react"
117
+
118
+ function Test() {
119
+ return <DateField maxDate={new Date(ends)} value={date} onChange={setDate} />
120
+ }
121
+ `.trim()
122
+
123
+ const result = applyTransform(input)
124
+ expect(result).not.toBeNull()
125
+ expect(result).toContain("TODO: tapestry-migration")
126
+ expect(result).toContain("native Date is not supported")
127
+ })
128
+ })
129
+
130
+ describe("multiple props with new Date", () => {
131
+ it("handles new Date in both minDate and maxDate", () => {
132
+ const input = `
133
+ import { DateField } from "@planningcenter/tapestry-react"
134
+
135
+ function Test() {
136
+ return (
137
+ <DateField
138
+ minDate={new Date("2024-01-01")}
139
+ maxDate={new Date(ends)}
140
+ value={date}
141
+ onChange={setDate}
142
+ />
143
+ )
144
+ }
145
+ `.trim()
146
+
147
+ const result = applyTransform(input)
148
+ expect(result).not.toBeNull()
149
+ expect(result).toContain('{"2024-01-01"}')
150
+ expect(result).toContain("native Date is not supported")
151
+ })
152
+ })
153
+
154
+ describe("edge cases", () => {
155
+ it("does not affect non-date props with new Date", () => {
156
+ const input = `
157
+ import { DateField } from "@planningcenter/tapestry-react"
158
+
159
+ function Test() {
160
+ return <DateField onChange={new Date()} value={date} />
161
+ }
162
+ `.trim()
163
+
164
+ const result = applyTransform(input)
165
+ expect(result).toBeNull()
166
+ })
167
+
168
+ it("does not affect other components", () => {
169
+ const input = `
170
+ import { Input } from "@planningcenter/tapestry-react"
171
+
172
+ function Test() {
173
+ return <Input value={new Date()} label="Name" />
174
+ }
175
+ `.trim()
176
+
177
+ const result = applyTransform(input)
178
+ expect(result).toBeNull()
179
+ })
180
+
181
+ it("does not affect DateField from other packages", () => {
182
+ const input = `
183
+ import { DateField } from "other-library"
184
+
185
+ function Test() {
186
+ return <DateField value={new Date("2024-01-01")} onChange={setDate} />
187
+ }
188
+ `.trim()
189
+
190
+ const result = applyTransform(input)
191
+ expect(result).toBeNull()
192
+ })
193
+
194
+ it("does not transform variable references", () => {
195
+ const input = `
196
+ import { DateField } from "@planningcenter/tapestry-react"
197
+
198
+ function Test() {
199
+ return <DateField value={startDate} onChange={setDate} />
200
+ }
201
+ `.trim()
202
+
203
+ const result = applyTransform(input)
204
+ expect(result).toBeNull()
205
+ })
206
+
207
+ it("does not transform moment expressions (handled by separate transform)", () => {
208
+ const input = `
209
+ import { DateField } from "@planningcenter/tapestry-react"
210
+
211
+ function Test() {
212
+ return <DateField value={moment().toDate()} onChange={setDate} />
213
+ }
214
+ `.trim()
215
+
216
+ const result = applyTransform(input)
217
+ expect(result).toBeNull()
218
+ })
219
+ })
220
+ })
@@ -0,0 +1,59 @@
1
+ import { Expression, NewExpression, Transform } from "jscodeshift"
2
+
3
+ import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
4
+ import { getAttribute } from "../../shared/actions/getAttribute"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ const DATE_PROPS = ["minDate", "maxDate", "value"]
8
+
9
+ function isNewDate(expr: Expression): expr is NewExpression {
10
+ return (
11
+ expr.type === "NewExpression" &&
12
+ expr.callee.type === "Identifier" &&
13
+ expr.callee.name === "Date"
14
+ )
15
+ }
16
+
17
+ const transform: Transform = attributeTransformFactory({
18
+ targetComponent: "DateField",
19
+ targetPackage: "@planningcenter/tapestry-react",
20
+ transform: (element, { j }) => {
21
+ let hasChanges = false
22
+
23
+ for (const propName of DATE_PROPS) {
24
+ const attr = getAttribute({ element, name: propName })
25
+ if (!attr?.value) continue
26
+ if (attr.value.type !== "JSXExpressionContainer") continue
27
+
28
+ const expr = attr.value.expression
29
+ if (expr.type === "JSXEmptyExpression") continue
30
+ if (!isNewDate(expr as Expression)) continue
31
+
32
+ const newExpr = expr as NewExpression
33
+ const arg = newExpr.arguments[0]
34
+
35
+ if (arg && arg.type === "StringLiteral") {
36
+ attr.value = j.jsxExpressionContainer(j.stringLiteral(arg.value))
37
+
38
+ addCommentToAttribute({
39
+ attribute: attr,
40
+ commentKind: "change",
41
+ j,
42
+ text: `Converted new Date("${arg.value}") to string literal — DatePicker accepts ISO date strings. Verify the date string is in YYYY-MM-DD format.`,
43
+ })
44
+ hasChanges = true
45
+ } else {
46
+ addCommentToAttribute({
47
+ attribute: attr,
48
+ j,
49
+ text: `native Date is not supported by DatePicker. Convert to an ISO date string (YYYY-MM-DD) or DateValue from @internationalized/date.`,
50
+ })
51
+ hasChanges = true
52
+ }
53
+ }
54
+
55
+ return hasChanges
56
+ },
57
+ })
58
+
59
+ export default transform
@@ -0,0 +1,120 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeDuplicateKeys"
5
+
6
+ const j = jscodeshift.withParser("tsx")
7
+
8
+ function applyTransform(source: string) {
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("removeDuplicateKeys transform", () => {
18
+ it("removes duplicate attributes and keeps the first occurrence", () => {
19
+ const source = `
20
+ import { DateField } from "@planningcenter/tapestry-react"
21
+
22
+ export function Test() {
23
+ return <DateField size="md" disabled size="lg" value={date} onChange={setDate} />
24
+ }
25
+ `.trim()
26
+
27
+ const result = applyTransform(source)
28
+ expect(result).not.toBeNull()
29
+ expect(result).toContain('size="md"')
30
+ expect(result).not.toContain('size="lg"')
31
+ expect(result).toContain("disabled")
32
+ expect(result).toContain("TODO: tapestry-migration (duplicate-props)")
33
+ expect(result).toContain("Removed duplicate key: size = lg")
34
+ })
35
+
36
+ it("handles multiple duplicates on a single element", () => {
37
+ const source = `
38
+ import { DateField } from "@planningcenter/tapestry-react"
39
+
40
+ export function Test() {
41
+ return (
42
+ <DateField
43
+ value={first}
44
+ onChange={firstHandler}
45
+ value={second}
46
+ onChange={secondHandler}
47
+ />
48
+ )
49
+ }
50
+ `.trim()
51
+
52
+ const result = applyTransform(source)
53
+ expect(result).not.toBeNull()
54
+ expect(result).toContain("value={first}")
55
+ expect(result).toContain("onChange={firstHandler}")
56
+ expect(result).not.toContain("value={second}")
57
+ expect(result).not.toContain("onChange={secondHandler}")
58
+ })
59
+
60
+ it("returns null when there are no duplicates", () => {
61
+ const source = `
62
+ import { DateField } from "@planningcenter/tapestry-react"
63
+
64
+ export function Test() {
65
+ return <DateField size="md" value={date} onChange={setDate} />
66
+ }
67
+ `.trim()
68
+
69
+ expect(applyTransform(source)).toBeNull()
70
+ })
71
+
72
+ it("handles aliased DateField imports", () => {
73
+ const source = `
74
+ import { DateField as TapestryDateField } from "@planningcenter/tapestry-react"
75
+
76
+ export function Test() {
77
+ return <TapestryDateField size="md" size="lg" value={date} onChange={setDate} />
78
+ }
79
+ `.trim()
80
+
81
+ const result = applyTransform(source)
82
+ expect(result).not.toBeNull()
83
+ expect(result).toContain('size="md"')
84
+ expect(result).not.toContain('size="lg"')
85
+ })
86
+
87
+ it("does not affect DateField imported from a different package", () => {
88
+ const source = `
89
+ import { DateField } from "@planningcenter/tapestry"
90
+
91
+ export function Test() {
92
+ return <DateField size="md" size="lg" />
93
+ }
94
+ `.trim()
95
+
96
+ expect(applyTransform(source)).toBeNull()
97
+ })
98
+
99
+ it("does not touch other components with duplicate attributes", () => {
100
+ const source = `
101
+ import { DateField } from "@planningcenter/tapestry-react"
102
+
103
+ export function Test() {
104
+ return (
105
+ <div>
106
+ <DateField size="md" size="lg" value={date} onChange={setDate} />
107
+ <input type="text" type="email" />
108
+ </div>
109
+ )
110
+ }
111
+ `.trim()
112
+
113
+ const result = applyTransform(source)
114
+ expect(result).not.toBeNull()
115
+ expect(result).toContain('size="md"')
116
+ expect(result).not.toContain('size="lg"')
117
+ expect(result).toContain('type="text"')
118
+ expect(result).toContain('type="email"')
119
+ })
120
+ })
@@ -0,0 +1,8 @@
1
+ import { removeDuplicateKeys } from "../../shared/actions/removeDuplicateKeys"
2
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
3
+
4
+ export default attributeTransformFactory({
5
+ targetComponent: "DateField",
6
+ targetPackage: "@planningcenter/tapestry-react",
7
+ transform: removeDuplicateKeys,
8
+ })
@@ -0,0 +1,119 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removeFormatValue"
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
+ function stripComments(source: string): string {
18
+ return source.replace(/\/\*[\s\S]*?\*\//g, "")
19
+ }
20
+
21
+ describe("removeFormatValue transform", () => {
22
+ it("removes formatValue and adds a TODO comment with the removed value", () => {
23
+ const input = `
24
+ import { DateField } from "@planningcenter/tapestry-react"
25
+
26
+ export default function Test() {
27
+ return <DateField formatValue="MM/dd/yyyy" value={date} onChange={setDate} />
28
+ }
29
+ `.trim()
30
+
31
+ const result = applyTransform(input)
32
+ expect(stripComments(result || "")).not.toContain("formatValue")
33
+ expect(result).toContain("TODO: tapestry-migration (formatValue)")
34
+ expect(result).toContain(
35
+ "DateField formats dates automatically based on the user's locale"
36
+ )
37
+ expect(result).toContain('removed: formatValue="MM/dd/yyyy"')
38
+ expect(result).toContain("value={date}")
39
+ expect(result).toContain("onChange={setDate}")
40
+ })
41
+
42
+ it("preserves other attributes", () => {
43
+ const input = `
44
+ import { DateField } from "@planningcenter/tapestry-react"
45
+
46
+ export default function Test() {
47
+ return (
48
+ <DateField
49
+ formatValue="MM/dd/yyyy"
50
+ label="Start date"
51
+ value={date}
52
+ onChange={setDate}
53
+ disabled
54
+ />
55
+ )
56
+ }
57
+ `.trim()
58
+
59
+ const result = applyTransform(input)
60
+ expect(result).toContain('label="Start date"')
61
+ expect(result).toContain("value={date}")
62
+ expect(result).toContain("onChange={setDate}")
63
+ expect(result).toContain("disabled")
64
+ expect(stripComments(result || "")).not.toContain("formatValue")
65
+ })
66
+
67
+ it("handles aliased DateField imports", () => {
68
+ const input = `
69
+ import { DateField as TapestryDateField } from "@planningcenter/tapestry-react"
70
+
71
+ export default function Test() {
72
+ return <TapestryDateField formatValue="MM/dd/yyyy" value={date} onChange={setDate} />
73
+ }
74
+ `.trim()
75
+
76
+ const result = applyTransform(input)
77
+ expect(stripComments(result || "")).not.toContain("formatValue")
78
+ expect(result).toContain("TODO: tapestry-migration (formatValue)")
79
+ })
80
+
81
+ it("returns null when DateField has no formatValue", () => {
82
+ const input = `
83
+ import { DateField } from "@planningcenter/tapestry-react"
84
+
85
+ export default function Test() {
86
+ return <DateField value={date} onChange={setDate} />
87
+ }
88
+ `.trim()
89
+
90
+ const result = applyTransform(input)
91
+ expect(result).toBe(null)
92
+ })
93
+
94
+ it("does not affect DateField imported from a different package", () => {
95
+ const input = `
96
+ import { DateField } from "@planningcenter/tapestry"
97
+
98
+ export default function Test() {
99
+ return <DateField formatValue="MM/dd/yyyy" />
100
+ }
101
+ `.trim()
102
+
103
+ const result = applyTransform(input)
104
+ expect(result).toBe(null)
105
+ })
106
+
107
+ it("does not affect other components with the same prop", () => {
108
+ const input = `
109
+ import { Input } from "@planningcenter/tapestry-react"
110
+
111
+ export default function Test() {
112
+ return <Input formatValue="abc" />
113
+ }
114
+ `.trim()
115
+
116
+ const result = applyTransform(input)
117
+ expect(result).toBe(null)
118
+ })
119
+ })