@planningcenter/tapestry-migration-cli 3.1.0-rc.14 → 3.1.0-rc.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-migration-cli",
3
- "version": "3.1.0-rc.14",
3
+ "version": "3.1.0-rc.16",
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-rc.14",
35
+ "@planningcenter/tapestry": "^3.1.0-rc.16",
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": "73fa96b50968cfc3d474d33ae91cd9e221f10fa5"
55
+ "gitHead": "d3f8d5b0ac2eafea617315fdcfa7a4911e67294d"
56
56
  }
@@ -40,6 +40,44 @@ function Test() {
40
40
  expect(result).not.toContain("Select.Option")
41
41
  })
42
42
 
43
+ it("should preserve data-* attributes on options", () => {
44
+ const input = `
45
+ import { Select } from "@planningcenter/tapestry-react"
46
+
47
+ function Test() {
48
+ return (
49
+ <Select emptyValue="Pick one">
50
+ <Select.Option value="apple" data-testid="apple-opt">Apple</Select.Option>
51
+ </Select>
52
+ )
53
+ }
54
+ `.trim()
55
+
56
+ const result = applyTransform(input)
57
+ expect(result).toContain('"data-testid": "apple-opt"')
58
+ expect(result).toContain('label: "Apple"')
59
+ expect(result).toContain('value: "apple"')
60
+ })
61
+
62
+ it("should preserve dynamic data-* attribute expressions on options", () => {
63
+ const input = `
64
+ import { Select } from "@planningcenter/tapestry-react"
65
+
66
+ function Test() {
67
+ return (
68
+ <Select emptyValue="Pick one">
69
+ <Select.Option value="apple" data-pendo={pendoIds.apple}>Apple</Select.Option>
70
+ </Select>
71
+ )
72
+ }
73
+ `.trim()
74
+
75
+ const result = applyTransform(input)
76
+ expect(result).toContain('"data-pendo": pendoIds.apple')
77
+ expect(result).toContain('label: "Apple"')
78
+ expect(result).toContain('value: "apple"')
79
+ })
80
+
43
81
  it("should handle disabled options", () => {
44
82
  const input = `
45
83
  import { Select } from "@planningcenter/tapestry-react"
@@ -10,6 +10,7 @@ import { addComment } from "../../shared/actions/addComment"
10
10
  import { removeChildren } from "../../shared/actions/removeChildren"
11
11
  import { andConditions } from "../../shared/conditions/andConditions"
12
12
  import { hasChildren } from "../../shared/conditions/hasChildren"
13
+ import { getAttributeExpression } from "../../shared/helpers/getAttributeExpression"
13
14
  import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
14
15
  import { getImportName } from "../../shared/transformFactories/helpers/manageImports"
15
16
  import { transformableSelect } from "../transformableSelect"
@@ -126,6 +127,18 @@ function convertOptionToObjectExpression(
126
127
  )
127
128
  }
128
129
 
130
+ // Preserve data-* attributes on the option object
131
+ const attrs = optionElement.openingElement.attributes || []
132
+ for (const attr of attrs) {
133
+ if (attr.type !== "JSXAttribute") continue
134
+ const name = attr.name.name as string
135
+ if (!name.startsWith("data-")) continue
136
+ const expr = getAttributeExpression(optionElement, name)
137
+ if (expr) {
138
+ properties.push(j.objectProperty(j.stringLiteral(name), expr))
139
+ }
140
+ }
141
+
129
142
  return j.objectExpression(properties)
130
143
  }
131
144
 
@@ -42,6 +42,29 @@ function Test() {
42
42
  expect(result).not.toContain("</Select>")
43
43
  })
44
44
 
45
+ it("should preserve data-* attributes on options", () => {
46
+ const input = `
47
+ import { Select } from "@planningcenter/tapestry-react"
48
+
49
+ function Test() {
50
+ return (
51
+ <Select>
52
+ {items.map(item => (
53
+ <Select.Option key={item.id} value={item.id} data-id={item.id}>
54
+ {item.name}
55
+ </Select.Option>
56
+ ))}
57
+ </Select>
58
+ )
59
+ }
60
+ `.trim()
61
+
62
+ const result = applyTransform(input)
63
+ expect(result).toContain('"data-id": item.id')
64
+ expect(result).toContain("label: item.name")
65
+ expect(result).toContain("value: item.id")
66
+ })
67
+
45
68
  it("should handle nested property access in children", () => {
46
69
  const input = `
47
70
  import { Select } from "@planningcenter/tapestry-react"
@@ -12,6 +12,7 @@ import {
12
12
  import { removeChildren } from "../../shared/actions/removeChildren"
13
13
  import { andConditions } from "../../shared/conditions/andConditions"
14
14
  import { hasChildren } from "../../shared/conditions/hasChildren"
15
+ import { getAttributeExpression } from "../../shared/helpers/getAttributeExpression"
15
16
  import { attributeTransformFactory } from "../../shared/transformFactories/attributeTransformFactory"
16
17
  import { getImportName } from "../../shared/transformFactories/helpers/manageImports"
17
18
  import { transformableSelect } from "../transformableSelect"
@@ -31,31 +32,6 @@ function isSelectOption(node: JSXElement, localSelectName: string): boolean {
31
32
  )
32
33
  }
33
34
 
34
- /**
35
- * Gets the expression AST node for a JSX attribute value.
36
- * Returns the expression node for dynamic values, or a StringLiteral for static ones.
37
- */
38
- function getAttributeExpression(
39
- element: JSXElement,
40
- attrName: string
41
- ): Expression | null {
42
- const attrs = element.openingElement.attributes || []
43
- for (const attr of attrs) {
44
- if (attr.type !== "JSXAttribute") continue
45
- if (attr.name.name !== attrName) continue
46
-
47
- if (!attr.value) return null // boolean shorthand has no expression
48
- if (attr.value.type === "StringLiteral") return attr.value
49
- if (attr.value.type === "JSXExpressionContainer") {
50
- const expr = attr.value.expression
51
- if (expr.type === "JSXEmptyExpression") return null
52
- return expr as Expression
53
- }
54
- return null
55
- }
56
- return null
57
- }
58
-
59
35
  /**
60
36
  * Gets the disabled expression if present and not a simple boolean shorthand true.
61
37
  * Returns the expression node, or true for shorthand, or null if not present.
@@ -282,6 +258,18 @@ const transform: Transform = attributeTransformFactory({
282
258
  properties.push(j.objectProperty(j.identifier("disabled"), disabledExpr))
283
259
  }
284
260
 
261
+ // Preserve data-* attributes on the option object
262
+ const attrs = optionElement.openingElement.attributes || []
263
+ for (const attr of attrs) {
264
+ if (attr.type !== "JSXAttribute") continue
265
+ const name = attr.name.name as string
266
+ if (!name.startsWith("data-")) continue
267
+ const expr = getAttributeExpression(optionElement, name)
268
+ if (expr) {
269
+ properties.push(j.objectProperty(j.stringLiteral(name), expr))
270
+ }
271
+ }
272
+
285
273
  // Build new .map() callback that returns an object
286
274
  const objectReturn = j.objectExpression(properties)
287
275
  const newCallback = j.arrowFunctionExpression(
@@ -0,0 +1,26 @@
1
+ import { Expression, JSXElement } from "jscodeshift"
2
+
3
+ /**
4
+ * Gets the expression AST node for a JSX attribute value.
5
+ * Returns the expression node for dynamic values, or a StringLiteral for static ones.
6
+ */
7
+ export function getAttributeExpression(
8
+ element: JSXElement,
9
+ attrName: string
10
+ ): Expression | null {
11
+ const attrs = element.openingElement.attributes || []
12
+ for (const attr of attrs) {
13
+ if (attr.type !== "JSXAttribute") continue
14
+ if (attr.name.name !== attrName) continue
15
+
16
+ if (!attr.value) return null // boolean shorthand has no expression
17
+ if (attr.value.type === "StringLiteral") return attr.value
18
+ if (attr.value.type === "JSXExpressionContainer") {
19
+ const expr = attr.value.expression
20
+ if (expr.type === "JSXEmptyExpression") return null
21
+ return expr as Expression
22
+ }
23
+ return null
24
+ }
25
+ return null
26
+ }