@planningcenter/tapestry-migration-cli 2.3.0-rc.4 → 2.3.0-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.
@@ -0,0 +1,300 @@
1
+ import jscodeshift, {
2
+ BooleanLiteral,
3
+ ConditionalExpression,
4
+ Identifier,
5
+ JSXAttribute,
6
+ JSXElement,
7
+ JSXExpressionContainer,
8
+ JSXIdentifier,
9
+ StringLiteral,
10
+ } from "jscodeshift"
11
+ import { describe, expect, it } from "vitest"
12
+
13
+ import { addAttribute, Conditional } from "./addAttribute"
14
+
15
+ const j = jscodeshift.withParser("tsx")
16
+
17
+ function createElementFromCode(code: string): JSXElement {
18
+ const source = j(`<div>${code}</div>`)
19
+ return source.find(j.JSXElement).at(0).get().value.children?.[0] as JSXElement
20
+ }
21
+
22
+ function getAttributeFromElement(
23
+ element: JSXElement,
24
+ name: string
25
+ ): JSXAttribute | null {
26
+ const attributes = element.openingElement.attributes || []
27
+ return (
28
+ (attributes.find(
29
+ (attr) =>
30
+ attr.type === "JSXAttribute" &&
31
+ (attr.name as JSXIdentifier)?.name === name
32
+ ) as JSXAttribute) || null
33
+ )
34
+ }
35
+
36
+ describe("addAttribute", () => {
37
+ describe("string values", () => {
38
+ it("should add string attribute to element", () => {
39
+ const element = createElementFromCode("<Button>Save</Button>")
40
+
41
+ addAttribute({ element, j, name: "kind", value: "primary" })
42
+
43
+ const kindAttr = getAttributeFromElement(element, "kind")
44
+ expect(kindAttr).not.toBeNull()
45
+ expect(kindAttr?.value?.type).toBe("StringLiteral")
46
+ expect((kindAttr?.value as StringLiteral)?.value).toBe("primary")
47
+ })
48
+
49
+ it("should add multiple string attributes", () => {
50
+ const element = createElementFromCode("<Button>Save</Button>")
51
+
52
+ addAttribute({ element, j, name: "kind", value: "primary" })
53
+ addAttribute({ element, j, name: "size", value: "large" })
54
+
55
+ const kindAttr = getAttributeFromElement(element, "kind")
56
+ const sizeAttr = getAttributeFromElement(element, "size")
57
+
58
+ expect(kindAttr).not.toBeNull()
59
+ expect(sizeAttr).not.toBeNull()
60
+ expect((kindAttr?.value as StringLiteral)?.value).toBe("primary")
61
+ expect((sizeAttr?.value as StringLiteral)?.value).toBe("large")
62
+ })
63
+
64
+ it("should handle empty string values", () => {
65
+ const element = createElementFromCode("<Button>Save</Button>")
66
+
67
+ addAttribute({ element, j, name: "title", value: "" })
68
+
69
+ const titleAttr = getAttributeFromElement(element, "title")
70
+ expect(titleAttr).not.toBeNull()
71
+ expect(titleAttr?.value?.type).toBe("StringLiteral")
72
+ expect((titleAttr?.value as StringLiteral)?.value).toBe("")
73
+ })
74
+
75
+ it("should handle special characters in string values", () => {
76
+ const element = createElementFromCode("<Button>Save</Button>")
77
+
78
+ addAttribute({ element, j, name: "aria-label", value: "Save & Continue" })
79
+
80
+ const ariaAttr = getAttributeFromElement(element, "aria-label")
81
+ expect(ariaAttr).not.toBeNull()
82
+ expect((ariaAttr?.value as StringLiteral)?.value).toBe("Save & Continue")
83
+ })
84
+ })
85
+
86
+ describe("boolean values", () => {
87
+ it("should add boolean true attribute", () => {
88
+ const element = createElementFromCode("<Button>Save</Button>")
89
+
90
+ addAttribute({ element, j, name: "disabled", value: true })
91
+
92
+ const disabledAttr = getAttributeFromElement(element, "disabled")
93
+ expect(disabledAttr).not.toBeNull()
94
+ expect(disabledAttr?.value?.type).toBe("JSXExpressionContainer")
95
+
96
+ const container = disabledAttr?.value as JSXExpressionContainer
97
+ expect(container.expression.type).toBe("BooleanLiteral")
98
+ expect((container.expression as BooleanLiteral).value).toBe(true)
99
+ })
100
+
101
+ it("should add boolean false attribute", () => {
102
+ const element = createElementFromCode("<Button>Save</Button>")
103
+
104
+ addAttribute({ element, j, name: "loading", value: false })
105
+
106
+ const loadingAttr = getAttributeFromElement(element, "loading")
107
+ expect(loadingAttr).not.toBeNull()
108
+ expect(loadingAttr?.value?.type).toBe("JSXExpressionContainer")
109
+
110
+ const container = loadingAttr?.value as JSXExpressionContainer
111
+ expect(container.expression.type).toBe("BooleanLiteral")
112
+ expect((container.expression as BooleanLiteral).value).toBe(false)
113
+ })
114
+ })
115
+
116
+ describe("null values", () => {
117
+ it("should not add attribute when value is null", () => {
118
+ const element = createElementFromCode("<Button>Save</Button>")
119
+ const initialAttrCount = (element.openingElement.attributes || []).length
120
+
121
+ addAttribute({ element, j, name: "kind", value: null })
122
+
123
+ const finalAttrCount = (element.openingElement.attributes || []).length
124
+ expect(finalAttrCount).toBe(initialAttrCount)
125
+
126
+ const kindAttr = getAttributeFromElement(element, "kind")
127
+ expect(kindAttr).toBeNull()
128
+ })
129
+ })
130
+
131
+ describe("conditional values", () => {
132
+ it("should add conditional attribute with test, consequent, and alternate", () => {
133
+ const element = createElementFromCode("<Button>Save</Button>")
134
+ const conditionalValue: Conditional = {
135
+ alternate: "secondary",
136
+ consequent: "primary",
137
+ test: "isPrimary",
138
+ }
139
+
140
+ addAttribute({ element, j, name: "kind", value: conditionalValue })
141
+
142
+ const kindAttr = getAttributeFromElement(element, "kind")
143
+ expect(kindAttr).not.toBeNull()
144
+ expect(kindAttr?.value?.type).toBe("JSXExpressionContainer")
145
+
146
+ const container = kindAttr?.value as JSXExpressionContainer
147
+ expect(container.expression.type).toBe("ConditionalExpression")
148
+
149
+ const conditional = container.expression as ConditionalExpression
150
+ expect(conditional.test.type).toBe("Identifier")
151
+ expect((conditional.test as Identifier).name).toBe("isPrimary")
152
+ expect(conditional.consequent.type).toBe("StringLiteral")
153
+ expect((conditional.consequent as StringLiteral).value).toBe("primary")
154
+ expect(conditional.alternate.type).toBe("StringLiteral")
155
+ expect((conditional.alternate as StringLiteral).value).toBe("secondary")
156
+ })
157
+
158
+ it("should handle conditional with empty string values", () => {
159
+ const element = createElementFromCode("<Button>Save</Button>")
160
+ const conditionalValue: Conditional = {
161
+ alternate: "",
162
+ consequent: "primary",
163
+ test: "hasTheme",
164
+ }
165
+
166
+ addAttribute({ element, j, name: "kind", value: conditionalValue })
167
+
168
+ const kindAttr = getAttributeFromElement(element, "kind")
169
+ expect(kindAttr).not.toBeNull()
170
+
171
+ const container = kindAttr?.value as JSXExpressionContainer
172
+ const conditional = container.expression as ConditionalExpression
173
+ expect((conditional.consequent as StringLiteral).value).toBe("primary")
174
+ expect((conditional.alternate as StringLiteral).value).toBe("")
175
+ })
176
+
177
+ it("should handle conditional with same consequent and alternate", () => {
178
+ const element = createElementFromCode("<Button>Save</Button>")
179
+ const conditionalValue: Conditional = {
180
+ alternate: "neutral",
181
+ consequent: "neutral",
182
+ test: "condition",
183
+ }
184
+
185
+ addAttribute({ element, j, name: "kind", value: conditionalValue })
186
+
187
+ const kindAttr = getAttributeFromElement(element, "kind")
188
+ expect(kindAttr).not.toBeNull()
189
+
190
+ const container = kindAttr?.value as JSXExpressionContainer
191
+ const conditional = container.expression as ConditionalExpression
192
+ expect((conditional.consequent as StringLiteral).value).toBe("neutral")
193
+ expect((conditional.alternate as StringLiteral).value).toBe("neutral")
194
+ })
195
+
196
+ it("should render conditional attribute correctly in source", () => {
197
+ const source = j("<Button>Save</Button>")
198
+ const element = source.find(j.JSXElement).at(0).get().value as JSXElement
199
+ const conditionalValue: Conditional = {
200
+ alternate: "secondary",
201
+ consequent: "primary",
202
+ test: "isPrimary",
203
+ }
204
+
205
+ addAttribute({ element, j, name: "kind", value: conditionalValue })
206
+
207
+ const result = source.toSource()
208
+ expect(result).toContain('kind={isPrimary ? "primary" : "secondary"}')
209
+ })
210
+
211
+ it("should handle multiple conditional attributes", () => {
212
+ const element = createElementFromCode("<Button>Save</Button>")
213
+ const kindConditional: Conditional = {
214
+ alternate: "secondary",
215
+ consequent: "primary",
216
+ test: "isPrimary",
217
+ }
218
+ const sizeConditional: Conditional = {
219
+ alternate: "small",
220
+ consequent: "large",
221
+ test: "isLarge",
222
+ }
223
+
224
+ addAttribute({ element, j, name: "kind", value: kindConditional })
225
+ addAttribute({ element, j, name: "size", value: sizeConditional })
226
+
227
+ const kindAttr = getAttributeFromElement(element, "kind")
228
+ const sizeAttr = getAttributeFromElement(element, "size")
229
+
230
+ expect(kindAttr).not.toBeNull()
231
+ expect(sizeAttr).not.toBeNull()
232
+ expect(element.openingElement.attributes).toHaveLength(2)
233
+ })
234
+ })
235
+
236
+ describe("adding to elements with existing attributes", () => {
237
+ it("should add attribute to element that already has attributes", () => {
238
+ const element = createElementFromCode(
239
+ '<Button onClick={handleClick} className="btn">Save</Button>'
240
+ )
241
+
242
+ addAttribute({ element, j, name: "kind", value: "primary" })
243
+
244
+ expect(element.openingElement.attributes).toHaveLength(3)
245
+
246
+ const kindAttr = getAttributeFromElement(element, "kind")
247
+ expect(kindAttr).not.toBeNull()
248
+ expect((kindAttr?.value as StringLiteral)?.value).toBe("primary")
249
+
250
+ const onClickAttr = getAttributeFromElement(element, "onClick")
251
+ const classAttr = getAttributeFromElement(element, "className")
252
+ expect(onClickAttr).not.toBeNull()
253
+ expect(classAttr).not.toBeNull()
254
+ })
255
+ })
256
+
257
+ describe("adding to self-closing elements", () => {
258
+ it("should add attribute to self-closing element", () => {
259
+ const element = createElementFromCode("<Button />")
260
+
261
+ addAttribute({ element, j, name: "kind", value: "primary" })
262
+
263
+ const kindAttr = getAttributeFromElement(element, "kind")
264
+ expect(kindAttr).not.toBeNull()
265
+ expect((kindAttr?.value as StringLiteral)?.value).toBe("primary")
266
+ })
267
+ })
268
+
269
+ describe("integration with JSCodeshift", () => {
270
+ it("should render correctly in transformed source", () => {
271
+ const source = j("<Button>Save</Button>")
272
+ const element = source.find(j.JSXElement).at(0).get().value as JSXElement
273
+
274
+ addAttribute({ element, j, name: "kind", value: "primary" })
275
+ addAttribute({ element, j, name: "disabled", value: true })
276
+
277
+ const result = source.toSource()
278
+
279
+ expect(result).toContain('kind="primary"')
280
+ expect(result).toContain("disabled={true}")
281
+ expect(result).toContain("<Button")
282
+ expect(result).toContain(">Save</Button>")
283
+ })
284
+
285
+ it("should handle complex attribute names", () => {
286
+ const element = createElementFromCode("<Button>Save</Button>")
287
+
288
+ addAttribute({ element, j, name: "data-testid", value: "save-button" })
289
+ addAttribute({ element, j, name: "aria-describedby", value: "help-text" })
290
+
291
+ const testIdAttr = getAttributeFromElement(element, "data-testid")
292
+ const ariaAttr = getAttributeFromElement(element, "aria-describedby")
293
+
294
+ expect(testIdAttr).not.toBeNull()
295
+ expect(ariaAttr).not.toBeNull()
296
+ expect((testIdAttr?.value as StringLiteral)?.value).toBe("save-button")
297
+ expect((ariaAttr?.value as StringLiteral)?.value).toBe("help-text")
298
+ })
299
+ })
300
+ })
@@ -0,0 +1,65 @@
1
+ import { JSCodeshift, JSXElement } from "jscodeshift"
2
+
3
+ export interface Conditional {
4
+ alternate: string
5
+ consequent: string
6
+ test: string
7
+ }
8
+
9
+ function formatValue(value: string | boolean, j: JSCodeshift) {
10
+ if (typeof value === "string") {
11
+ return j.stringLiteral(value)
12
+ } else if (typeof value === "boolean") {
13
+ return j.jsxExpressionContainer(j.booleanLiteral(value))
14
+ } else {
15
+ throw new Error(`Unsupported attribute value type: ${typeof value}`)
16
+ }
17
+ }
18
+
19
+ export function addAttribute({
20
+ element,
21
+ name,
22
+ j,
23
+ value,
24
+ }: {
25
+ element: JSXElement
26
+ j: JSCodeshift
27
+ name: string
28
+ value: string | boolean | Conditional | null
29
+ }) {
30
+ if (value === null) return
31
+ const attributes = element.openingElement.attributes || []
32
+
33
+ if (
34
+ typeof value === "object" &&
35
+ "test" in value &&
36
+ "consequent" in value &&
37
+ "alternate" in value
38
+ ) {
39
+ addConditionalAttribute(value, element, j, name)
40
+ return
41
+ }
42
+
43
+ const formattedValue = formatValue(value, j)
44
+ attributes.push(j.jsxAttribute(j.jsxIdentifier(name), formattedValue))
45
+ }
46
+
47
+ function addConditionalAttribute(
48
+ { test, consequent, alternate }: Conditional,
49
+ element: JSXElement,
50
+ j: JSCodeshift,
51
+ targetAttribute: string
52
+ ) {
53
+ const conditional = j.jsxExpressionContainer(
54
+ j.conditionalExpression(
55
+ j.identifier(test),
56
+ j.stringLiteral(consequent),
57
+ j.stringLiteral(alternate)
58
+ )
59
+ )
60
+ const attribute = j.jsxAttribute(
61
+ j.jsxIdentifier(targetAttribute),
62
+ conditional
63
+ )
64
+ element.openingElement.attributes?.push(attribute)
65
+ }
@@ -84,7 +84,7 @@ describe("addComment", () => {
84
84
  .at(0)
85
85
  .get().value as JSXElement
86
86
 
87
- const commentText = "TODO: Update this nested button"
87
+ const commentText = "Update this nested button"
88
88
  addComment({
89
89
  element: buttonElement,
90
90
  j,
@@ -0,0 +1,261 @@
1
+ import jscodeshift, {
2
+ JSXAttribute,
3
+ JSXElement,
4
+ JSXIdentifier,
5
+ } from "jscodeshift"
6
+ import { describe, expect, it } from "vitest"
7
+
8
+ import { getAttributeValue } from "./getAttributeValue"
9
+
10
+ const j = jscodeshift.withParser("tsx")
11
+
12
+ function createElementFromCode(code: string): JSXElement {
13
+ const source = j(`<div>${code}</div>`)
14
+ return source.find(j.JSXElement).at(0).get().value.children?.[0] as JSXElement
15
+ }
16
+
17
+ function getAttributeFromElement(
18
+ element: JSXElement,
19
+ name: string
20
+ ): JSXAttribute | null {
21
+ const attributes = element.openingElement.attributes || []
22
+ return (
23
+ (attributes.find(
24
+ (attr) =>
25
+ attr.type === "JSXAttribute" &&
26
+ (attr.name as JSXIdentifier)?.name === name
27
+ ) as JSXAttribute) || null
28
+ )
29
+ }
30
+
31
+ describe("getAttributeValue", () => {
32
+ describe("string literal values", () => {
33
+ it("should extract string literal value", () => {
34
+ const element = createElementFromCode(
35
+ '<Button kind="primary">Save</Button>'
36
+ )
37
+ const kindAttribute = getAttributeFromElement(element, "kind")
38
+
39
+ const value = getAttributeValue({ attribute: kindAttribute, j })
40
+
41
+ expect(value).toBe("primary")
42
+ })
43
+
44
+ it("should handle empty string literal", () => {
45
+ const element = createElementFromCode('<Button title="">Save</Button>')
46
+ const titleAttribute = getAttributeFromElement(element, "title")
47
+
48
+ const value = getAttributeValue({ attribute: titleAttribute, j })
49
+
50
+ expect(value).toBe("")
51
+ })
52
+
53
+ it("should handle strings with special characters", () => {
54
+ const element = createElementFromCode(
55
+ '<Button aria-label="Save & Continue">Save</Button>'
56
+ )
57
+ const ariaAttribute = getAttributeFromElement(element, "aria-label")
58
+
59
+ const value = getAttributeValue({ attribute: ariaAttribute, j })
60
+
61
+ expect(value).toBe("Save & Continue")
62
+ })
63
+
64
+ it("should handle strings with quotes", () => {
65
+ const element = createElementFromCode(
66
+ "<Button title='Say \"Hello\"'>Save</Button>"
67
+ )
68
+ const titleAttribute = getAttributeFromElement(element, "title")
69
+
70
+ const value = getAttributeValue({ attribute: titleAttribute, j })
71
+
72
+ expect(value).toBe('Say "Hello"')
73
+ })
74
+ })
75
+
76
+ describe("expression container values", () => {
77
+ it("should convert boolean expression to source code", () => {
78
+ const element = createElementFromCode(
79
+ "<Button disabled={true}>Save</Button>"
80
+ )
81
+ const disabledAttribute = getAttributeFromElement(element, "disabled")
82
+
83
+ const value = getAttributeValue({ attribute: disabledAttribute, j })
84
+
85
+ expect(value).toBe("{true}")
86
+ })
87
+
88
+ it("should convert variable expression to source code", () => {
89
+ const element = createElementFromCode(
90
+ "<Button onClick={handleClick}>Save</Button>"
91
+ )
92
+ const onClickAttribute = getAttributeFromElement(element, "onClick")
93
+
94
+ const value = getAttributeValue({ attribute: onClickAttribute, j })
95
+
96
+ expect(value).toBe("{handleClick}")
97
+ })
98
+
99
+ it("should convert complex expression to source code", () => {
100
+ const element = createElementFromCode(
101
+ '<Button style={{ color: "red", fontSize: 16 }}>Save</Button>'
102
+ )
103
+ const styleAttribute = getAttributeFromElement(element, "style")
104
+
105
+ const value = getAttributeValue({ attribute: styleAttribute, j })
106
+
107
+ expect(value).toContain("color")
108
+ expect(value).toContain("red")
109
+ expect(value).toContain("fontSize")
110
+ expect(value).toContain("16")
111
+ })
112
+
113
+ it("should handle function call expressions", () => {
114
+ const element = createElementFromCode(
115
+ '<Button className={getButtonClass("primary")}>Save</Button>'
116
+ )
117
+ const classAttribute = getAttributeFromElement(element, "className")
118
+
119
+ const value = getAttributeValue({ attribute: classAttribute, j })
120
+
121
+ expect(value).toBe('{getButtonClass("primary")}')
122
+ })
123
+
124
+ it("should handle template literal expressions", () => {
125
+ const element = createElementFromCode(
126
+ "<Button className={`btn-${variant}`}>Save</Button>"
127
+ )
128
+ const classAttribute = getAttributeFromElement(element, "className")
129
+
130
+ const value = getAttributeValue({ attribute: classAttribute, j })
131
+
132
+ expect(value).toBe("{`btn-${variant}`}")
133
+ })
134
+
135
+ it("should handle ternary expressions", () => {
136
+ const element = createElementFromCode(
137
+ "<Button disabled={loading ? true : false}>Save</Button>"
138
+ )
139
+ const disabledAttribute = getAttributeFromElement(element, "disabled")
140
+
141
+ const value = getAttributeValue({ attribute: disabledAttribute, j })
142
+
143
+ expect(value).toBe("{loading ? true : false}")
144
+ })
145
+ })
146
+
147
+ describe("null and undefined attributes", () => {
148
+ it("should return null for null attribute", () => {
149
+ const value = getAttributeValue({ attribute: null, j })
150
+
151
+ expect(value).toBeNull()
152
+ })
153
+
154
+ it("should return null for undefined attribute", () => {
155
+ const value = getAttributeValue({ attribute: undefined, j })
156
+
157
+ expect(value).toBeNull()
158
+ })
159
+ })
160
+
161
+ describe("attributes without values", () => {
162
+ it("should return null for boolean attribute without value", () => {
163
+ const element = createElementFromCode("<Button disabled>Save</Button>")
164
+ const disabledAttribute = getAttributeFromElement(element, "disabled")
165
+
166
+ const value = getAttributeValue({ attribute: disabledAttribute, j })
167
+
168
+ expect(value).toBeNull()
169
+ })
170
+
171
+ it("should handle attribute with null value", () => {
172
+ const mockAttribute = {
173
+ name: { name: "test", type: "JSXIdentifier" },
174
+ type: "JSXAttribute",
175
+ value: null,
176
+ } as JSXAttribute
177
+
178
+ const value = getAttributeValue({ attribute: mockAttribute, j })
179
+
180
+ expect(value).toBeNull()
181
+ })
182
+ })
183
+
184
+ describe("edge cases", () => {
185
+ it("should handle numeric expressions", () => {
186
+ const element = createElementFromCode(
187
+ "<Button tabIndex={0}>Save</Button>"
188
+ )
189
+ const tabIndexAttribute = getAttributeFromElement(element, "tabIndex")
190
+
191
+ const value = getAttributeValue({ attribute: tabIndexAttribute, j })
192
+
193
+ expect(value).toBe("{0}")
194
+ })
195
+
196
+ it("should handle array expressions", () => {
197
+ const element = createElementFromCode(
198
+ "<Button data={[1, 2, 3]}>Save</Button>"
199
+ )
200
+ const dataAttribute = getAttributeFromElement(element, "data")
201
+
202
+ const value = getAttributeValue({ attribute: dataAttribute, j })
203
+
204
+ expect(value).toBe("{[1, 2, 3]}")
205
+ })
206
+
207
+ it("should handle nested object expressions", () => {
208
+ const element = createElementFromCode(
209
+ '<Button config={{ theme: { primary: "blue" } }}>Save</Button>'
210
+ )
211
+ const configAttribute = getAttributeFromElement(element, "config")
212
+
213
+ const value = getAttributeValue({ attribute: configAttribute, j })
214
+
215
+ expect(value).toContain("theme")
216
+ expect(value).toContain("primary")
217
+ expect(value).toContain("blue")
218
+ })
219
+
220
+ it("should handle JSX element expressions", () => {
221
+ const element = createElementFromCode(
222
+ '<Button icon={<Icon name="save" />}>Save</Button>'
223
+ )
224
+ const iconAttribute = getAttributeFromElement(element, "icon")
225
+
226
+ const value = getAttributeValue({ attribute: iconAttribute, j })
227
+
228
+ expect(value).toBe('{<Icon name="save" />}')
229
+ })
230
+ })
231
+
232
+ describe("integration scenarios", () => {
233
+ it("should handle mixed attribute types on same element", () => {
234
+ const element = createElementFromCode(
235
+ '<Button kind="primary" disabled={true} onClick={handleClick} data-testid="save-btn">Save</Button>'
236
+ )
237
+
238
+ const kindValue = getAttributeValue({
239
+ attribute: getAttributeFromElement(element, "kind"),
240
+ j,
241
+ })
242
+ const disabledValue = getAttributeValue({
243
+ attribute: getAttributeFromElement(element, "disabled"),
244
+ j,
245
+ })
246
+ const onClickValue = getAttributeValue({
247
+ attribute: getAttributeFromElement(element, "onClick"),
248
+ j,
249
+ })
250
+ const testIdValue = getAttributeValue({
251
+ attribute: getAttributeFromElement(element, "data-testid"),
252
+ j,
253
+ })
254
+
255
+ expect(kindValue).toBe("primary")
256
+ expect(disabledValue).toBe("{true}")
257
+ expect(onClickValue).toBe("{handleClick}")
258
+ expect(testIdValue).toBe("save-btn")
259
+ })
260
+ })
261
+ })
@@ -0,0 +1,15 @@
1
+ import { JSCodeshift, JSXAttribute } from "jscodeshift"
2
+
3
+ export function getAttributeValue({
4
+ attribute,
5
+ j,
6
+ }: {
7
+ attribute: JSXAttribute | null | undefined
8
+ j: JSCodeshift
9
+ }) {
10
+ return attribute && attribute.value
11
+ ? attribute?.value?.type === "StringLiteral"
12
+ ? attribute.value.value
13
+ : j(attribute.value).toSource()
14
+ : null
15
+ }