@planningcenter/tapestry-migration-cli 3.2.2-rc.9 → 3.2.2
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/shared/helpers/unsupportedPropsHelpers.ts +24 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.test.ts +330 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +156 -15
- package/src/components/shared/transformFactories/ternaryConditionalToPropFactory.ts +17 -12
- package/src/components/time-field/index.ts +48 -0
- package/src/components/time-field/transforms/auditSpreadProps.test.ts +76 -0
- package/src/components/time-field/transforms/auditSpreadProps.ts +10 -0
- package/src/components/time-field/transforms/convertStyleProps.test.ts +43 -0
- package/src/components/time-field/transforms/convertStyleProps.ts +10 -0
- package/src/components/time-field/transforms/flagMinMax.test.ts +103 -0
- package/src/components/time-field/transforms/flagMinMax.ts +31 -0
- package/src/components/time-field/transforms/mergeFieldIntoTimeField.test.ts +106 -0
- package/src/components/time-field/transforms/mergeFieldIntoTimeField.ts +5 -0
- package/src/components/time-field/transforms/moveTimeFieldImport.test.ts +153 -0
- package/src/components/time-field/transforms/moveTimeFieldImport.ts +14 -0
- package/src/components/time-field/transforms/sizeMapping.test.ts +173 -0
- package/src/components/time-field/transforms/sizeMapping.ts +15 -0
- package/src/components/time-field/transforms/stateToInvalid.test.ts +87 -0
- package/src/components/time-field/transforms/stateToInvalid.ts +56 -0
- package/src/components/time-field/transforms/stateToInvalidTernary.test.ts +100 -0
- package/src/components/time-field/transforms/stateToInvalidTernary.ts +11 -0
- package/src/components/time-field/transforms/tupleToTime.test.ts +182 -0
- package/src/components/time-field/transforms/tupleToTime.ts +107 -0
- package/src/components/time-field/transforms/twelveHourClockToHourCycle.test.ts +117 -0
- package/src/components/time-field/transforms/twelveHourClockToHourCycle.ts +65 -0
- package/src/components/time-field/transforms/unsupportedProps.test.ts +160 -0
- package/src/components/time-field/transforms/unsupportedProps.ts +37 -0
- package/src/index.ts +2 -1
|
@@ -35,27 +35,32 @@ export function ternaryConditionalToPropFactory({
|
|
|
35
35
|
const expr = attr.value.expression
|
|
36
36
|
if (expr.type !== "ConditionalExpression") return false
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
) {
|
|
42
|
-
return false
|
|
43
|
-
}
|
|
38
|
+
const isNullish = (node: Expression) =>
|
|
39
|
+
node.type === "NullLiteral" ||
|
|
40
|
+
(node.type === "Identifier" && node.name === "undefined")
|
|
44
41
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
expr.alternate.type === "Identifier" &&
|
|
48
|
-
expr.alternate.name === "undefined"
|
|
42
|
+
const isMatchString = (node: Expression) =>
|
|
43
|
+
node.type === "StringLiteral" && node.value === matchValue
|
|
49
44
|
|
|
50
|
-
|
|
45
|
+
const matchInConsequent =
|
|
46
|
+
isMatchString(expr.consequent as Expression) &&
|
|
47
|
+
isNullish(expr.alternate as Expression)
|
|
48
|
+
const matchInAlternate =
|
|
49
|
+
isMatchString(expr.alternate as Expression) &&
|
|
50
|
+
isNullish(expr.consequent as Expression)
|
|
51
|
+
|
|
52
|
+
if (!matchInConsequent && !matchInAlternate) return false
|
|
51
53
|
|
|
52
54
|
const testExpr = expr.test as Expression
|
|
55
|
+
const propValue = matchInConsequent
|
|
56
|
+
? testExpr
|
|
57
|
+
: j.unaryExpression("!", testExpr)
|
|
53
58
|
|
|
54
59
|
removeAttribute(fromProp, { element, j, source })
|
|
55
60
|
element.openingElement.attributes.push(
|
|
56
61
|
j.jsxAttribute(
|
|
57
62
|
j.jsxIdentifier(toProp),
|
|
58
|
-
j.jsxExpressionContainer(
|
|
63
|
+
j.jsxExpressionContainer(propValue)
|
|
59
64
|
)
|
|
60
65
|
)
|
|
61
66
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import auditSpreadProps from "./transforms/auditSpreadProps"
|
|
4
|
+
import convertStyleProps from "./transforms/convertStyleProps"
|
|
5
|
+
import flagMinMax from "./transforms/flagMinMax"
|
|
6
|
+
import mergeFieldIntoTimeField from "./transforms/mergeFieldIntoTimeField"
|
|
7
|
+
import moveTimeFieldImport from "./transforms/moveTimeFieldImport"
|
|
8
|
+
import sizeMapping from "./transforms/sizeMapping"
|
|
9
|
+
import stateToInvalid from "./transforms/stateToInvalid"
|
|
10
|
+
import stateToInvalidTernary from "./transforms/stateToInvalidTernary"
|
|
11
|
+
import tupleToTime from "./transforms/tupleToTime"
|
|
12
|
+
import twelveHourClockToHourCycle from "./transforms/twelveHourClockToHourCycle"
|
|
13
|
+
import unsupportedProps from "./transforms/unsupportedProps"
|
|
14
|
+
|
|
15
|
+
const transform: Transform = (fileInfo, api, options) => {
|
|
16
|
+
let currentSource = fileInfo.source
|
|
17
|
+
let hasAnyChanges = false
|
|
18
|
+
|
|
19
|
+
const transforms = [
|
|
20
|
+
mergeFieldIntoTimeField,
|
|
21
|
+
auditSpreadProps,
|
|
22
|
+
sizeMapping,
|
|
23
|
+
stateToInvalidTernary,
|
|
24
|
+
stateToInvalid,
|
|
25
|
+
twelveHourClockToHourCycle,
|
|
26
|
+
tupleToTime,
|
|
27
|
+
flagMinMax,
|
|
28
|
+
convertStyleProps,
|
|
29
|
+
unsupportedProps,
|
|
30
|
+
moveTimeFieldImport,
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for (const individualTransform of transforms) {
|
|
34
|
+
const result = individualTransform(
|
|
35
|
+
{ ...fileInfo, source: currentSource },
|
|
36
|
+
api,
|
|
37
|
+
options
|
|
38
|
+
)
|
|
39
|
+
if (result && result !== currentSource) {
|
|
40
|
+
currentSource = result as string
|
|
41
|
+
hasAnyChanges = true
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return hasAnyChanges ? currentSource : null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default transform
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./auditSpreadProps"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
const AUDIT_COMMENT =
|
|
9
|
+
"TODO: tapestry-migration (spreadAttribute): Spread props can contain unsupported props, please explore usages and migrate as needed."
|
|
10
|
+
|
|
11
|
+
function applyTransform(source: string) {
|
|
12
|
+
const fileInfo = { path: "test.tsx", source }
|
|
13
|
+
return transform(
|
|
14
|
+
fileInfo,
|
|
15
|
+
{ j, jscodeshift: j, report: () => {}, stats: () => {} },
|
|
16
|
+
{}
|
|
17
|
+
) as string | null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe("auditSpreadProps transform", () => {
|
|
21
|
+
it("adds comment to TimeField with spread props", () => {
|
|
22
|
+
const input = `
|
|
23
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
24
|
+
|
|
25
|
+
export default function Test() {
|
|
26
|
+
const props = { onChange: handleChange }
|
|
27
|
+
return <TimeField {...props} value={time} onChange={setTime} />
|
|
28
|
+
}
|
|
29
|
+
`.trim()
|
|
30
|
+
|
|
31
|
+
const result = applyTransform(input)
|
|
32
|
+
expect(result).toContain(AUDIT_COMMENT)
|
|
33
|
+
expect(result).toContain("{...props}")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("returns null when TimeField has no spread props", () => {
|
|
37
|
+
const input = `
|
|
38
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
39
|
+
|
|
40
|
+
export default function Test() {
|
|
41
|
+
return <TimeField value={time} onChange={setTime} />
|
|
42
|
+
}
|
|
43
|
+
`.trim()
|
|
44
|
+
|
|
45
|
+
const result = applyTransform(input)
|
|
46
|
+
expect(result).toBe(null)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it("returns null when TimeField is from another package", () => {
|
|
50
|
+
const input = `
|
|
51
|
+
import { TimeField } from "other-library"
|
|
52
|
+
|
|
53
|
+
export default function Test() {
|
|
54
|
+
const props = { onChange: handleChange }
|
|
55
|
+
return <TimeField {...props} value={time} />
|
|
56
|
+
}
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
const result = applyTransform(input)
|
|
60
|
+
expect(result).toBe(null)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("returns null when TimeField is not imported at all", () => {
|
|
64
|
+
const input = `
|
|
65
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
66
|
+
|
|
67
|
+
export default function Test() {
|
|
68
|
+
const props = { onClick: handleClick }
|
|
69
|
+
return <Button {...props}>Save</Button>
|
|
70
|
+
}
|
|
71
|
+
`.trim()
|
|
72
|
+
|
|
73
|
+
const result = applyTransform(input)
|
|
74
|
+
expect(result).toBe(null)
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { commentOnSpreadPropsFactory } from "../../shared/transformFactories/commentOnSpreadPropsFactory"
|
|
4
|
+
|
|
5
|
+
const transform: Transform = commentOnSpreadPropsFactory({
|
|
6
|
+
targetComponent: "TimeField",
|
|
7
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export default transform
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./convertStyleProps"
|
|
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("convertStyleProps transform", () => {
|
|
18
|
+
it("returns null when TimeField has no style props", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
function Test() {
|
|
23
|
+
return <TimeField value={time} onChange={setTime} />
|
|
24
|
+
}
|
|
25
|
+
`.trim()
|
|
26
|
+
|
|
27
|
+
const result = applyTransform(input)
|
|
28
|
+
expect(result).toBeNull()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("does not affect TimeField from other packages", () => {
|
|
32
|
+
const input = `
|
|
33
|
+
import { TimeField } from "some-other-lib"
|
|
34
|
+
|
|
35
|
+
function Test() {
|
|
36
|
+
return <TimeField p={2} value={time} onChange={setTime} />
|
|
37
|
+
}
|
|
38
|
+
`.trim()
|
|
39
|
+
|
|
40
|
+
const result = applyTransform(input)
|
|
41
|
+
expect(result).toBeNull()
|
|
42
|
+
})
|
|
43
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { stackViewPlugin } from "../../../stubs/stackViewPlugin"
|
|
2
|
+
import { stylePropTransformFactory } from "../../shared/transformFactories/stylePropTransformFactory"
|
|
3
|
+
|
|
4
|
+
export default stylePropTransformFactory({
|
|
5
|
+
plugin: stackViewPlugin,
|
|
6
|
+
stylesToKeep: [],
|
|
7
|
+
stylesToRemove: [],
|
|
8
|
+
targetComponent: "TimeField",
|
|
9
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
10
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./flagMinMax"
|
|
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("flagMinMax transform", () => {
|
|
18
|
+
it("adds TODO comment to min prop", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
function Test() {
|
|
23
|
+
return <TimeField min={9} value={time} onChange={setTime} />
|
|
24
|
+
}
|
|
25
|
+
`.trim()
|
|
26
|
+
|
|
27
|
+
const result = applyTransform(input)
|
|
28
|
+
expect(result).not.toBeNull()
|
|
29
|
+
expect(result).toContain("TODO: tapestry-migration (min)")
|
|
30
|
+
expect(result).toContain("TimeValue")
|
|
31
|
+
expect(result).toContain("min={9}")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it("adds TODO comment to max prop", () => {
|
|
35
|
+
const input = `
|
|
36
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
37
|
+
|
|
38
|
+
function Test() {
|
|
39
|
+
return <TimeField max={17} value={time} onChange={setTime} />
|
|
40
|
+
}
|
|
41
|
+
`.trim()
|
|
42
|
+
|
|
43
|
+
const result = applyTransform(input)
|
|
44
|
+
expect(result).not.toBeNull()
|
|
45
|
+
expect(result).toContain("TODO: tapestry-migration (max)")
|
|
46
|
+
expect(result).toContain("TimeValue")
|
|
47
|
+
expect(result).toContain("max={17}")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it("flags both min and max when present", () => {
|
|
51
|
+
const input = `
|
|
52
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
53
|
+
|
|
54
|
+
function Test() {
|
|
55
|
+
return <TimeField min={9} max={17} value={time} onChange={setTime} />
|
|
56
|
+
}
|
|
57
|
+
`.trim()
|
|
58
|
+
|
|
59
|
+
const result = applyTransform(input)
|
|
60
|
+
expect(result).not.toBeNull()
|
|
61
|
+
expect(result).toContain("TODO: tapestry-migration (min)")
|
|
62
|
+
expect(result).toContain("TODO: tapestry-migration (max)")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it("returns null when neither min nor max is present", () => {
|
|
66
|
+
const input = `
|
|
67
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
68
|
+
|
|
69
|
+
function Test() {
|
|
70
|
+
return <TimeField value={time} onChange={setTime} />
|
|
71
|
+
}
|
|
72
|
+
`.trim()
|
|
73
|
+
|
|
74
|
+
const result = applyTransform(input)
|
|
75
|
+
expect(result).toBeNull()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it("does not affect other components", () => {
|
|
79
|
+
const input = `
|
|
80
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
81
|
+
|
|
82
|
+
function Test() {
|
|
83
|
+
return <Input min={1} max={10} label="Count" />
|
|
84
|
+
}
|
|
85
|
+
`.trim()
|
|
86
|
+
|
|
87
|
+
const result = applyTransform(input)
|
|
88
|
+
expect(result).toBeNull()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("does not affect TimeField from other packages", () => {
|
|
92
|
+
const input = `
|
|
93
|
+
import { TimeField } from "some-other-lib"
|
|
94
|
+
|
|
95
|
+
function Test() {
|
|
96
|
+
return <TimeField min={9} max={17} value={time} onChange={setTime} />
|
|
97
|
+
}
|
|
98
|
+
`.trim()
|
|
99
|
+
|
|
100
|
+
const result = applyTransform(input)
|
|
101
|
+
expect(result).toBeNull()
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { 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 TIME_PROPS = ["min", "max"]
|
|
8
|
+
|
|
9
|
+
const transform: Transform = attributeTransformFactory({
|
|
10
|
+
targetComponent: "TimeField",
|
|
11
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
12
|
+
transform: (element, { j }) => {
|
|
13
|
+
let hasChanges = false
|
|
14
|
+
|
|
15
|
+
for (const propName of TIME_PROPS) {
|
|
16
|
+
const attr = getAttribute({ element, name: propName })
|
|
17
|
+
if (!attr) continue
|
|
18
|
+
|
|
19
|
+
addCommentToAttribute({
|
|
20
|
+
attribute: attr,
|
|
21
|
+
j,
|
|
22
|
+
text: `'${propName}' previously accepted an hour number (0–23). TimeField now expects a TimeValue (e.g. new Time(hour, minute)) from @internationalized/date. Migrate this value manually.`,
|
|
23
|
+
})
|
|
24
|
+
hasChanges = true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return hasChanges
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export default transform
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./mergeFieldIntoTimeField"
|
|
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("mergeFieldIntoTimeField transform", () => {
|
|
18
|
+
it("merges Field label into TimeField and unwraps Field", () => {
|
|
19
|
+
const input = `
|
|
20
|
+
import { Field, TimeField } from "@planningcenter/tapestry-react"
|
|
21
|
+
|
|
22
|
+
function Test() {
|
|
23
|
+
return (
|
|
24
|
+
<Field label="Start time"><TimeField value={time} onChange={setTime} /></Field>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
`.trim()
|
|
28
|
+
|
|
29
|
+
const result = applyTransform(input)
|
|
30
|
+
expect(result).not.toBeNull()
|
|
31
|
+
expect(result).not.toContain("<Field")
|
|
32
|
+
expect(result).toContain('label="Start time"')
|
|
33
|
+
expect(result).toContain("<TimeField")
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it("renames feedbackText to description on TimeField", () => {
|
|
37
|
+
const input = `
|
|
38
|
+
import { Field, TimeField } from "@planningcenter/tapestry-react"
|
|
39
|
+
|
|
40
|
+
function Test() {
|
|
41
|
+
return (
|
|
42
|
+
<Field feedbackText="Required"><TimeField value={time} onChange={setTime} /></Field>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
`.trim()
|
|
46
|
+
|
|
47
|
+
const result = applyTransform(input)
|
|
48
|
+
expect(result).not.toBeNull()
|
|
49
|
+
expect(result).not.toContain("<Field")
|
|
50
|
+
expect(result).not.toContain("feedbackText")
|
|
51
|
+
expect(result).toContain('description="Required"')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("flags helpContent as unsupported and removes Field", () => {
|
|
55
|
+
const input = `
|
|
56
|
+
import { Field, TimeField } from "@planningcenter/tapestry-react"
|
|
57
|
+
|
|
58
|
+
function Test() {
|
|
59
|
+
return (
|
|
60
|
+
<Field helpContent="Help"><TimeField value={time} onChange={setTime} /></Field>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
`.trim()
|
|
64
|
+
|
|
65
|
+
const result = applyTransform(input)
|
|
66
|
+
expect(result).not.toBeNull()
|
|
67
|
+
expect(result).not.toContain("<Field")
|
|
68
|
+
expect(result).toContain(
|
|
69
|
+
"TODO: tapestry-migration (mergeFieldIntoTimeField)"
|
|
70
|
+
)
|
|
71
|
+
expect(result).toContain("helpContent")
|
|
72
|
+
expect(result).toContain("not supported by TimeField")
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it("returns null when Field is not imported from tapestry-react", () => {
|
|
76
|
+
const input = `
|
|
77
|
+
import { Field } from "some-other-library"
|
|
78
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
79
|
+
|
|
80
|
+
function Test() {
|
|
81
|
+
return (
|
|
82
|
+
<Field label="Time"><TimeField value={time} onChange={setTime} /></Field>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
`.trim()
|
|
86
|
+
|
|
87
|
+
const result = applyTransform(input)
|
|
88
|
+
expect(result).toBeNull()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it("returns null when TimeField is not imported from tapestry-react", () => {
|
|
92
|
+
const input = `
|
|
93
|
+
import { Field } from "@planningcenter/tapestry-react"
|
|
94
|
+
import { TimeField } from "some-other-library"
|
|
95
|
+
|
|
96
|
+
function Test() {
|
|
97
|
+
return (
|
|
98
|
+
<Field label="Time"><TimeField value={time} onChange={setTime} /></Field>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
`.trim()
|
|
102
|
+
|
|
103
|
+
const result = applyTransform(input)
|
|
104
|
+
expect(result).toBeNull()
|
|
105
|
+
})
|
|
106
|
+
})
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./moveTimeFieldImport"
|
|
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("moveTimeFieldImport transform", () => {
|
|
19
|
+
describe("import migration", () => {
|
|
20
|
+
it("should change import package and keep TimeField name", () => {
|
|
21
|
+
const input = `
|
|
22
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
23
|
+
|
|
24
|
+
function Test() {
|
|
25
|
+
return <TimeField value={time} onChange={setTime} />
|
|
26
|
+
}
|
|
27
|
+
`.trim()
|
|
28
|
+
|
|
29
|
+
const result = applyTransform(input)
|
|
30
|
+
expect(result).toContain(
|
|
31
|
+
'import { TimeField } from "@planningcenter/tapestry"'
|
|
32
|
+
)
|
|
33
|
+
expect(result).not.toContain("@planningcenter/tapestry-react")
|
|
34
|
+
expect(result).toContain("<TimeField")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it("should only move TimeField, leaving other tapestry-react imports", () => {
|
|
38
|
+
const input = `
|
|
39
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
40
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
41
|
+
|
|
42
|
+
function Test() {
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<Button>Click</Button>
|
|
46
|
+
<TimeField value={time} onChange={setTime} />
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
`.trim()
|
|
51
|
+
|
|
52
|
+
const result = applyTransform(input)
|
|
53
|
+
expect(result).toContain(
|
|
54
|
+
'import { Button } from "@planningcenter/tapestry-react"'
|
|
55
|
+
)
|
|
56
|
+
expect(result).toContain(
|
|
57
|
+
'import { TimeField } from "@planningcenter/tapestry"'
|
|
58
|
+
)
|
|
59
|
+
expect(result).toContain("<TimeField")
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("import conflict handling", () => {
|
|
64
|
+
it("should use TdsTimeField alias when TimeField is already imported elsewhere", () => {
|
|
65
|
+
const input = `
|
|
66
|
+
import { TimeField } from "some-other-library"
|
|
67
|
+
import { TimeField as LegacyTimeField } from "@planningcenter/tapestry-react"
|
|
68
|
+
|
|
69
|
+
function Test() {
|
|
70
|
+
return (
|
|
71
|
+
<div>
|
|
72
|
+
<TimeField />
|
|
73
|
+
<LegacyTimeField value={time} onChange={setTime} />
|
|
74
|
+
</div>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
`.trim()
|
|
78
|
+
|
|
79
|
+
const result = applyTransform(input)
|
|
80
|
+
expect(result).toContain('import { TimeField } from "some-other-library"')
|
|
81
|
+
expect(result).toContain(
|
|
82
|
+
'import { TimeField as TdsTimeField } from "@planningcenter/tapestry"'
|
|
83
|
+
)
|
|
84
|
+
expect(result).toContain("<TdsTimeField")
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe("edge cases", () => {
|
|
89
|
+
it("should not affect other components", () => {
|
|
90
|
+
const input = `
|
|
91
|
+
import { Button } from "@planningcenter/tapestry-react"
|
|
92
|
+
|
|
93
|
+
function Test() {
|
|
94
|
+
return <Button>Click me</Button>
|
|
95
|
+
}
|
|
96
|
+
`.trim()
|
|
97
|
+
|
|
98
|
+
const result = applyTransform(input)
|
|
99
|
+
expect(result).toContain(
|
|
100
|
+
'import { Button } from "@planningcenter/tapestry-react"'
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("should handle no imports", () => {
|
|
105
|
+
const input = `
|
|
106
|
+
function Test() {
|
|
107
|
+
return <div>No imports</div>
|
|
108
|
+
}
|
|
109
|
+
`.trim()
|
|
110
|
+
|
|
111
|
+
const result = applyTransform(input)
|
|
112
|
+
expect(result).toBe(input)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it("should preserve all attributes", () => {
|
|
116
|
+
const input = `
|
|
117
|
+
import { TimeField } from "@planningcenter/tapestry-react"
|
|
118
|
+
|
|
119
|
+
function Test() {
|
|
120
|
+
return (
|
|
121
|
+
<TimeField
|
|
122
|
+
value={time}
|
|
123
|
+
onChange={setTime}
|
|
124
|
+
disabled
|
|
125
|
+
/>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
`.trim()
|
|
129
|
+
|
|
130
|
+
const result = applyTransform(input)
|
|
131
|
+
expect(result).toContain(
|
|
132
|
+
'import { TimeField } from "@planningcenter/tapestry"'
|
|
133
|
+
)
|
|
134
|
+
expect(result).toContain("value={time}")
|
|
135
|
+
expect(result).toContain("onChange={setTime}")
|
|
136
|
+
expect(result).toContain("disabled")
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it("should handle alias import", () => {
|
|
140
|
+
const input = `
|
|
141
|
+
import { TimeField as TapestryTimeField } from "@planningcenter/tapestry-react"
|
|
142
|
+
|
|
143
|
+
function Test() {
|
|
144
|
+
return <TapestryTimeField value={time} onChange={setTime} />
|
|
145
|
+
}
|
|
146
|
+
`.trim()
|
|
147
|
+
|
|
148
|
+
const result = applyTransform(input)
|
|
149
|
+
expect(result).toContain("@planningcenter/tapestry")
|
|
150
|
+
expect(result).not.toContain("@planningcenter/tapestry-react")
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
})
|
|
@@ -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: "TdsTimeField",
|
|
8
|
+
fromComponent: "TimeField",
|
|
9
|
+
fromPackage: "@planningcenter/tapestry-react",
|
|
10
|
+
toComponent: "TimeField",
|
|
11
|
+
toPackage: "@planningcenter/tapestry",
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export default transform
|