@wise/wds-codemods 0.0.1-experimental-cbae00f → 0.0.1-experimental-926a862

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.
Files changed (60) hide show
  1. package/dist/index.js +133 -135
  2. package/dist/index.js.map +1 -1
  3. package/dist/reportManualReview-DQ00-OKx.js +50 -0
  4. package/dist/reportManualReview-DQ00-OKx.js.map +1 -0
  5. package/dist/transforms/button.js +493 -566
  6. package/dist/transforms/button.js.map +1 -1
  7. package/package.json +19 -14
  8. package/.changeset/better-impalas-drop.md +0 -5
  9. package/.changeset/config.json +0 -13
  10. package/.changeset/quick-mails-joke.md +0 -128
  11. package/.github/CODEOWNERS +0 -1
  12. package/.github/actions/bootstrap/action.yml +0 -49
  13. package/.github/actions/commitlint/action.yml +0 -27
  14. package/.github/actions/test/action.yml +0 -23
  15. package/.github/workflows/cd-cd.yml +0 -127
  16. package/.github/workflows/renovate.yml +0 -16
  17. package/.husky/commit-msg +0 -1
  18. package/.husky/pre-commit +0 -1
  19. package/.nvmrc +0 -1
  20. package/.prettierignore +0 -1
  21. package/.prettierrc.js +0 -5
  22. package/DEVELOPER.md +0 -783
  23. package/babel.config.js +0 -28
  24. package/commitlint.config.js +0 -3
  25. package/dist/index.d.ts +0 -1
  26. package/dist/transforms/button.d.ts +0 -16
  27. package/eslint.config.js +0 -15
  28. package/jest.config.js +0 -9
  29. package/mkdocs.yml +0 -4
  30. package/renovate.json +0 -9
  31. package/scripts/build.sh +0 -10
  32. package/src/__tests__/runCodemod.test.ts +0 -96
  33. package/src/index.ts +0 -4
  34. package/src/runCodemod.ts +0 -88
  35. package/src/transforms/button/__tests__/button.test.tsx +0 -153
  36. package/src/transforms/button/button.ts +0 -418
  37. package/src/transforms/helpers/__tests__/createTestTransform.test.ts +0 -27
  38. package/src/transforms/helpers/__tests__/hasImport.test.ts +0 -52
  39. package/src/transforms/helpers/__tests__/iconUtils.test.ts +0 -207
  40. package/src/transforms/helpers/__tests__/jsxElementUtils.test.ts +0 -130
  41. package/src/transforms/helpers/__tests__/jsxReportingUtils.test.ts +0 -265
  42. package/src/transforms/helpers/createTestTransform.ts +0 -18
  43. package/src/transforms/helpers/hasImport.ts +0 -60
  44. package/src/transforms/helpers/iconUtils.ts +0 -87
  45. package/src/transforms/helpers/index.ts +0 -5
  46. package/src/transforms/helpers/jsxElementUtils.ts +0 -67
  47. package/src/transforms/helpers/jsxReportingUtils.ts +0 -224
  48. package/src/utils/__tests__/getOptions.test.ts +0 -170
  49. package/src/utils/__tests__/handleError.test.ts +0 -18
  50. package/src/utils/__tests__/loadTransformModules.test.ts +0 -51
  51. package/src/utils/__tests__/reportManualReview.test.ts +0 -42
  52. package/src/utils/getOptions.ts +0 -63
  53. package/src/utils/handleError.ts +0 -6
  54. package/src/utils/index.ts +0 -4
  55. package/src/utils/loadTransformModules.ts +0 -28
  56. package/src/utils/reportManualReview.ts +0 -17
  57. package/test-button.tsx +0 -230
  58. package/test-file.js +0 -2
  59. package/tsconfig.json +0 -14
  60. package/tsup.config.js +0 -13
@@ -1,418 +0,0 @@
1
- import type { API, FileInfo, JSCodeshift, JSXIdentifier, Options } from 'jscodeshift';
2
-
3
- import reportManualReview from '../../utils/reportManualReview';
4
- import hasImport from '../helpers/hasImport';
5
- import processIconChildren from '../helpers/iconUtils';
6
- import {
7
- addAttributesIfMissing,
8
- hasAttributeOnElement,
9
- setNameIfJSXIdentifier,
10
- } from '../helpers/jsxElementUtils';
11
- import { createReporter } from '../helpers/jsxReportingUtils';
12
-
13
- export const parser = 'tsx';
14
-
15
- interface LegacyProps {
16
- priority?: string;
17
- size?: string;
18
- type?: string;
19
- htmlType?: string;
20
- sentiment?: string;
21
- [key: string]: unknown;
22
- }
23
-
24
- const priorityMapping: Record<string, Record<string, string>> = {
25
- accent: {
26
- primary: 'primary',
27
- secondary: 'secondary-neutral',
28
- tertiary: 'tertiary',
29
- },
30
- positive: {
31
- primary: 'primary',
32
- secondary: 'secondary-neutral',
33
- tertiary: 'secondary-neutral',
34
- },
35
- negative: {
36
- primary: 'primary',
37
- secondary: 'secondary',
38
- tertiary: 'secondary',
39
- },
40
- };
41
-
42
- const sizeMap: Record<string, string> = {
43
- EXTRA_SMALL: 'xs',
44
- SMALL: 'sm',
45
- MEDIUM: 'md',
46
- LARGE: 'lg',
47
- EXTRA_LARGE: 'xl',
48
- xs: 'sm',
49
- sm: 'sm',
50
- md: 'md',
51
- lg: 'lg',
52
- xl: 'xl',
53
- };
54
-
55
- const resolveSize = (size?: string): string | undefined => {
56
- if (!size) return size;
57
- const match = /^Size\.(EXTRA_SMALL|SMALL|MEDIUM|LARGE|EXTRA_LARGE)$/u.exec(size);
58
- if (match) {
59
- return sizeMap[match[1]];
60
- }
61
- return sizeMap[size] || size;
62
- };
63
-
64
- const resolvePriority = (type?: string, priority?: string): string | undefined => {
65
- if (type && priority) {
66
- return priorityMapping[type]?.[priority] || priority;
67
- }
68
- return priority;
69
- };
70
-
71
- const resolveType = (type?: string, htmlType?: string): string | null => {
72
- if (htmlType) {
73
- return htmlType;
74
- }
75
-
76
- const legacyButtonTypes = [
77
- 'accent',
78
- 'negative',
79
- 'positive',
80
- 'primary',
81
- 'pay',
82
- 'secondary',
83
- 'danger',
84
- 'link',
85
- ];
86
- return type && legacyButtonTypes.includes(type) ? type : null;
87
- };
88
-
89
- const convertEnumValue = (value?: string): string | undefined => {
90
- if (!value) return value;
91
- const strippedValue = value.replace(/^['"]|['"]$/gu, '');
92
- const enumMapping: Record<string, string> = {
93
- 'Priority.SECONDARY': 'secondary',
94
- 'Priority.PRIMARY': 'primary',
95
- 'Priority.TERTIARY': 'tertiary',
96
- 'ControlType.NEGATIVE': 'negative',
97
- 'ControlType.POSITIVE': 'positive',
98
- 'ControlType.ACCENT': 'accent',
99
- };
100
- return enumMapping[strippedValue] || strippedValue;
101
- };
102
-
103
- /**
104
- * This transform function modifies the Button and ActionButton components from the @transferwise/components library.
105
- * It updates the ActionButton component to use the Button component with specific attributes and mappings.
106
- * It also processes icon children and removes legacy props.
107
- *
108
- * @param {FileInfo} file - The file information object.
109
- * @param {API} api - The API object for jscodeshift.
110
- * @param {Options} options - The options object for jscodeshift.
111
- * @returns {string} - The transformed source code.
112
- */
113
- const transformer = (file: FileInfo, api: API, options: Options) => {
114
- const j: JSCodeshift = api.jscodeshift;
115
- const root = j(file.source);
116
- const manualReviewIssues: string[] = [];
117
-
118
- // Create reporter instance
119
- const reporter = createReporter(j, manualReviewIssues);
120
-
121
- const { exists: hasButtonImport } = hasImport(root, '@transferwise/components', 'Button', j);
122
- const { exists: hasActionButtonImport, remove: removeActionButtonImport } = hasImport(
123
- root,
124
- '@transferwise/components',
125
- 'ActionButton',
126
- j,
127
- );
128
-
129
- const iconImports = new Set<string>();
130
- root.find(j.ImportDeclaration, { source: { value: '@transferwise/icons' } }).forEach((path) => {
131
- path.node.specifiers?.forEach((specifier) => {
132
- if (
133
- (specifier.type === 'ImportDefaultSpecifier' || specifier.type === 'ImportSpecifier') &&
134
- specifier.local
135
- ) {
136
- const localName = (specifier.local as { name: string }).name;
137
- iconImports.add(localName);
138
- }
139
- });
140
- });
141
-
142
- if (hasActionButtonImport) {
143
- root.findJSXElements('ActionButton').forEach((path) => {
144
- const { openingElement, closingElement } = path.node;
145
-
146
- openingElement.name = setNameIfJSXIdentifier(openingElement.name, 'Button')!;
147
- if (closingElement) {
148
- closingElement.name = setNameIfJSXIdentifier(closingElement.name, 'Button')!;
149
- }
150
-
151
- addAttributesIfMissing(j, openingElement, [
152
- { attribute: j.jsxAttribute(j.jsxIdentifier('v2')), name: 'v2' },
153
- { attribute: j.jsxAttribute(j.jsxIdentifier('size'), j.literal('sm')), name: 'size' },
154
- ]);
155
-
156
- processIconChildren(j, path.node.children, iconImports, openingElement);
157
-
158
- if ((openingElement.attributes ?? []).some((attr) => attr.type === 'JSXSpreadAttribute')) {
159
- reporter.reportSpreadProps(path);
160
- }
161
-
162
- const legacyPropNames = ['priority', 'text'];
163
- const legacyProps: LegacyProps = {};
164
-
165
- openingElement.attributes?.forEach((attr) => {
166
- if (attr.type === 'JSXAttribute' && attr.name && attr.name.type === 'JSXIdentifier') {
167
- const { name } = attr.name;
168
- if (legacyPropNames.includes(name)) {
169
- if (attr.value) {
170
- if (attr.value.type === 'StringLiteral') {
171
- legacyProps[name] = attr.value.value;
172
- } else if (attr.value.type === 'JSXExpressionContainer') {
173
- reporter.reportAttribute(attr, path);
174
- }
175
- }
176
- }
177
- }
178
- });
179
-
180
- const hasTextProp = openingElement.attributes?.some(
181
- (attr) =>
182
- attr.type === 'JSXAttribute' &&
183
- attr.name.type === 'JSXIdentifier' &&
184
- attr.name.name === 'text',
185
- );
186
- const hasChildren = path.node.children?.some(
187
- (child) =>
188
- (child.type === 'JSXText' && child.value.trim() !== '') ||
189
- child.type === 'JSXElement' ||
190
- child.type === 'JSXExpressionContainer',
191
- );
192
-
193
- if (hasTextProp && hasChildren) {
194
- reporter.reportPropWithChildren(path, 'text');
195
- }
196
-
197
- (path.node.children || []).forEach((child) => {
198
- if (child.type === 'JSXExpressionContainer') {
199
- const expr = child.expression;
200
- if (
201
- expr.type === 'ConditionalExpression' ||
202
- expr.type === 'CallExpression' ||
203
- expr.type === 'Identifier' ||
204
- expr.type === 'MemberExpression'
205
- ) {
206
- reporter.reportAmbiguousChildren(path, 'icon');
207
- }
208
- }
209
- });
210
- });
211
-
212
- removeActionButtonImport();
213
- }
214
-
215
- if (hasButtonImport) {
216
- root.findJSXElements('Button').forEach((path) => {
217
- const { openingElement } = path.node;
218
-
219
- if (hasAttributeOnElement(openingElement, 'v2')) return;
220
-
221
- addAttributesIfMissing(j, openingElement, [
222
- { attribute: j.jsxAttribute(j.jsxIdentifier('v2')), name: 'v2' },
223
- ]);
224
- processIconChildren(j, path.node.children, iconImports, openingElement);
225
-
226
- const legacyProps: LegacyProps = {};
227
- const legacyPropNames = ['priority', 'size', 'type', 'htmlType', 'sentiment'];
228
-
229
- openingElement.attributes?.forEach((attr) => {
230
- if (attr.type === 'JSXAttribute' && attr.name && attr.name.type === 'JSXIdentifier') {
231
- const { name } = attr.name;
232
- if (legacyPropNames.includes(name)) {
233
- if (attr.value) {
234
- if (attr.value.type === 'StringLiteral') {
235
- legacyProps[name] = attr.value.value;
236
- } else if (attr.value.type === 'JSXExpressionContainer') {
237
- legacyProps[name] = convertEnumValue(String(j(attr.value.expression).toSource()));
238
- }
239
- } else {
240
- legacyProps[name] = undefined;
241
- }
242
- }
243
- }
244
- });
245
-
246
- if (openingElement.attributes) {
247
- openingElement.attributes = openingElement.attributes.filter(
248
- (attr) =>
249
- !(
250
- attr.type === 'JSXAttribute' &&
251
- attr.name &&
252
- legacyPropNames.includes((attr.name as JSXIdentifier).name)
253
- ),
254
- );
255
- }
256
-
257
- if ('size' in legacyProps) {
258
- const rawValue = legacyProps.size;
259
- const resolved = resolveSize(rawValue);
260
- const supportedSizes = ['xs', 'sm', 'md', 'lg', 'xl'];
261
-
262
- if (
263
- typeof rawValue === 'string' &&
264
- typeof resolved === 'string' &&
265
- supportedSizes.includes(resolved)
266
- ) {
267
- openingElement.attributes?.push(
268
- j.jsxAttribute(j.jsxIdentifier('size'), j.literal(resolved)),
269
- );
270
- } else if (typeof rawValue === 'string') {
271
- reporter.reportUnsupportedValue(path, 'size', rawValue);
272
- } else if (rawValue !== undefined) {
273
- reporter.reportAmbiguousExpression(path, 'size');
274
- }
275
- }
276
-
277
- if ('priority' in legacyProps) {
278
- const rawValue = legacyProps.priority;
279
- const converted = convertEnumValue(rawValue);
280
- const mapped = resolvePriority(legacyProps.type, converted);
281
- const supportedPriorities = ['primary', 'secondary', 'tertiary', 'secondary-neutral'];
282
-
283
- if (
284
- typeof rawValue === 'string' &&
285
- typeof mapped === 'string' &&
286
- supportedPriorities.includes(mapped)
287
- ) {
288
- openingElement.attributes?.push(
289
- j.jsxAttribute(j.jsxIdentifier('priority'), j.literal(mapped)),
290
- );
291
- } else if (typeof rawValue === 'string') {
292
- reporter.reportUnsupportedValue(path, 'priority', rawValue);
293
- } else if (rawValue !== undefined) {
294
- reporter.reportAmbiguousExpression(path, 'priority');
295
- }
296
- }
297
-
298
- if ('type' in legacyProps || 'htmlType' in legacyProps) {
299
- const rawType = legacyProps.type;
300
- const rawHtmlType = legacyProps.htmlType;
301
-
302
- const resolvedType =
303
- typeof rawType === 'string'
304
- ? rawType
305
- : rawType && typeof rawType === 'object'
306
- ? convertEnumValue(j(rawType).toSource())
307
- : undefined;
308
-
309
- const resolved = resolveType(resolvedType, rawHtmlType);
310
-
311
- const supportedTypes = [
312
- 'accent',
313
- 'negative',
314
- 'positive',
315
- 'primary',
316
- 'pay',
317
- 'secondary',
318
- 'danger',
319
- 'link',
320
- 'submit',
321
- 'button',
322
- 'reset',
323
- ];
324
-
325
- if (typeof resolved === 'string' && supportedTypes.includes(resolved)) {
326
- openingElement.attributes?.push(
327
- j.jsxAttribute(j.jsxIdentifier('type'), j.literal(resolved)),
328
- );
329
-
330
- if (resolved === 'negative') {
331
- openingElement.attributes?.push(
332
- j.jsxAttribute(j.jsxIdentifier('sentiment'), j.literal('negative')),
333
- );
334
- }
335
- } else if (typeof rawType === 'string' || typeof rawHtmlType === 'string') {
336
- reporter.reportUnsupportedValue(path, 'type', rawType ?? rawHtmlType ?? '');
337
- } else if (rawType !== undefined || rawHtmlType !== undefined) {
338
- reporter.reportAmbiguousExpression(path, 'type');
339
- }
340
- }
341
-
342
- if ('sentiment' in legacyProps) {
343
- const rawValue = legacyProps.sentiment;
344
- if (rawValue === 'negative') {
345
- openingElement.attributes?.push(
346
- j.jsxAttribute(j.jsxIdentifier('sentiment'), j.literal('negative')),
347
- );
348
- } else if (typeof rawValue === 'string') {
349
- reporter.reportUnsupportedValue(path, 'sentiment', rawValue);
350
- } else if (rawValue !== undefined) {
351
- reporter.reportAmbiguousExpression(path, 'sentiment');
352
- }
353
- }
354
-
355
- let asIndex = -1;
356
- let asValue: string | null = null;
357
- let hrefExists = false;
358
- let asAmbiguous = false;
359
- let hrefAmbiguous = false;
360
-
361
- openingElement.attributes?.forEach((attr, index) => {
362
- if (attr.type === 'JSXAttribute' && attr.name) {
363
- if (attr.name.name === 'as') {
364
- if (attr.value) {
365
- if (attr.value.type === 'StringLiteral') {
366
- asValue = attr.value.value;
367
- } else if (attr.value.type === 'JSXExpressionContainer') {
368
- asAmbiguous = true;
369
- reporter.reportAttribute(attr, path);
370
- }
371
- }
372
- asIndex = index;
373
- }
374
-
375
- if (attr.name.name === 'href') {
376
- hrefExists = true;
377
- if (attr.value && attr.value.type !== 'StringLiteral') {
378
- hrefAmbiguous = true;
379
- reporter.reportAttribute(attr, path);
380
- }
381
- }
382
- }
383
- });
384
-
385
- if (asValue && asValue !== 'a') {
386
- reporter.reportUnsupportedValue(path, 'as', asValue);
387
- }
388
-
389
- if (asValue === 'a') {
390
- if (asIndex !== -1) {
391
- openingElement.attributes = openingElement.attributes?.filter(
392
- (_, idx) => idx !== asIndex,
393
- );
394
- }
395
- if (!hrefExists) {
396
- openingElement.attributes = [
397
- ...(openingElement.attributes ?? []),
398
- j.jsxAttribute(j.jsxIdentifier('href'), j.literal('#')),
399
- ];
400
- }
401
- }
402
-
403
- if ((openingElement.attributes ?? []).some((attr) => attr.type === 'JSXSpreadAttribute')) {
404
- reporter.reportSpreadProps(path);
405
- }
406
- });
407
- }
408
-
409
- if (manualReviewIssues.length > 0) {
410
- manualReviewIssues.forEach(async (issue) => {
411
- await reportManualReview(file.path, issue);
412
- });
413
- }
414
-
415
- return root.toSource();
416
- };
417
-
418
- export default transformer;
@@ -1,27 +0,0 @@
1
- import { applyTransform } from 'jscodeshift/src/testUtils';
2
-
3
- import createTestTransform from '../createTestTransform';
4
-
5
- jest.mock('jscodeshift/src/testUtils', () => ({
6
- applyTransform: jest.fn(),
7
- }));
8
-
9
- describe('createTestTransform', () => {
10
- it('should call applyTransform with the correct arguments', () => {
11
- const mockTransformer = jest.fn();
12
- const mockInput = { path: 'test-file.ts', source: 'const a = 1;' };
13
- const mockOptions = {};
14
- const mockTestOptions = { parser: 'tsx' };
15
-
16
- const transform = createTestTransform(mockTransformer);
17
-
18
- transform(mockInput);
19
-
20
- expect(applyTransform).toHaveBeenCalledWith(
21
- mockTransformer,
22
- mockOptions,
23
- mockInput,
24
- mockTestOptions,
25
- );
26
- });
27
- });
@@ -1,52 +0,0 @@
1
- import jscodeshift from 'jscodeshift';
2
-
3
- import hasImport from '../hasImport';
4
-
5
- describe('hasImport', () => {
6
- function getRoot(source: string) {
7
- return jscodeshift(source);
8
- }
9
-
10
- it('returns true if the named import exists from the given module', () => {
11
- const source = `import { Button, Card } from '@transferwise/components';`;
12
- const root = getRoot(source);
13
- expect(hasImport(root, '@transferwise/components', 'Button', jscodeshift).exists).toBe(true);
14
- expect(hasImport(root, '@transferwise/components', 'Card', jscodeshift).exists).toBe(true);
15
- });
16
-
17
- it('returns false if the named import does not exist from the given module', () => {
18
- const source = `import { Button } from '@transferwise/components';`;
19
- const root = getRoot(source);
20
- expect(hasImport(root, '@transferwise/components', 'Card', jscodeshift).exists).toBe(false);
21
- });
22
-
23
- it('returns false if the import is from a different module', () => {
24
- const source = `import { Button } from 'other-lib';`;
25
- const root = getRoot(source);
26
- expect(hasImport(root, '@transferwise/components', 'Button', jscodeshift).exists).toBe(false);
27
- });
28
-
29
- it('returns false if there are no imports at all', () => {
30
- const source = `const a = 1;`;
31
- const root = getRoot(source);
32
- expect(hasImport(root, '@transferwise/components', 'Button', jscodeshift).exists).toBe(false);
33
- });
34
-
35
- it('returns true for default imports', () => {
36
- const source = `import Button from '@transferwise/components';`;
37
- const root = getRoot(source);
38
- expect(hasImport(root, '@transferwise/components', 'Button', jscodeshift).exists).toBe(true);
39
- });
40
-
41
- it('returns true for aliased imports', () => {
42
- const source = `import { Button as TWButton } from '@transferwise/components';`;
43
- const root = getRoot(source);
44
- expect(hasImport(root, '@transferwise/components', 'Button', jscodeshift).exists).toBe(true);
45
- });
46
-
47
- it('returns false for namespace imports', () => {
48
- const source = `import * as TW from '@transferwise/components';`;
49
- const root = getRoot(source);
50
- expect(hasImport(root, '@transferwise/components', 'Button', jscodeshift).exists).toBe(false);
51
- });
52
- });
@@ -1,207 +0,0 @@
1
- import jscodeshift, {
2
- type JSXAttribute,
3
- type JSXElement,
4
- type JSXExpressionContainer,
5
- type JSXOpeningElement,
6
- } from 'jscodeshift';
7
-
8
- import { processIconChildren } from '..';
9
-
10
- describe('processIconChildren', () => {
11
- const j = jscodeshift;
12
-
13
- function getRoot(source: string) {
14
- return j(source);
15
- }
16
-
17
- function findOpeningElement(root: ReturnType<typeof jscodeshift>) {
18
- return (root.find(j.JSXOpeningElement).at(0).get() as { node: JSXOpeningElement }).node;
19
- }
20
-
21
- it('adds addonStart attribute when icon is first child', () => {
22
- const source = `
23
- <Button>
24
- <IconA />
25
- <OtherComponent />
26
- </Button>
27
- `;
28
- const root = getRoot(source);
29
- const openingElement = findOpeningElement(root);
30
- const iconImports = new Set(['IconA']);
31
- const children = root
32
- .find(j.JSXElement)
33
- .nodes()
34
- .filter(
35
- (node) =>
36
- node.openingElement.name.type === 'JSXIdentifier' &&
37
- node.openingElement.name.name !== 'Button',
38
- );
39
-
40
- const childrenArray: (JSXElement | JSXExpressionContainer | unknown)[] = children;
41
-
42
- processIconChildren(j, childrenArray, iconImports, openingElement);
43
-
44
- expect(openingElement.attributes).toHaveLength(1);
45
- expect((openingElement.attributes![0] as JSXAttribute).name.name).toBe('addonStart');
46
- const attrValue = (openingElement.attributes![0] as JSXAttribute).value;
47
- // Check that there is an attribute named 'addonStart' and it contains 'IconA' somewhere in its value stringified
48
- const addonStartAttr = openingElement.attributes!.find(
49
- (attr) => (attr as JSXAttribute).name.name === 'addonStart',
50
- ) as JSXAttribute | undefined;
51
- expect(addonStartAttr).toBeDefined();
52
- const attrValueStr = addonStartAttr && JSON.stringify(addonStartAttr.value);
53
- expect(attrValueStr).toContain('IconA');
54
- expect(childrenArray).toHaveLength(1);
55
- const childOpeningElement = (childrenArray[0] as JSXElement).openingElement;
56
- expect(
57
- childOpeningElement.name.type === 'JSXIdentifier' ? childOpeningElement.name.name : undefined,
58
- ).toBe('OtherComponent');
59
- });
60
-
61
- it('adds addonEnd attribute when icon is last child', () => {
62
- const source = `
63
- <Button>
64
- <OtherComponent />
65
- <IconB />
66
- </Button>
67
- `;
68
- const root = getRoot(source);
69
- const openingElement = findOpeningElement(root);
70
- const iconImports = new Set(['IconB']);
71
- const children = root
72
- .find(j.JSXElement)
73
- .nodes()
74
- .filter(
75
- (node) =>
76
- node.openingElement.name.type === 'JSXIdentifier' &&
77
- node.openingElement.name.name !== 'Button',
78
- );
79
-
80
- const childrenArray: (JSXElement | JSXExpressionContainer | unknown)[] = children;
81
-
82
- processIconChildren(j, childrenArray, iconImports, openingElement);
83
-
84
- expect(openingElement.attributes).toHaveLength(1);
85
- expect((openingElement.attributes![0] as JSXAttribute).name.name).toBe('addonEnd');
86
- const attrValueB = (openingElement.attributes![0] as JSXAttribute).value;
87
- const addonEndAttr = openingElement.attributes!.find(
88
- (attr) => (attr as JSXAttribute).name.name === 'addonEnd',
89
- ) as JSXAttribute | undefined;
90
- expect(addonEndAttr).toBeDefined();
91
- const attrValueStrB = addonEndAttr && JSON.stringify(addonEndAttr.value);
92
- expect(attrValueStrB).toContain('IconB');
93
- expect(childrenArray).toHaveLength(1);
94
- const childName = (childrenArray[0] as JSXElement).openingElement.name;
95
- expect(childName.type === 'JSXIdentifier' ? childName.name : undefined).toBe('OtherComponent');
96
- });
97
-
98
- it('unwraps JSXExpressionContainer and processes icon child', () => {
99
- const source = `
100
- <Button>
101
- {<IconC />}
102
- <OtherComponent />
103
- </Button>
104
- `;
105
- const root = getRoot(source);
106
- const openingElement = findOpeningElement(root);
107
- const iconImports = new Set(['IconC']);
108
- const children = root
109
- .find(j.JSXElement)
110
- .nodes()
111
- .filter(
112
- (node) =>
113
- node.openingElement.name.type === 'JSXIdentifier' &&
114
- node.openingElement.name.name !== 'Button',
115
- );
116
-
117
- const wrappedIconChild: JSXExpressionContainer = {
118
- type: 'JSXExpressionContainer',
119
- expression: children[0],
120
- };
121
- const childrenArray: (JSXElement | JSXExpressionContainer | unknown)[] = [
122
- wrappedIconChild,
123
- children[1],
124
- ];
125
-
126
- processIconChildren(j, childrenArray, iconImports, openingElement);
127
-
128
- expect(openingElement.attributes).toHaveLength(1);
129
- expect((openingElement.attributes![0] as JSXAttribute).name.name).toBe('addonStart');
130
- const attrValue = (openingElement.attributes![0] as JSXAttribute).value;
131
- const addonStartAttrC = openingElement.attributes!.find(
132
- (attr) => (attr as JSXAttribute).name.name === 'addonStart',
133
- ) as JSXAttribute | undefined;
134
- expect(addonStartAttrC).toBeDefined();
135
- const attrValueStrC = addonStartAttrC && JSON.stringify(addonStartAttrC.value);
136
- expect(attrValueStrC).toContain('IconC');
137
- expect(childrenArray).toHaveLength(1);
138
- const childName = (childrenArray[0] as JSXElement).openingElement.name;
139
- expect(childName.type === 'JSXIdentifier' ? childName.name : undefined).toBe('OtherComponent');
140
- });
141
-
142
- it('does nothing if no icon child found', () => {
143
- const source = `
144
- <Button>
145
- <Component1 />
146
- <Component2 />
147
- </Button>
148
- `;
149
- const root = getRoot(source);
150
- const openingElement = findOpeningElement(root);
151
- const iconImports = new Set(['IconX']);
152
- const children = root
153
- .find(j.JSXElement)
154
- .nodes()
155
- .filter(
156
- (node) =>
157
- node.openingElement.name.type === 'JSXIdentifier' &&
158
- node.openingElement.name.name !== 'Button',
159
- );
160
-
161
- const childrenArray: (JSXElement | JSXExpressionContainer | unknown)[] = children;
162
-
163
- processIconChildren(j, childrenArray, iconImports, openingElement);
164
-
165
- expect(openingElement.attributes).toHaveLength(0);
166
- expect(childrenArray).toHaveLength(2);
167
- });
168
-
169
- it('does nothing if children is undefined', () => {
170
- const source = `<Button />`;
171
- const root = getRoot(source);
172
- const openingElement = findOpeningElement(root);
173
- const iconImports = new Set(['IconY']);
174
-
175
- processIconChildren(j, undefined, iconImports, openingElement);
176
-
177
- expect(openingElement.attributes).toHaveLength(0);
178
- });
179
-
180
- it('does nothing if openingElement.attributes is undefined', () => {
181
- const source = `
182
- <Button>
183
- <IconZ />
184
- </Button>
185
- `;
186
- const root = getRoot(source);
187
- const iconImports = new Set(['IconZ']);
188
- const children = root
189
- .find(j.JSXElement)
190
- .nodes()
191
- .filter(
192
- (node) =>
193
- node.openingElement.name.type === 'JSXIdentifier' &&
194
- node.openingElement.name.name !== 'Button',
195
- );
196
-
197
- const originalOpeningElement = findOpeningElement(root);
198
- const openingElement = { ...originalOpeningElement, attributes: undefined };
199
-
200
- const childrenArray: (JSXElement | JSXExpressionContainer | unknown)[] = children;
201
-
202
- processIconChildren(j, childrenArray, iconImports, openingElement);
203
-
204
- expect(openingElement.attributes).toBeUndefined();
205
- expect(childrenArray).toHaveLength(1);
206
- });
207
- });