@planningcenter/tapestry-migration-cli 3.1.0-rc.8 → 3.1.0
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/dist/tapestry-react-shim.cjs +7 -1
- package/package.json +3 -3
- package/src/components/input/transformableInput.ts +47 -6
- package/src/components/input/transforms/mergeFieldIntoInput.test.ts +78 -0
- package/src/components/input/transforms/mergeFieldIntoInput.ts +6 -212
- package/src/components/input/transforms/removeDuplicateKeys.test.ts +3 -3
- package/src/components/input/transforms/removeTypeInput.test.ts +212 -0
- package/src/components/input/transforms/removeTypeInput.ts +22 -0
- package/src/components/input/transforms/removeTypeText.ts +2 -3
- package/src/components/input/transforms/unsupportedProps.test.ts +20 -20
- package/src/components/select/index.ts +58 -0
- package/src/components/select/transformableSelect.ts +7 -0
- package/src/components/select/transforms/auditSpreadProps.test.ts +103 -0
- package/src/components/select/transforms/auditSpreadProps.ts +26 -0
- package/src/components/select/transforms/childrenToOptions.test.ts +367 -0
- package/src/components/select/transforms/childrenToOptions.ts +295 -0
- package/src/components/select/transforms/convertLegacyOptions.test.ts +150 -0
- package/src/components/select/transforms/convertLegacyOptions.ts +105 -0
- package/src/components/select/transforms/convertStyleProps.test.ts +73 -0
- package/src/components/select/transforms/convertStyleProps.ts +12 -0
- package/src/components/select/transforms/emptyValueToPlaceholder.test.ts +122 -0
- package/src/components/select/transforms/emptyValueToPlaceholder.ts +22 -0
- package/src/components/select/transforms/innerRefToRef.test.ts +89 -0
- package/src/components/select/transforms/innerRefToRef.ts +18 -0
- package/src/components/select/transforms/mapChildrenToOptions.test.ts +521 -0
- package/src/components/select/transforms/mapChildrenToOptions.ts +312 -0
- package/src/components/select/transforms/mergeFieldIntoSelect.test.ts +506 -0
- package/src/components/select/transforms/mergeFieldIntoSelect.ts +7 -0
- package/src/components/select/transforms/mergeSelectLabel.test.ts +458 -0
- package/src/components/select/transforms/mergeSelectLabel.ts +225 -0
- package/src/components/select/transforms/moveSelectImport.test.ts +148 -0
- package/src/components/select/transforms/moveSelectImport.ts +14 -0
- package/src/components/select/transforms/removeDefaultProps.test.ts +249 -0
- package/src/components/select/transforms/removeDefaultProps.ts +112 -0
- package/src/components/select/transforms/sizeMapping.test.ts +188 -0
- package/src/components/select/transforms/sizeMapping.ts +17 -0
- package/src/components/select/transforms/skipMultipleSelect.test.ts +148 -0
- package/src/components/select/transforms/skipMultipleSelect.ts +23 -0
- package/src/components/select/transforms/stateToInvalid.test.ts +217 -0
- package/src/components/select/transforms/stateToInvalid.ts +59 -0
- package/src/components/select/transforms/stateToInvalidTernary.test.ts +146 -0
- package/src/components/select/transforms/stateToInvalidTernary.ts +13 -0
- package/src/components/select/transforms/unsupportedProps.test.ts +252 -0
- package/src/components/select/transforms/unsupportedProps.ts +44 -0
- package/src/components/shared/helpers/getAttributeExpression.ts +26 -0
- package/src/components/shared/helpers/unsupportedPropsHelpers.ts +52 -2
- package/src/components/shared/transformFactories/mergeFieldFactory.ts +244 -0
- package/src/components/shared/transformFactories/stylePropTransformFactory.ts +2 -1
- package/src/components/text-area/index.ts +48 -0
- package/src/components/text-area/transforms/auditSpreadProps.test.ts +139 -0
- package/src/components/text-area/transforms/auditSpreadProps.ts +10 -0
- package/src/components/text-area/transforms/convertStyleProps.test.ts +158 -0
- package/src/components/text-area/transforms/convertStyleProps.ts +10 -0
- package/src/components/text-area/transforms/innerRefToRef.test.ts +206 -0
- package/src/components/text-area/transforms/innerRefToRef.ts +14 -0
- package/src/components/text-area/transforms/mergeFieldIntoTextArea.test.ts +477 -0
- package/src/components/text-area/transforms/mergeFieldIntoTextArea.ts +5 -0
- package/src/components/text-area/transforms/moveTextAreaImport.test.ts +168 -0
- package/src/components/text-area/transforms/moveTextAreaImport.ts +13 -0
- package/src/components/text-area/transforms/removeDuplicateKeys.test.ts +129 -0
- package/src/components/text-area/transforms/removeDuplicateKeys.ts +8 -0
- package/src/components/text-area/transforms/removeRedundantAriaLabel.test.ts +183 -0
- package/src/components/text-area/transforms/removeRedundantAriaLabel.ts +59 -0
- package/src/components/text-area/transforms/sizeMapping.test.ts +199 -0
- package/src/components/text-area/transforms/sizeMapping.ts +15 -0
- package/src/components/text-area/transforms/stateToInvalid.test.ts +204 -0
- package/src/components/text-area/transforms/stateToInvalid.ts +57 -0
- package/src/components/text-area/transforms/stateToInvalidTernary.test.ts +133 -0
- package/src/components/text-area/transforms/stateToInvalidTernary.ts +11 -0
- package/src/components/text-area/transforms/unsupportedProps.test.ts +275 -0
- package/src/components/text-area/transforms/unsupportedProps.ts +35 -0
- package/src/index.ts +4 -1
|
@@ -2917,6 +2917,7 @@ const tokens = {
|
|
|
2917
2917
|
"--t-fill-color-interaction-hover": "hsl(204, 100%, 35%)",
|
|
2918
2918
|
"--t-fill-color-interaction-active": "hsl(204, 100%, 30%)",
|
|
2919
2919
|
"--t-fill-color-interaction-disabled": "hsl(0, 0%, 81%)",
|
|
2920
|
+
"--t-fill-color-control-neutral": "hsl(0, 0%, 58%)",
|
|
2920
2921
|
"--t-fill-color-control-neutral-off": "hsl(0, 0%, 58%)",
|
|
2921
2922
|
"--t-fill-color-control-neutral-on": "hsl(0, 0%, 24%)",
|
|
2922
2923
|
"--t-fill-color-control": "hsl(204, 100%, 40%)",
|
|
@@ -3232,7 +3233,12 @@ const tokens = {
|
|
|
3232
3233
|
"--t-form-font-color-error": "hsl(8, 60%, 45%)",
|
|
3233
3234
|
"--t-form-font-color-readonly": "hsl(0, 0%, 24%)",
|
|
3234
3235
|
"--t-form-picker-icon-color": "hsl(0, 0%, 24%)",
|
|
3235
|
-
"--t-form-placeholder-color": "hsl(0, 0%, 58%)"
|
|
3236
|
+
"--t-form-placeholder-color": "hsl(0, 0%, 58%)",
|
|
3237
|
+
"--t-form-toggle-color": "hsl(0, 0%, 58%)",
|
|
3238
|
+
"--t-form-toggle-color-disabled": "hsl(0, 0%, 81%)",
|
|
3239
|
+
"--t-form-toggle-color-hover": "hsl(0, 0%, 50%)",
|
|
3240
|
+
"--t-form-toggle-color-on": "hsl(204, 100%, 40%)",
|
|
3241
|
+
"--t-form-toggle-color-on-hover": "hsl(204, 100%, 35%)"
|
|
3236
3242
|
};
|
|
3237
3243
|
function token(varName) {
|
|
3238
3244
|
return `var(${varName})`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@planningcenter/tapestry-migration-cli",
|
|
3
|
-
"version": "3.1.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "CLI tool for Tapestry migrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@emotion/react": "^11.14.0",
|
|
35
|
-
"@planningcenter/tapestry": "^3.1.0
|
|
35
|
+
"@planningcenter/tapestry": "^3.1.0",
|
|
36
36
|
"@planningcenter/tapestry-react": "^4.11.5",
|
|
37
37
|
"@types/jscodeshift": "^17.3.0",
|
|
38
38
|
"@types/node": "^20.0.0",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "3d173bf28e92e340ac45b1f08aae5ad64334e94b"
|
|
56
56
|
}
|
|
@@ -1,8 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import {
|
|
1
|
+
import { JSXElement } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { ACCEPTED_INPUT_TYPES } from "../shared/helpers/unsupportedPropsHelpers"
|
|
4
4
|
import { TransformCondition } from "../shared/types"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// Include legacy "input" value so removeTypeInput can strip it
|
|
7
|
+
const acceptedTypes: readonly string[] = [...ACCEPTED_INPUT_TYPES, "input"]
|
|
8
|
+
|
|
9
|
+
function hasUnsupportedType(element: JSXElement): boolean {
|
|
10
|
+
const attributes = element.openingElement.attributes || []
|
|
11
|
+
const typeAttr = attributes.find(
|
|
12
|
+
(attr) =>
|
|
13
|
+
attr.type === "JSXAttribute" &&
|
|
14
|
+
attr.name?.type === "JSXIdentifier" &&
|
|
15
|
+
attr.name.name === "type"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
if (!typeAttr || typeAttr.type !== "JSXAttribute") {
|
|
19
|
+
return false
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeAttr.value?.type === "StringLiteral") {
|
|
23
|
+
return !acceptedTypes.includes(typeAttr.value.value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (typeAttr.value?.type === "JSXExpressionContainer") {
|
|
27
|
+
const { expression } = typeAttr.value
|
|
28
|
+
if (expression.type === "StringLiteral") {
|
|
29
|
+
return !acceptedTypes.includes(expression.value)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hasAttribute(element: JSXElement, attributeName: string): boolean {
|
|
37
|
+
const attributes = element.openingElement.attributes || []
|
|
38
|
+
return attributes.some(
|
|
39
|
+
(attr) =>
|
|
40
|
+
attr.type === "JSXAttribute" &&
|
|
41
|
+
attr.name?.type === "JSXIdentifier" &&
|
|
42
|
+
attr.name.name === attributeName
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const transformableInput: TransformCondition = (element) =>
|
|
47
|
+
!hasAttribute(element, "renderLeft") &&
|
|
48
|
+
!hasAttribute(element, "renderRight") &&
|
|
49
|
+
!hasUnsupportedType(element)
|
|
@@ -305,6 +305,84 @@ function Test() {
|
|
|
305
305
|
})
|
|
306
306
|
})
|
|
307
307
|
|
|
308
|
+
describe("key prop handling", () => {
|
|
309
|
+
it("moves key from Field to Input when Input has no key", () => {
|
|
310
|
+
const input = `
|
|
311
|
+
import { Field, Input } from "@planningcenter/tapestry-react"
|
|
312
|
+
|
|
313
|
+
function Test() {
|
|
314
|
+
return (
|
|
315
|
+
<Box>
|
|
316
|
+
<Field key="item-1" label="Name"><Input /></Field>
|
|
317
|
+
</Box>
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
`.trim()
|
|
321
|
+
|
|
322
|
+
const result = applyTransform(input)
|
|
323
|
+
expect(result).not.toBeNull()
|
|
324
|
+
expect(result).not.toContain("<Field")
|
|
325
|
+
expect(result).toContain('key="item-1"')
|
|
326
|
+
expect(result).toContain("<Input")
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it("keeps Input's key when both Field and Input have key", () => {
|
|
330
|
+
const input = `
|
|
331
|
+
import { Field, Input } from "@planningcenter/tapestry-react"
|
|
332
|
+
|
|
333
|
+
function Test() {
|
|
334
|
+
return (
|
|
335
|
+
<Box>
|
|
336
|
+
<Field key="field-1" label="Name"><Input key="input-1" /></Field>
|
|
337
|
+
</Box>
|
|
338
|
+
)
|
|
339
|
+
}
|
|
340
|
+
`.trim()
|
|
341
|
+
|
|
342
|
+
const result = applyTransform(input)
|
|
343
|
+
expect(result).not.toBeNull()
|
|
344
|
+
expect(result).not.toContain("<Field")
|
|
345
|
+
expect(result).toContain('key="input-1"')
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
describe("spread props on Field", () => {
|
|
350
|
+
it("bails out with TODO comment when Field has spread props", () => {
|
|
351
|
+
const input = `
|
|
352
|
+
import { Field, Input } from "@planningcenter/tapestry-react"
|
|
353
|
+
|
|
354
|
+
function Test() {
|
|
355
|
+
return (
|
|
356
|
+
<Field {...fieldProps}><Input /></Field>
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
`.trim()
|
|
360
|
+
|
|
361
|
+
const result = applyTransform(input)
|
|
362
|
+
expect(result).not.toBeNull()
|
|
363
|
+
expect(result).toContain("<Field")
|
|
364
|
+
expect(result).toContain("TODO: tapestry-migration (mergeFieldIntoInput)")
|
|
365
|
+
expect(result).toContain("spread props")
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
it("bails out when Field has spread props mixed with regular props", () => {
|
|
369
|
+
const input = `
|
|
370
|
+
import { Field, Input } from "@planningcenter/tapestry-react"
|
|
371
|
+
|
|
372
|
+
function Test() {
|
|
373
|
+
return (
|
|
374
|
+
<Field label="Name" {...fieldProps}><Input /></Field>
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
`.trim()
|
|
378
|
+
|
|
379
|
+
const result = applyTransform(input)
|
|
380
|
+
expect(result).not.toBeNull()
|
|
381
|
+
expect(result).toContain("<Field")
|
|
382
|
+
expect(result).toContain("spread props")
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
308
386
|
describe("not from tapestry-react", () => {
|
|
309
387
|
it("returns null when Field is not imported from tapestry-react", () => {
|
|
310
388
|
const input = `
|
|
@@ -1,213 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mergeFieldFactory } from "../../shared/transformFactories/mergeFieldFactory"
|
|
2
|
+
import { transformableInput } from "../transformableInput"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
getImportName,
|
|
8
|
-
removeImportFromDeclaration,
|
|
9
|
-
} from "../../shared/transformFactories/helpers/manageImports"
|
|
10
|
-
|
|
11
|
-
const SCOPE = "mergeFieldIntoInput"
|
|
12
|
-
|
|
13
|
-
const transform: Transform = (fileInfo, api) => {
|
|
14
|
-
const j = api.jscodeshift
|
|
15
|
-
const source = j(fileInfo.source)
|
|
16
|
-
|
|
17
|
-
const fieldLocalName = getImportName(
|
|
18
|
-
"Field",
|
|
19
|
-
"@planningcenter/tapestry-react",
|
|
20
|
-
{ j, source }
|
|
21
|
-
)
|
|
22
|
-
if (!fieldLocalName) return null
|
|
23
|
-
|
|
24
|
-
const inputLocalName = getImportName(
|
|
25
|
-
"Input",
|
|
26
|
-
"@planningcenter/tapestry-react",
|
|
27
|
-
{ j, source }
|
|
28
|
-
)
|
|
29
|
-
if (!inputLocalName) return null
|
|
30
|
-
|
|
31
|
-
let hasChanges = false
|
|
32
|
-
let anyFieldRemoved = false
|
|
33
|
-
|
|
34
|
-
source.find(j.JSXElement).forEach((path) => {
|
|
35
|
-
const el = path.value
|
|
36
|
-
const opening = el.openingElement
|
|
37
|
-
|
|
38
|
-
if (opening.name.type !== "JSXIdentifier") return
|
|
39
|
-
if (opening.name.name !== fieldLocalName) return
|
|
40
|
-
|
|
41
|
-
const elementChildren = (el.children || []).filter(
|
|
42
|
-
(child) =>
|
|
43
|
-
child.type !== "JSXText" || (child as JSXText).value.trim() !== ""
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
const inputChildren = elementChildren.filter((child) => {
|
|
47
|
-
if (child.type !== "JSXElement") return false
|
|
48
|
-
const childOpening = (child as JSXElement).openingElement
|
|
49
|
-
return (
|
|
50
|
-
childOpening.name.type === "JSXIdentifier" &&
|
|
51
|
-
childOpening.name.name === inputLocalName
|
|
52
|
-
)
|
|
53
|
-
}) as JSXElement[]
|
|
54
|
-
|
|
55
|
-
const hasRenderSide = orConditions(
|
|
56
|
-
hasAttribute("renderLeft"),
|
|
57
|
-
hasAttribute("renderRight")
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
// Case: exactly 1 child and it is an Input — merge props and unwrap
|
|
61
|
-
if (elementChildren.length === 1 && inputChildren.length === 1) {
|
|
62
|
-
const inputEl = inputChildren[0]
|
|
63
|
-
|
|
64
|
-
// Skip inputs with renderLeft/renderRight — they are not transformable
|
|
65
|
-
if (hasRenderSide(inputEl)) return
|
|
66
|
-
|
|
67
|
-
const fieldAttrs = opening.attributes || []
|
|
68
|
-
|
|
69
|
-
for (const attr of fieldAttrs) {
|
|
70
|
-
if (attr.type !== "JSXAttribute") continue
|
|
71
|
-
if (attr.name.type !== "JSXIdentifier") continue
|
|
72
|
-
|
|
73
|
-
const attrName = attr.name.name
|
|
74
|
-
const inputAttrs = inputEl.openingElement.attributes || []
|
|
75
|
-
|
|
76
|
-
if (attrName === "label") {
|
|
77
|
-
const hasLabel = inputAttrs.some(
|
|
78
|
-
(a) =>
|
|
79
|
-
a.type === "JSXAttribute" &&
|
|
80
|
-
a.name?.type === "JSXIdentifier" &&
|
|
81
|
-
a.name.name === "label"
|
|
82
|
-
)
|
|
83
|
-
if (hasLabel) {
|
|
84
|
-
addComment({
|
|
85
|
-
element: inputEl,
|
|
86
|
-
j,
|
|
87
|
-
scope: SCOPE,
|
|
88
|
-
source,
|
|
89
|
-
text: "Field had label prop but Input already has label. Please migrate manually.",
|
|
90
|
-
})
|
|
91
|
-
} else {
|
|
92
|
-
inputEl.openingElement.attributes.push(attr)
|
|
93
|
-
}
|
|
94
|
-
} else if (attrName === "feedbackText") {
|
|
95
|
-
const hasDescription = inputAttrs.some(
|
|
96
|
-
(a) =>
|
|
97
|
-
a.type === "JSXAttribute" &&
|
|
98
|
-
a.name?.type === "JSXIdentifier" &&
|
|
99
|
-
a.name.name === "description"
|
|
100
|
-
)
|
|
101
|
-
if (hasDescription) {
|
|
102
|
-
addComment({
|
|
103
|
-
element: inputEl,
|
|
104
|
-
j,
|
|
105
|
-
scope: SCOPE,
|
|
106
|
-
source,
|
|
107
|
-
text: "Field had feedbackText prop but Input already has description. Please migrate manually.",
|
|
108
|
-
})
|
|
109
|
-
} else {
|
|
110
|
-
const newAttr = j.jsxAttribute(
|
|
111
|
-
j.jsxIdentifier("description"),
|
|
112
|
-
attr.value
|
|
113
|
-
)
|
|
114
|
-
inputEl.openingElement.attributes.push(newAttr)
|
|
115
|
-
}
|
|
116
|
-
} else if (attrName === "state") {
|
|
117
|
-
const hasState = inputAttrs.some(
|
|
118
|
-
(a) =>
|
|
119
|
-
a.type === "JSXAttribute" &&
|
|
120
|
-
a.name?.type === "JSXIdentifier" &&
|
|
121
|
-
a.name.name === "state"
|
|
122
|
-
)
|
|
123
|
-
if (hasState) {
|
|
124
|
-
addComment({
|
|
125
|
-
element: inputEl,
|
|
126
|
-
j,
|
|
127
|
-
scope: SCOPE,
|
|
128
|
-
source,
|
|
129
|
-
text: "Field had state prop but Input already has state. Please migrate manually.",
|
|
130
|
-
})
|
|
131
|
-
} else {
|
|
132
|
-
inputEl.openingElement.attributes.push(attr)
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
135
|
-
addComment({
|
|
136
|
-
element: inputEl,
|
|
137
|
-
j,
|
|
138
|
-
scope: SCOPE,
|
|
139
|
-
source,
|
|
140
|
-
text: `Field prop '${attrName}' is not supported by Input. Please migrate manually.`,
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const parent = path.parent?.value
|
|
146
|
-
if (parent?.children) {
|
|
147
|
-
const idx = parent.children.indexOf(el)
|
|
148
|
-
if (idx === -1) return
|
|
149
|
-
parent.children.splice(idx, 1, ...(el.children || []))
|
|
150
|
-
} else {
|
|
151
|
-
// Root JSX (e.g. directly inside return parens) — use path.replace
|
|
152
|
-
const nonWsChildren = (el.children || []).filter(
|
|
153
|
-
(child) =>
|
|
154
|
-
child.type !== "JSXText" || (child as JSXText).value.trim() !== ""
|
|
155
|
-
)
|
|
156
|
-
if (nonWsChildren.length === 1) {
|
|
157
|
-
path.replace(nonWsChildren[0])
|
|
158
|
-
} else {
|
|
159
|
-
path.replace(
|
|
160
|
-
j.jsxFragment(
|
|
161
|
-
j.jsxOpeningFragment(),
|
|
162
|
-
j.jsxClosingFragment(),
|
|
163
|
-
el.children || []
|
|
164
|
-
)
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
hasChanges = true
|
|
169
|
-
anyFieldRemoved = true
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Case: more than 1 non-whitespace child — comment each Input child, leave Field
|
|
174
|
-
if (elementChildren.length > 1) {
|
|
175
|
-
for (const child of inputChildren) {
|
|
176
|
-
// Skip inputs with renderLeft/renderRight — they are not transformable
|
|
177
|
-
if (hasRenderSide(child)) continue
|
|
178
|
-
addComment({
|
|
179
|
-
element: child,
|
|
180
|
-
j,
|
|
181
|
-
scope: SCOPE,
|
|
182
|
-
source,
|
|
183
|
-
text: "Field has multiple children and cannot be auto-merged into Input. Please migrate manually.",
|
|
184
|
-
})
|
|
185
|
-
hasChanges = true
|
|
186
|
-
}
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Case: exactly 1 child but not an Input — skip without comment
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
// Remove Field from imports only if all Field usages were converted
|
|
194
|
-
if (anyFieldRemoved) {
|
|
195
|
-
const stillUsesField =
|
|
196
|
-
source.find(j.JSXOpeningElement, {
|
|
197
|
-
name: { name: fieldLocalName },
|
|
198
|
-
}).length > 0
|
|
199
|
-
|
|
200
|
-
if (!stillUsesField) {
|
|
201
|
-
const fieldImports = source.find(j.ImportDeclaration, {
|
|
202
|
-
source: { value: "@planningcenter/tapestry-react" },
|
|
203
|
-
})
|
|
204
|
-
for (let i = 0; i < fieldImports.length; i++) {
|
|
205
|
-
removeImportFromDeclaration(fieldImports.at(i), "Field")
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return hasChanges ? source.toSource() : null
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export default transform
|
|
4
|
+
export default mergeFieldFactory({
|
|
5
|
+
condition: transformableInput,
|
|
6
|
+
targetComponent: "Input",
|
|
7
|
+
})
|
|
@@ -195,7 +195,7 @@ describe("removeDuplicateKeys transform", () => {
|
|
|
195
195
|
export function TestComponent() {
|
|
196
196
|
return (
|
|
197
197
|
<form>
|
|
198
|
-
<Input type="
|
|
198
|
+
<Input type="email" kind="primary" type="text">Submit</Input>
|
|
199
199
|
<Input disabled loading disabled>Loading</Input>
|
|
200
200
|
<Input size="small">Small</Input>
|
|
201
201
|
</form>
|
|
@@ -208,8 +208,8 @@ describe("removeDuplicateKeys transform", () => {
|
|
|
208
208
|
expect(result).not.toBeNull()
|
|
209
209
|
|
|
210
210
|
// First input: should keep first 'type'
|
|
211
|
-
expect(result).toContain('type="
|
|
212
|
-
expect(result).not.toContain('type="
|
|
211
|
+
expect(result).toContain('type="email"')
|
|
212
|
+
expect(result).not.toContain('type="text"')
|
|
213
213
|
|
|
214
214
|
expect(result).toContain("<Input disabled loading>")
|
|
215
215
|
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import jscodeshift from "jscodeshift"
|
|
2
|
+
import { describe, expect, it } from "vitest"
|
|
3
|
+
|
|
4
|
+
import transform from "./removeTypeInput"
|
|
5
|
+
|
|
6
|
+
const j = jscodeshift.withParser("tsx")
|
|
7
|
+
|
|
8
|
+
// Helper to run transform and get result
|
|
9
|
+
function applyTransform(source: string): string | null {
|
|
10
|
+
const fileInfo = { path: "test.tsx", source }
|
|
11
|
+
const api = {
|
|
12
|
+
j,
|
|
13
|
+
jscodeshift: j,
|
|
14
|
+
report: () => {},
|
|
15
|
+
stats: () => {},
|
|
16
|
+
}
|
|
17
|
+
const result = transform(fileInfo, api, {})
|
|
18
|
+
return result as string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe("removeTypeInput transform", () => {
|
|
22
|
+
describe("basic transformation", () => {
|
|
23
|
+
it("should remove type='input' from Input", () => {
|
|
24
|
+
const input = `
|
|
25
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
26
|
+
|
|
27
|
+
function Component() {
|
|
28
|
+
return <Input type="input" />
|
|
29
|
+
}
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const result = applyTransform(input)
|
|
33
|
+
|
|
34
|
+
expect(result).toContain("<Input />")
|
|
35
|
+
expect(result).not.toContain('type="input"')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('should remove type="input" from Input with other props', () => {
|
|
39
|
+
const input = `
|
|
40
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
41
|
+
|
|
42
|
+
function Component() {
|
|
43
|
+
return <Input type="input" onChange={handleChange} />
|
|
44
|
+
}
|
|
45
|
+
`
|
|
46
|
+
|
|
47
|
+
const result = applyTransform(input)
|
|
48
|
+
|
|
49
|
+
expect(result).toContain("<Input onChange={handleChange} />")
|
|
50
|
+
expect(result).not.toContain('type="input"')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it("should preserve Input without type='input'", () => {
|
|
54
|
+
const input = `
|
|
55
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
56
|
+
|
|
57
|
+
function Component() {
|
|
58
|
+
return <Input onChange={handleChange} />
|
|
59
|
+
}
|
|
60
|
+
`
|
|
61
|
+
|
|
62
|
+
const result = applyTransform(input)
|
|
63
|
+
|
|
64
|
+
expect(result).toBeNull()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("should preserve Input with type='email'", () => {
|
|
68
|
+
const input = `
|
|
69
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
70
|
+
|
|
71
|
+
function Component() {
|
|
72
|
+
return <Input type="email" />
|
|
73
|
+
}
|
|
74
|
+
`
|
|
75
|
+
|
|
76
|
+
const result = applyTransform(input)
|
|
77
|
+
|
|
78
|
+
expect(result).toBeNull()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it("should preserve Input with type='number'", () => {
|
|
82
|
+
const input = `
|
|
83
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
84
|
+
|
|
85
|
+
function Component() {
|
|
86
|
+
return <Input type="number" />
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
|
|
90
|
+
const result = applyTransform(input)
|
|
91
|
+
|
|
92
|
+
expect(result).toBeNull()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("should remove type='text' from Input", () => {
|
|
96
|
+
const input = `
|
|
97
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
98
|
+
|
|
99
|
+
function Component() {
|
|
100
|
+
return <Input type="text" />
|
|
101
|
+
}
|
|
102
|
+
`
|
|
103
|
+
|
|
104
|
+
const result = applyTransform(input)
|
|
105
|
+
|
|
106
|
+
expect(result).toContain("<Input />")
|
|
107
|
+
expect(result).not.toContain('type="text"')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it("should remove type='text' from Input with other props", () => {
|
|
111
|
+
const input = `
|
|
112
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
113
|
+
|
|
114
|
+
function Component() {
|
|
115
|
+
return <Input type="text" value={searchTerm} placeholder="Search" onChange={handleChange} />
|
|
116
|
+
}
|
|
117
|
+
`
|
|
118
|
+
|
|
119
|
+
const result = applyTransform(input)
|
|
120
|
+
|
|
121
|
+
expect(result).not.toContain('type="text"')
|
|
122
|
+
expect(result).toContain("value={searchTerm}")
|
|
123
|
+
expect(result).toContain('placeholder="Search"')
|
|
124
|
+
expect(result).toContain("onChange={handleChange}")
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
describe("multiple inputs", () => {
|
|
129
|
+
it("should handle mixed Input usage", () => {
|
|
130
|
+
const input = `
|
|
131
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
132
|
+
|
|
133
|
+
function Component() {
|
|
134
|
+
return (
|
|
135
|
+
<div>
|
|
136
|
+
<Input type="input" />
|
|
137
|
+
<Input type="text" />
|
|
138
|
+
<Input type="email" />
|
|
139
|
+
<Input type="input" onChange={handleChange} />
|
|
140
|
+
</div>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
`
|
|
144
|
+
|
|
145
|
+
const result = applyTransform(input)
|
|
146
|
+
|
|
147
|
+
expect(result).toContain('<Input type="email" />')
|
|
148
|
+
expect(result).toContain("<Input onChange={handleChange} />")
|
|
149
|
+
expect(result).not.toContain('type="input"')
|
|
150
|
+
expect(result).not.toContain('type="text"')
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe("edge cases", () => {
|
|
155
|
+
it("should not affect HTML input elements", () => {
|
|
156
|
+
const input = `
|
|
157
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
158
|
+
|
|
159
|
+
function Component() {
|
|
160
|
+
return (
|
|
161
|
+
<div>
|
|
162
|
+
<Input type="input" />
|
|
163
|
+
<input type="input" />
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
`
|
|
168
|
+
|
|
169
|
+
const result = applyTransform(input)
|
|
170
|
+
|
|
171
|
+
expect(result).toContain("<Input />")
|
|
172
|
+
expect(result).toContain('<input type="input" />')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it("should handle expression syntax type={'input'}", () => {
|
|
176
|
+
const input = `
|
|
177
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
178
|
+
|
|
179
|
+
function Component() {
|
|
180
|
+
return <Input type={"input"} />
|
|
181
|
+
}
|
|
182
|
+
`
|
|
183
|
+
|
|
184
|
+
const result = applyTransform(input)
|
|
185
|
+
|
|
186
|
+
if (result) {
|
|
187
|
+
expect(result).toContain("<Input />")
|
|
188
|
+
expect(result).not.toContain('type={"input"}')
|
|
189
|
+
} else {
|
|
190
|
+
expect(input).toContain('type={"input"}')
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
describe("import handling", () => {
|
|
196
|
+
it("should not affect imports", () => {
|
|
197
|
+
const input = `
|
|
198
|
+
import { Input } from "@planningcenter/tapestry-react"
|
|
199
|
+
|
|
200
|
+
function Component() {
|
|
201
|
+
return <Input type="input" />
|
|
202
|
+
}
|
|
203
|
+
`
|
|
204
|
+
|
|
205
|
+
const result = applyTransform(input)
|
|
206
|
+
|
|
207
|
+
expect(result).toContain(
|
|
208
|
+
'import { Input } from "@planningcenter/tapestry-react"'
|
|
209
|
+
)
|
|
210
|
+
})
|
|
211
|
+
})
|
|
212
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Transform } from "jscodeshift"
|
|
2
|
+
|
|
3
|
+
import { removeAttribute } from "../../shared/actions/removeAttribute"
|
|
4
|
+
import { hasAttributeValue } from "../../shared/conditions/hasAttributeValue"
|
|
5
|
+
import { orConditions } from "../../shared/conditions/orConditions"
|
|
6
|
+
import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
|
|
7
|
+
import { transformableInput } from "../transformableInput"
|
|
8
|
+
|
|
9
|
+
const transform: Transform = attributeTransformFactory({
|
|
10
|
+
condition: (element) =>
|
|
11
|
+
transformableInput(element) &&
|
|
12
|
+
orConditions(
|
|
13
|
+
hasAttributeValue("type", "input"),
|
|
14
|
+
hasAttributeValue("type", "text")
|
|
15
|
+
)(element),
|
|
16
|
+
targetComponent: "Input",
|
|
17
|
+
targetPackage: "@planningcenter/tapestry-react",
|
|
18
|
+
transform: (element, { j, source }) =>
|
|
19
|
+
removeAttribute("type", { element, j, source }),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export default transform
|
|
@@ -6,9 +6,8 @@ import { attributeTransformFactory } from "../../shared/transformFactories/attri
|
|
|
6
6
|
import { transformableInput } from "../transformableInput"
|
|
7
7
|
|
|
8
8
|
const transform: Transform = attributeTransformFactory({
|
|
9
|
-
condition: (element
|
|
10
|
-
transformableInput(element,
|
|
11
|
-
hasAttributeValue("type", "text")(element, context),
|
|
9
|
+
condition: (element) =>
|
|
10
|
+
transformableInput(element) && hasAttributeValue("type", "text")(element),
|
|
12
11
|
targetComponent: "Input",
|
|
13
12
|
targetPackage: "@planningcenter/tapestry-react",
|
|
14
13
|
transform: (element, { j, source }) =>
|