@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.
- package/package.json +3 -3
- package/src/components/button/transforms/unsupportedProps.ts +3 -31
- package/src/components/checkbox/transforms/unsupportedProps.ts +3 -28
- package/src/components/date-picker/index.ts +56 -0
- package/src/components/date-picker/transforms/auditSpreadProps.test.ts +97 -0
- package/src/components/date-picker/transforms/auditSpreadProps.ts +10 -0
- package/src/components/date-picker/transforms/convertStyleProps.test.ts +58 -0
- package/src/components/date-picker/transforms/convertStyleProps.ts +10 -0
- package/src/components/date-picker/transforms/maxDateToMax.test.ts +87 -0
- package/src/components/date-picker/transforms/maxDateToMax.ts +16 -0
- package/src/components/date-picker/transforms/mergeFieldIntoDateField.test.ts +240 -0
- package/src/components/date-picker/transforms/mergeFieldIntoDateField.ts +5 -0
- package/src/components/date-picker/transforms/minDateToMin.test.ts +87 -0
- package/src/components/date-picker/transforms/minDateToMin.ts +16 -0
- package/src/components/date-picker/transforms/momentToDateString.test.ts +240 -0
- package/src/components/date-picker/transforms/momentToDateString.ts +87 -0
- package/src/components/date-picker/transforms/moveDatePickerImport.test.ts +157 -0
- package/src/components/date-picker/transforms/moveDatePickerImport.ts +14 -0
- package/src/components/date-picker/transforms/nativeDateToString.test.ts +220 -0
- package/src/components/date-picker/transforms/nativeDateToString.ts +59 -0
- package/src/components/date-picker/transforms/removeDuplicateKeys.test.ts +120 -0
- package/src/components/date-picker/transforms/removeDuplicateKeys.ts +8 -0
- package/src/components/date-picker/transforms/removeFormatValue.test.ts +119 -0
- package/src/components/date-picker/transforms/removeFormatValue.ts +22 -0
- package/src/components/date-picker/transforms/removePlaceholder.test.ts +117 -0
- package/src/components/date-picker/transforms/removePlaceholder.ts +22 -0
- package/src/components/date-picker/transforms/sizeMapping.test.ts +173 -0
- package/src/components/date-picker/transforms/sizeMapping.ts +15 -0
- package/src/components/date-picker/transforms/stateToInvalid.test.ts +109 -0
- package/src/components/date-picker/transforms/stateToInvalid.ts +57 -0
- package/src/components/date-picker/transforms/stateToInvalidTernary.test.ts +72 -0
- package/src/components/date-picker/transforms/stateToInvalidTernary.ts +11 -0
- package/src/components/date-picker/transforms/unsupportedProps.test.ts +170 -0
- package/src/components/date-picker/transforms/unsupportedProps.ts +10 -0
- package/src/components/input/transforms/unsupportedProps.ts +2 -2
- package/src/components/link/transforms/unsupportedProps.test.ts +3 -1
- package/src/components/link/transforms/unsupportedProps.ts +9 -37
- package/src/components/radio/transforms/unsupportedProps.ts +3 -28
- package/src/components/select/transforms/unsupportedProps.ts +9 -35
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +22 -0
- package/src/components/shared/transformFactories/unsupportedPropsFactory.test.ts +162 -0
- package/src/components/shared/transformFactories/unsupportedPropsFactory.ts +60 -0
- package/src/components/text-area/transforms/unsupportedProps.ts +3 -28
- package/src/components/time-field/transforms/unsupportedProps.ts +3 -30
- package/src/components/toggle-switch/transforms/unsupportedProps.ts +3 -28
- 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
|
+
})
|