@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,240 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./mergeFieldIntoDateField"
|
|
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("mergeFieldIntoDateField transform", () => {
|
|
18
|
+
describe("basic prop merging", () => {
|
|
19
|
+
it("merges label prop from Field into DateField", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
function Test() {
|
|
24
|
+
return (
|
|
25
|
+
<Field label="Start date"><DateField value={date} onChange={setDate} /></Field>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
`.trim()
|
|
29
|
+
|
|
30
|
+
const result = applyTransform(input)
|
|
31
|
+
expect(result).not.toBeNull()
|
|
32
|
+
expect(result).not.toContain("<Field")
|
|
33
|
+
expect(result).toContain('label="Start date"')
|
|
34
|
+
expect(result).toContain("<DateField")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("renames feedbackText to description on DateField", () => {
|
|
38
|
+
const input = `
|
|
39
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
40
|
+
|
|
41
|
+
function Test() {
|
|
42
|
+
return (
|
|
43
|
+
<Field feedbackText="Required"><DateField value={date} onChange={setDate} /></Field>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
`.trim()
|
|
47
|
+
|
|
48
|
+
const result = applyTransform(input)
|
|
49
|
+
expect(result).not.toBeNull()
|
|
50
|
+
expect(result).not.toContain("<Field")
|
|
51
|
+
expect(result).not.toContain("feedbackText")
|
|
52
|
+
expect(result).toContain('description="Required"')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it("copies state prop as-is to DateField", () => {
|
|
56
|
+
const input = `
|
|
57
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
58
|
+
|
|
59
|
+
function Test() {
|
|
60
|
+
return (
|
|
61
|
+
<Field state="error"><DateField value={date} onChange={setDate} /></Field>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
`.trim()
|
|
65
|
+
|
|
66
|
+
const result = applyTransform(input)
|
|
67
|
+
expect(result).not.toBeNull()
|
|
68
|
+
expect(result).not.toContain("<Field")
|
|
69
|
+
expect(result).toContain('state="error"')
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe("unsupported props", () => {
|
|
74
|
+
it("adds TODO comment for helpContent and removes Field", () => {
|
|
75
|
+
const input = `
|
|
76
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
77
|
+
|
|
78
|
+
function Test() {
|
|
79
|
+
return (
|
|
80
|
+
<Field helpContent="Help"><DateField value={date} onChange={setDate} /></Field>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
`.trim()
|
|
84
|
+
|
|
85
|
+
const result = applyTransform(input)
|
|
86
|
+
expect(result).not.toBeNull()
|
|
87
|
+
expect(result).not.toContain("<Field")
|
|
88
|
+
expect(result).toContain(
|
|
89
|
+
"TODO: tapestry-migration (mergeFieldIntoDateField)"
|
|
90
|
+
)
|
|
91
|
+
expect(result).toContain("helpContent")
|
|
92
|
+
expect(result).toContain("not supported by DateField")
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe("conflict detection", () => {
|
|
97
|
+
it("adds warning when DateField already has label", () => {
|
|
98
|
+
const input = `
|
|
99
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
100
|
+
|
|
101
|
+
function Test() {
|
|
102
|
+
return (
|
|
103
|
+
<Field label="Name"><DateField label="Other" value={date} onChange={setDate} /></Field>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
`.trim()
|
|
107
|
+
|
|
108
|
+
const result = applyTransform(input)
|
|
109
|
+
expect(result).not.toBeNull()
|
|
110
|
+
expect(result).not.toContain("<Field")
|
|
111
|
+
expect(result).toContain('label="Other"')
|
|
112
|
+
expect(result).toContain(
|
|
113
|
+
"TODO: tapestry-migration (mergeFieldIntoDateField)"
|
|
114
|
+
)
|
|
115
|
+
expect(result).toContain("already has label")
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe("multiple children", () => {
|
|
120
|
+
it("adds comment when Field has multiple children", () => {
|
|
121
|
+
const input = `
|
|
122
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
123
|
+
|
|
124
|
+
function Test() {
|
|
125
|
+
return (
|
|
126
|
+
<Field><DateField value={date} onChange={setDate} /><button /></Field>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
`.trim()
|
|
130
|
+
|
|
131
|
+
const result = applyTransform(input)
|
|
132
|
+
expect(result).not.toBeNull()
|
|
133
|
+
expect(result).toContain("<Field")
|
|
134
|
+
expect(result).toContain(
|
|
135
|
+
"TODO: tapestry-migration (mergeFieldIntoDateField)"
|
|
136
|
+
)
|
|
137
|
+
expect(result).toContain("multiple children")
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe("spread props on Field", () => {
|
|
142
|
+
it("bails out with TODO comment when Field has spread props", () => {
|
|
143
|
+
const input = `
|
|
144
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
145
|
+
|
|
146
|
+
function Test() {
|
|
147
|
+
return (
|
|
148
|
+
<Field {...fieldProps}><DateField value={date} onChange={setDate} /></Field>
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
`.trim()
|
|
152
|
+
|
|
153
|
+
const result = applyTransform(input)
|
|
154
|
+
expect(result).not.toBeNull()
|
|
155
|
+
expect(result).toContain("<Field")
|
|
156
|
+
expect(result).toContain(
|
|
157
|
+
"TODO: tapestry-migration (mergeFieldIntoDateField)"
|
|
158
|
+
)
|
|
159
|
+
expect(result).toContain("spread props")
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe("import cleanup", () => {
|
|
164
|
+
it("removes Field from import when all Fields are converted", () => {
|
|
165
|
+
const input = `
|
|
166
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
167
|
+
|
|
168
|
+
function Test() {
|
|
169
|
+
return (
|
|
170
|
+
<Field label="Start date"><DateField value={date} onChange={setDate} /></Field>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
`.trim()
|
|
174
|
+
|
|
175
|
+
const result = applyTransform(input)
|
|
176
|
+
expect(result).not.toBeNull()
|
|
177
|
+
expect(result).not.toContain("{ DateField, Field }")
|
|
178
|
+
expect(result).toContain("DateField")
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
describe("not from tapestry-react", () => {
|
|
183
|
+
it("returns null when Field is not imported from tapestry-react", () => {
|
|
184
|
+
const input = `
|
|
185
|
+
import { Field } from "some-other-library"
|
|
186
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
187
|
+
|
|
188
|
+
function Test() {
|
|
189
|
+
return (
|
|
190
|
+
<Field label="Name"><DateField value={date} onChange={setDate} /></Field>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
`.trim()
|
|
194
|
+
|
|
195
|
+
const result = applyTransform(input)
|
|
196
|
+
expect(result).toBeNull()
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it("returns null when DateField is not imported from tapestry-react", () => {
|
|
200
|
+
const input = `
|
|
201
|
+
import { Field } from "@planningcenter/tapestry-react"
|
|
202
|
+
import { DateField } from "some-other-library"
|
|
203
|
+
|
|
204
|
+
function Test() {
|
|
205
|
+
return (
|
|
206
|
+
<Field label="Name"><DateField value={date} onChange={setDate} /></Field>
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
`.trim()
|
|
210
|
+
|
|
211
|
+
const result = applyTransform(input)
|
|
212
|
+
expect(result).toBeNull()
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe("full prop set", () => {
|
|
217
|
+
it("merges label, feedbackText, state and flags helpContent as unsupported", () => {
|
|
218
|
+
const input = `
|
|
219
|
+
import { DateField, Field } from "@planningcenter/tapestry-react"
|
|
220
|
+
|
|
221
|
+
function Test() {
|
|
222
|
+
return (
|
|
223
|
+
<Field label="Start date" feedbackText="Required" state="error" helpContent="Help">
|
|
224
|
+
<DateField value={date} onChange={setDate} />
|
|
225
|
+
</Field>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
`.trim()
|
|
229
|
+
|
|
230
|
+
const result = applyTransform(input)
|
|
231
|
+
expect(result).not.toBeNull()
|
|
232
|
+
expect(result).not.toContain("<Field")
|
|
233
|
+
expect(result).toContain('label="Start date"')
|
|
234
|
+
expect(result).toContain('description="Required"')
|
|
235
|
+
expect(result).toContain('state="error"')
|
|
236
|
+
expect(result).toContain("helpContent")
|
|
237
|
+
expect(result).toContain("not supported by DateField")
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./minDateToMin"
|
|
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("minDateToMin transform", () => {
|
|
19
|
+
it("should rename minDate to min", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
function Test() {
|
|
24
|
+
return <DateField minDate={startDate} value={date} onChange={setDate} />
|
|
25
|
+
}
|
|
26
|
+
`.trim()
|
|
27
|
+
|
|
28
|
+
const result = applyTransform(input)
|
|
29
|
+
expect(result).toContain("min={startDate}")
|
|
30
|
+
expect(result).not.toContain("minDate")
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("should rename minDate with inline Date value", () => {
|
|
34
|
+
const input = `
|
|
35
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
36
|
+
|
|
37
|
+
function Test() {
|
|
38
|
+
return <DateField minDate={new Date("2024-01-01")} value={date} onChange={setDate} />
|
|
39
|
+
}
|
|
40
|
+
`.trim()
|
|
41
|
+
|
|
42
|
+
const result = applyTransform(input)
|
|
43
|
+
expect(result).toContain("min={")
|
|
44
|
+
expect(result).not.toContain("minDate=")
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("should not transform DateField without minDate prop", () => {
|
|
48
|
+
const input = `
|
|
49
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
50
|
+
|
|
51
|
+
function Test() {
|
|
52
|
+
return <DateField value={date} onChange={setDate} />
|
|
53
|
+
}
|
|
54
|
+
`.trim()
|
|
55
|
+
|
|
56
|
+
const result = applyTransform(input)
|
|
57
|
+
expect(result).toBe(input)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("should not affect other components", () => {
|
|
61
|
+
const input = `
|
|
62
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
63
|
+
|
|
64
|
+
function Test() {
|
|
65
|
+
return <Input minDate={startDate} label="Name" />
|
|
66
|
+
}
|
|
67
|
+
`.trim()
|
|
68
|
+
|
|
69
|
+
const result = applyTransform(input)
|
|
70
|
+
expect(result).toBe(input)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it("should preserve other props", () => {
|
|
74
|
+
const input = `
|
|
75
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
76
|
+
|
|
77
|
+
function Test() {
|
|
78
|
+
return <DateField minDate={startDate} maxDate={endDate} value={date} onChange={setDate} />
|
|
79
|
+
}
|
|
80
|
+
`.trim()
|
|
81
|
+
|
|
82
|
+
const result = applyTransform(input)
|
|
83
|
+
expect(result).toContain("min={startDate}")
|
|
84
|
+
expect(result).toContain("maxDate={endDate}")
|
|
85
|
+
expect(result).toContain("value={date}")
|
|
86
|
+
})
|
|
87
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { transformAttributeName } from "../../shared/actions/transformAttributeName"
|
|
4
|
+
import { hasAttribute } from "../../shared/conditions/hasAttribute"
|
|
5
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
6
|
+
|
|
7
|
+
const transform: Transform = attributeTransformFactory({
|
|
8
|
+
condition: hasAttribute("minDate"),
|
|
9
|
+
targetComponent: "DateField",
|
|
10
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
11
|
+
transform: (element, { j, options }) => {
|
|
12
|
+
return transformAttributeName("minDate", "min", { element, j, options })
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export default transform
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./momentToDateString"
|
|
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("momentToDateString transform", () => {
|
|
18
|
+
describe("minDate with moment().toDate()", () => {
|
|
19
|
+
it("converts moment(starts).add(1, 'day').toDate() to .format()", () => {
|
|
20
|
+
const input = `
|
|
21
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
22
|
+
|
|
23
|
+
function Test() {
|
|
24
|
+
return <DateField minDate={moment(starts).add(1, 'day').toDate()} value={date} onChange={setDate} />
|
|
25
|
+
}
|
|
26
|
+
`.trim()
|
|
27
|
+
|
|
28
|
+
const result = applyTransform(input)
|
|
29
|
+
expect(result).not.toBeNull()
|
|
30
|
+
expect(result).toContain('.format("YYYY-MM-DD")')
|
|
31
|
+
expect(result).not.toContain(".toDate()}")
|
|
32
|
+
expect(result).toContain("moment(starts).add(1, 'day')")
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it("converts moment().add(-10, 'years').toDate() to .format()", () => {
|
|
36
|
+
const input = `
|
|
37
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
38
|
+
|
|
39
|
+
function Test() {
|
|
40
|
+
return <DateField minDate={moment().add(-10, 'years').toDate()} value={date} onChange={setDate} />
|
|
41
|
+
}
|
|
42
|
+
`.trim()
|
|
43
|
+
|
|
44
|
+
const result = applyTransform(input)
|
|
45
|
+
expect(result).not.toBeNull()
|
|
46
|
+
expect(result).toContain('.format("YYYY-MM-DD")')
|
|
47
|
+
expect(result).not.toContain(".toDate()}")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("converts simple moment().toDate() to .format()", () => {
|
|
51
|
+
const input = `
|
|
52
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
53
|
+
|
|
54
|
+
function Test() {
|
|
55
|
+
return <DateField minDate={moment().toDate()} value={date} onChange={setDate} />
|
|
56
|
+
}
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
const result = applyTransform(input)
|
|
60
|
+
expect(result).not.toBeNull()
|
|
61
|
+
expect(result).toContain('moment().format("YYYY-MM-DD")')
|
|
62
|
+
expect(result).not.toContain(".toDate()}")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("adds a change comment noting the conversion", () => {
|
|
66
|
+
const input = `
|
|
67
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
68
|
+
|
|
69
|
+
function Test() {
|
|
70
|
+
return <DateField minDate={moment().toDate()} value={date} onChange={setDate} />
|
|
71
|
+
}
|
|
72
|
+
`.trim()
|
|
73
|
+
|
|
74
|
+
const result = applyTransform(input)
|
|
75
|
+
expect(result).not.toBeNull()
|
|
76
|
+
expect(result).toContain("Converted .toDate()")
|
|
77
|
+
expect(result).toContain("ISO date strings")
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
describe("maxDate with moment().toDate()", () => {
|
|
82
|
+
it("converts maxDate moment chain to .format()", () => {
|
|
83
|
+
const input = `
|
|
84
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
85
|
+
|
|
86
|
+
function Test() {
|
|
87
|
+
return <DateField maxDate={moment(ends).subtract(1, 'day').toDate()} value={date} onChange={setDate} />
|
|
88
|
+
}
|
|
89
|
+
`.trim()
|
|
90
|
+
|
|
91
|
+
const result = applyTransform(input)
|
|
92
|
+
expect(result).not.toBeNull()
|
|
93
|
+
expect(result).toContain('.format("YYYY-MM-DD")')
|
|
94
|
+
expect(result).not.toContain(".toDate()}")
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe("value with moment().toDate()", () => {
|
|
99
|
+
it("converts value moment chain to .format()", () => {
|
|
100
|
+
const input = `
|
|
101
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
102
|
+
|
|
103
|
+
function Test() {
|
|
104
|
+
return <DateField value={moment(selectedDate).toDate()} onChange={setDate} />
|
|
105
|
+
}
|
|
106
|
+
`.trim()
|
|
107
|
+
|
|
108
|
+
const result = applyTransform(input)
|
|
109
|
+
expect(result).not.toBeNull()
|
|
110
|
+
expect(result).toContain('.format("YYYY-MM-DD")')
|
|
111
|
+
expect(result).not.toContain(".toDate()}")
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe("multiple props with moment", () => {
|
|
116
|
+
it("converts both minDate and maxDate moment chains", () => {
|
|
117
|
+
const input = `
|
|
118
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
119
|
+
|
|
120
|
+
function Test() {
|
|
121
|
+
return (
|
|
122
|
+
<DateField
|
|
123
|
+
minDate={moment().add(-10, 'years').toDate()}
|
|
124
|
+
maxDate={moment().toDate()}
|
|
125
|
+
value={date}
|
|
126
|
+
onChange={setDate}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
`.trim()
|
|
131
|
+
|
|
132
|
+
const result = applyTransform(input)
|
|
133
|
+
expect(result).not.toBeNull()
|
|
134
|
+
expect(result).not.toContain(".toDate()}")
|
|
135
|
+
const formatMatches = result!.match(/\)\.format\("YYYY-MM-DD"\)\}/g)
|
|
136
|
+
expect(formatMatches).toHaveLength(2)
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
describe("moment without .toDate()", () => {
|
|
141
|
+
it("adds TODO comment when moment is used without .toDate()", () => {
|
|
142
|
+
const input = `
|
|
143
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
144
|
+
|
|
145
|
+
function Test() {
|
|
146
|
+
return <DateField minDate={moment(starts)} value={date} onChange={setDate} />
|
|
147
|
+
}
|
|
148
|
+
`.trim()
|
|
149
|
+
|
|
150
|
+
const result = applyTransform(input)
|
|
151
|
+
expect(result).not.toBeNull()
|
|
152
|
+
expect(result).toContain("TODO: tapestry-migration")
|
|
153
|
+
expect(result).toContain("manual conversion")
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it("adds TODO for moment variable reference in value", () => {
|
|
157
|
+
const input = `
|
|
158
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
159
|
+
|
|
160
|
+
function Test() {
|
|
161
|
+
const d = moment()
|
|
162
|
+
return <DateField value={moment(d)} onChange={setDate} />
|
|
163
|
+
}
|
|
164
|
+
`.trim()
|
|
165
|
+
|
|
166
|
+
const result = applyTransform(input)
|
|
167
|
+
expect(result).not.toBeNull()
|
|
168
|
+
expect(result).toContain("TODO: tapestry-migration")
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
describe("non-date values", () => {
|
|
173
|
+
it("does not transform string values", () => {
|
|
174
|
+
const input = `
|
|
175
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
176
|
+
|
|
177
|
+
function Test() {
|
|
178
|
+
return <DateField value="2024-01-01" onChange={setDate} />
|
|
179
|
+
}
|
|
180
|
+
`.trim()
|
|
181
|
+
|
|
182
|
+
const result = applyTransform(input)
|
|
183
|
+
expect(result).toBeNull()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it("does not transform variable references", () => {
|
|
187
|
+
const input = `
|
|
188
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
189
|
+
|
|
190
|
+
function Test() {
|
|
191
|
+
return <DateField minDate={startDate} value={date} onChange={setDate} />
|
|
192
|
+
}
|
|
193
|
+
`.trim()
|
|
194
|
+
|
|
195
|
+
const result = applyTransform(input)
|
|
196
|
+
expect(result).toBeNull()
|
|
197
|
+
})
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
describe("edge cases", () => {
|
|
201
|
+
it("does not affect other components", () => {
|
|
202
|
+
const input = `
|
|
203
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
204
|
+
|
|
205
|
+
function Test() {
|
|
206
|
+
return <Input value={moment().toDate()} label="Name" />
|
|
207
|
+
}
|
|
208
|
+
`.trim()
|
|
209
|
+
|
|
210
|
+
const result = applyTransform(input)
|
|
211
|
+
expect(result).toBeNull()
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
it("does not transform non-date props that use moment", () => {
|
|
215
|
+
const input = `
|
|
216
|
+
import { DateField } from "@planningcenter/tapestry-react"
|
|
217
|
+
|
|
218
|
+
function Test() {
|
|
219
|
+
return <DateField onChange={moment().toDate()} value={date} />
|
|
220
|
+
}
|
|
221
|
+
`.trim()
|
|
222
|
+
|
|
223
|
+
const result = applyTransform(input)
|
|
224
|
+
expect(result).toBeNull()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it("does not transform DateField from other packages", () => {
|
|
228
|
+
const input = `
|
|
229
|
+
import { DateField } from "other-library"
|
|
230
|
+
|
|
231
|
+
function Test() {
|
|
232
|
+
return <DateField minDate={moment().toDate()} value={date} onChange={setDate} />
|
|
233
|
+
}
|
|
234
|
+
`.trim()
|
|
235
|
+
|
|
236
|
+
const result = applyTransform(input)
|
|
237
|
+
expect(result).toBeNull()
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CallExpression,
|
|
3
|
+
Expression,
|
|
4
|
+
MemberExpression,
|
|
5
|
+
Transform,
|
|
6
|
+
} from "jscodeshift"
|
|
7
|
+
|
|
8
|
+
import { addCommentToAttribute } from "../../shared/actions/addCommentToAttribute"
|
|
9
|
+
import { getAttribute } from "../../shared/actions/getAttribute"
|
|
10
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
11
|
+
|
|
12
|
+
const DATE_PROPS = ["minDate", "maxDate", "value"]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Checks if an expression is a call to `.toDate()` at the end of a chain
|
|
16
|
+
*/
|
|
17
|
+
function isToDateCall(expr: Expression): expr is CallExpression {
|
|
18
|
+
return (
|
|
19
|
+
expr.type === "CallExpression" &&
|
|
20
|
+
expr.callee.type === "MemberExpression" &&
|
|
21
|
+
expr.callee.property.type === "Identifier" &&
|
|
22
|
+
expr.callee.property.name === "toDate" &&
|
|
23
|
+
expr.arguments.length === 0
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Checks whether an expression references `moment` anywhere in its call chain
|
|
29
|
+
*/
|
|
30
|
+
function usesMoment(expr: Expression): boolean {
|
|
31
|
+
if (expr.type === "Identifier" && expr.name === "moment") return true
|
|
32
|
+
if (expr.type === "CallExpression") {
|
|
33
|
+
if (usesMoment(expr.callee as Expression)) return true
|
|
34
|
+
return expr.arguments.some((arg) =>
|
|
35
|
+
arg.type !== "SpreadElement" ? usesMoment(arg as Expression) : false
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
if (expr.type === "MemberExpression") {
|
|
39
|
+
return usesMoment(expr.object as Expression)
|
|
40
|
+
}
|
|
41
|
+
return false
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const transform: Transform = attributeTransformFactory({
|
|
45
|
+
targetComponent: "DateField",
|
|
46
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
47
|
+
transform: (element, { j }) => {
|
|
48
|
+
let hasChanges = false
|
|
49
|
+
|
|
50
|
+
for (const propName of DATE_PROPS) {
|
|
51
|
+
const attr = getAttribute({ element, name: propName })
|
|
52
|
+
if (!attr?.value) continue
|
|
53
|
+
if (attr.value.type !== "JSXExpressionContainer") continue
|
|
54
|
+
|
|
55
|
+
const expr = attr.value.expression
|
|
56
|
+
if (expr.type === "JSXEmptyExpression") continue
|
|
57
|
+
|
|
58
|
+
if (!usesMoment(expr as Expression)) continue
|
|
59
|
+
|
|
60
|
+
if (isToDateCall(expr as Expression)) {
|
|
61
|
+
const callExpr = expr as CallExpression
|
|
62
|
+
const memberExpr = callExpr.callee as MemberExpression
|
|
63
|
+
memberExpr.property = j.identifier("format")
|
|
64
|
+
callExpr.arguments = [j.stringLiteral("YYYY-MM-DD")]
|
|
65
|
+
|
|
66
|
+
addCommentToAttribute({
|
|
67
|
+
attribute: attr,
|
|
68
|
+
commentKind: "change",
|
|
69
|
+
j,
|
|
70
|
+
text: `Converted .toDate() to .format("YYYY-MM-DD") — DatePicker accepts ISO date strings instead of Date objects. Verify the format is correct.`,
|
|
71
|
+
})
|
|
72
|
+
hasChanges = true
|
|
73
|
+
} else {
|
|
74
|
+
addCommentToAttribute({
|
|
75
|
+
attribute: attr,
|
|
76
|
+
j,
|
|
77
|
+
text: `This prop uses moment and may need manual conversion. DatePicker accepts DateValue or ISO date strings (YYYY-MM-DD) instead of Date objects.`,
|
|
78
|
+
})
|
|
79
|
+
hasChanges = true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return hasChanges
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
export default transform
|