@planningcenter/tapestry-migration-cli 3.2.3-rc.3 → 3.2.3-rc.5

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/package.json +3 -3
  2. package/src/components/date-picker/index.ts +56 -0
  3. package/src/components/date-picker/transforms/auditSpreadProps.test.ts +97 -0
  4. package/src/components/date-picker/transforms/auditSpreadProps.ts +10 -0
  5. package/src/components/date-picker/transforms/convertStyleProps.test.ts +58 -0
  6. package/src/components/date-picker/transforms/convertStyleProps.ts +10 -0
  7. package/src/components/date-picker/transforms/maxDateToMax.test.ts +87 -0
  8. package/src/components/date-picker/transforms/maxDateToMax.ts +16 -0
  9. package/src/components/date-picker/transforms/mergeFieldIntoDateField.test.ts +240 -0
  10. package/src/components/date-picker/transforms/mergeFieldIntoDateField.ts +5 -0
  11. package/src/components/date-picker/transforms/minDateToMin.test.ts +87 -0
  12. package/src/components/date-picker/transforms/minDateToMin.ts +16 -0
  13. package/src/components/date-picker/transforms/momentToDateString.test.ts +240 -0
  14. package/src/components/date-picker/transforms/momentToDateString.ts +87 -0
  15. package/src/components/date-picker/transforms/moveDatePickerImport.test.ts +157 -0
  16. package/src/components/date-picker/transforms/moveDatePickerImport.ts +14 -0
  17. package/src/components/date-picker/transforms/nativeDateToString.test.ts +220 -0
  18. package/src/components/date-picker/transforms/nativeDateToString.ts +59 -0
  19. package/src/components/date-picker/transforms/removeDuplicateKeys.test.ts +120 -0
  20. package/src/components/date-picker/transforms/removeDuplicateKeys.ts +8 -0
  21. package/src/components/date-picker/transforms/removeFormatValue.test.ts +119 -0
  22. package/src/components/date-picker/transforms/removeFormatValue.ts +22 -0
  23. package/src/components/date-picker/transforms/removePlaceholder.test.ts +117 -0
  24. package/src/components/date-picker/transforms/removePlaceholder.ts +22 -0
  25. package/src/components/date-picker/transforms/sizeMapping.test.ts +173 -0
  26. package/src/components/date-picker/transforms/sizeMapping.ts +15 -0
  27. package/src/components/date-picker/transforms/stateToInvalid.test.ts +109 -0
  28. package/src/components/date-picker/transforms/stateToInvalid.ts +57 -0
  29. package/src/components/date-picker/transforms/stateToInvalidTernary.test.ts +72 -0
  30. package/src/components/date-picker/transforms/stateToInvalidTernary.ts +11 -0
  31. package/src/components/date-picker/transforms/unsupportedProps.test.ts +170 -0
  32. package/src/components/date-picker/transforms/unsupportedProps.ts +37 -0
  33. package/src/components/shared/helpers/unsupportedPropsHelpers.ts +18 -0
  34. package/src/index.ts +1 -0
@@ -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
+ })
@@ -0,0 +1,22 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
4
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ function buildComment(propName: string, formattedValue: string) {
8
+ const valuePart = formattedValue
9
+ ? ` - removed: ${propName}=${formattedValue}`
10
+ : ""
11
+ return `prop is not supported. DateField formats dates automatically based on the user's locale.${valuePart}`
12
+ }
13
+
14
+ const transform: Transform = attributeTransformFactory({
15
+ condition: hasAttribute("formatValue"),
16
+ targetComponent: "DateField",
17
+ targetPackage: "@planningcenter/tapestry-react",
18
+ transform: (element, { j, source }) =>
19
+ removeAttribute("formatValue", { buildComment, element, j, source }),
20
+ })
21
+
22
+ export default transform
@@ -0,0 +1,117 @@
1
+ import jscodeshift from "jscodeshift"
2
+ import { describe, expect, it } from "vitest"
3
+
4
+ import transform from "./removePlaceholder"
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("removePlaceholder transform", () => {
22
+ it("removes placeholder 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 placeholder="Pick a date" value={date} onChange={setDate} />
28
+ }
29
+ `.trim()
30
+
31
+ const result = applyTransform(input)
32
+ expect(stripComments(result || "")).not.toContain("placeholder")
33
+ expect(result).toContain("TODO: tapestry-migration (placeholder)")
34
+ expect(result).toContain("locale-aware date segments")
35
+ expect(result).toContain('removed: placeholder="Pick a date"')
36
+ expect(result).toContain("value={date}")
37
+ expect(result).toContain("onChange={setDate}")
38
+ })
39
+
40
+ it("preserves other attributes", () => {
41
+ const input = `
42
+ import { DateField } from "@planningcenter/tapestry-react"
43
+
44
+ export default function Test() {
45
+ return (
46
+ <DateField
47
+ placeholder="Pick a date"
48
+ label="Start date"
49
+ value={date}
50
+ onChange={setDate}
51
+ disabled
52
+ />
53
+ )
54
+ }
55
+ `.trim()
56
+
57
+ const result = applyTransform(input)
58
+ expect(result).toContain('label="Start date"')
59
+ expect(result).toContain("value={date}")
60
+ expect(result).toContain("onChange={setDate}")
61
+ expect(result).toContain("disabled")
62
+ expect(stripComments(result || "")).not.toContain("placeholder")
63
+ })
64
+
65
+ it("handles aliased DateField imports", () => {
66
+ const input = `
67
+ import { DateField as TapestryDateField } from "@planningcenter/tapestry-react"
68
+
69
+ export default function Test() {
70
+ return <TapestryDateField placeholder="Pick a date" value={date} onChange={setDate} />
71
+ }
72
+ `.trim()
73
+
74
+ const result = applyTransform(input)
75
+ expect(stripComments(result || "")).not.toContain("placeholder")
76
+ expect(result).toContain("TODO: tapestry-migration (placeholder)")
77
+ })
78
+
79
+ it("returns null when DateField has no placeholder", () => {
80
+ const input = `
81
+ import { DateField } from "@planningcenter/tapestry-react"
82
+
83
+ export default function Test() {
84
+ return <DateField value={date} onChange={setDate} />
85
+ }
86
+ `.trim()
87
+
88
+ const result = applyTransform(input)
89
+ expect(result).toBe(null)
90
+ })
91
+
92
+ it("does not affect DateField imported from a different package", () => {
93
+ const input = `
94
+ import { DateField } from "@planningcenter/tapestry"
95
+
96
+ export default function Test() {
97
+ return <DateField placeholder="Pick a date" />
98
+ }
99
+ `.trim()
100
+
101
+ const result = applyTransform(input)
102
+ expect(result).toBe(null)
103
+ })
104
+
105
+ it("does not affect other components with the same prop", () => {
106
+ const input = `
107
+ import { Input } from "@planningcenter/tapestry-react"
108
+
109
+ export default function Test() {
110
+ return <Input placeholder="Search" />
111
+ }
112
+ `.trim()
113
+
114
+ const result = applyTransform(input)
115
+ expect(result).toBe(null)
116
+ })
117
+ })
@@ -0,0 +1,22 @@
1
+ import { Transform } from "jscodeshift"
2
+
3
+ import { removeAttribute } from "../../shared/actions/removeAttribute"
4
+ import { hasAttribute } from "../../shared/conditions/hasAttribute"
5
+ import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
6
+
7
+ function buildComment(propName: string, formattedValue: string) {
8
+ const valuePart = formattedValue
9
+ ? ` - removed: ${propName}=${formattedValue}`
10
+ : ""
11
+ return `prop is not supported. DateField renders locale-aware date segments instead of a placeholder.${valuePart}`
12
+ }
13
+
14
+ const transform: Transform = attributeTransformFactory({
15
+ condition: hasAttribute("placeholder"),
16
+ targetComponent: "DateField",
17
+ targetPackage: "@planningcenter/tapestry-react",
18
+ transform: (element, { j, source }) =>
19
+ removeAttribute("placeholder", { buildComment, element, j, source }),
20
+ })
21
+
22
+ export default transform