react-code-smell-detector 1.5.0 → 1.5.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.
Files changed (87) hide show
  1. package/README.md +241 -4
  2. package/dist/__tests__/aiRefactoring.test.d.ts +2 -0
  3. package/dist/__tests__/aiRefactoring.test.d.ts.map +1 -0
  4. package/dist/__tests__/aiRefactoring.test.js +86 -0
  5. package/dist/__tests__/analyzer-real.test.d.ts +2 -0
  6. package/dist/__tests__/analyzer-real.test.d.ts.map +1 -0
  7. package/dist/__tests__/analyzer-real.test.js +149 -0
  8. package/dist/__tests__/analyzer.test.d.ts +2 -0
  9. package/dist/__tests__/analyzer.test.d.ts.map +1 -0
  10. package/dist/__tests__/analyzer.test.js +173 -0
  11. package/dist/__tests__/baseline.test.d.ts +2 -0
  12. package/dist/__tests__/baseline.test.d.ts.map +1 -0
  13. package/dist/__tests__/baseline.test.js +136 -0
  14. package/dist/__tests__/bundleAnalyzer.test.d.ts +2 -0
  15. package/dist/__tests__/bundleAnalyzer.test.d.ts.map +1 -0
  16. package/dist/__tests__/bundleAnalyzer.test.js +182 -0
  17. package/dist/__tests__/customRules.test.d.ts +2 -0
  18. package/dist/__tests__/customRules.test.d.ts.map +1 -0
  19. package/dist/__tests__/customRules.test.js +283 -0
  20. package/dist/__tests__/detectors/index.test.d.ts +2 -0
  21. package/dist/__tests__/detectors/index.test.d.ts.map +1 -0
  22. package/dist/__tests__/detectors/index.test.js +1012 -0
  23. package/dist/__tests__/detectors/newDetectors.test.d.ts +2 -0
  24. package/dist/__tests__/detectors/newDetectors.test.d.ts.map +1 -0
  25. package/dist/__tests__/detectors/newDetectors.test.js +333 -0
  26. package/dist/__tests__/docGenerator.test.d.ts +2 -0
  27. package/dist/__tests__/docGenerator.test.d.ts.map +1 -0
  28. package/dist/__tests__/docGenerator.test.js +157 -0
  29. package/dist/__tests__/fixer.test.d.ts +2 -0
  30. package/dist/__tests__/fixer.test.d.ts.map +1 -0
  31. package/dist/__tests__/fixer.test.js +193 -0
  32. package/dist/__tests__/git.test.d.ts +2 -0
  33. package/dist/__tests__/git.test.d.ts.map +1 -0
  34. package/dist/__tests__/git.test.js +38 -0
  35. package/dist/__tests__/graphGenerator.test.d.ts +2 -0
  36. package/dist/__tests__/graphGenerator.test.d.ts.map +1 -0
  37. package/dist/__tests__/graphGenerator.test.js +190 -0
  38. package/dist/__tests__/htmlReporter.test.d.ts +2 -0
  39. package/dist/__tests__/htmlReporter.test.d.ts.map +1 -0
  40. package/dist/__tests__/htmlReporter.test.js +258 -0
  41. package/dist/__tests__/interactiveFixer.test.d.ts +2 -0
  42. package/dist/__tests__/interactiveFixer.test.d.ts.map +1 -0
  43. package/dist/__tests__/interactiveFixer.test.js +231 -0
  44. package/dist/__tests__/performanceBudget.test.js +195 -44
  45. package/dist/__tests__/reporter.test.d.ts +2 -0
  46. package/dist/__tests__/reporter.test.d.ts.map +1 -0
  47. package/dist/__tests__/reporter.test.js +136 -0
  48. package/dist/__tests__/watcher.test.d.ts +2 -0
  49. package/dist/__tests__/watcher.test.d.ts.map +1 -0
  50. package/dist/__tests__/watcher.test.js +161 -0
  51. package/dist/__tests__/webhooks.test.d.ts +2 -0
  52. package/dist/__tests__/webhooks.test.d.ts.map +1 -0
  53. package/dist/__tests__/webhooks.test.js +209 -0
  54. package/dist/aiRefactoring.d.ts +29 -0
  55. package/dist/aiRefactoring.d.ts.map +1 -0
  56. package/dist/aiRefactoring.js +290 -0
  57. package/dist/analyzer.d.ts.map +1 -1
  58. package/dist/analyzer.js +23 -0
  59. package/dist/cli.js +17 -0
  60. package/dist/detectors/contextApi.d.ts +11 -0
  61. package/dist/detectors/contextApi.d.ts.map +1 -0
  62. package/dist/detectors/contextApi.js +151 -0
  63. package/dist/detectors/errorBoundary.d.ts +11 -0
  64. package/dist/detectors/errorBoundary.d.ts.map +1 -0
  65. package/dist/detectors/errorBoundary.js +167 -0
  66. package/dist/detectors/formValidation.d.ts +11 -0
  67. package/dist/detectors/formValidation.d.ts.map +1 -0
  68. package/dist/detectors/formValidation.js +193 -0
  69. package/dist/detectors/index.d.ts +5 -0
  70. package/dist/detectors/index.d.ts.map +1 -1
  71. package/dist/detectors/index.js +10 -0
  72. package/dist/detectors/stateManagement.d.ts +11 -0
  73. package/dist/detectors/stateManagement.d.ts.map +1 -0
  74. package/dist/detectors/stateManagement.js +193 -0
  75. package/dist/detectors/testingGaps.d.ts +15 -0
  76. package/dist/detectors/testingGaps.d.ts.map +1 -0
  77. package/dist/detectors/testingGaps.js +182 -0
  78. package/dist/guide.d.ts +9 -0
  79. package/dist/guide.d.ts.map +1 -0
  80. package/dist/guide.js +922 -0
  81. package/dist/index.d.ts +1 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +1 -0
  84. package/dist/types/index.d.ts +11 -1
  85. package/dist/types/index.d.ts.map +1 -1
  86. package/dist/types/index.js +16 -0
  87. package/package.json +1 -1
@@ -0,0 +1,258 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { generateHTMLReport } from '../htmlReporter.js';
3
+ import { DEFAULT_CONFIG } from '../types/index.js';
4
+ describe('HTML Reporter', () => {
5
+ const createMockResult = (overrides = {}) => {
6
+ const defaultSmellsByType = Object.fromEntries(Object.keys(DEFAULT_CONFIG).filter(k => k !== 'customRulesPath').map(k => [k, 0]));
7
+ // Add all required smell types
8
+ const smellsByType = {
9
+ 'useEffect-overuse': 0,
10
+ 'prop-drilling': 0,
11
+ 'large-component': 0,
12
+ 'unmemoized-calculation': 0,
13
+ 'missing-dependency': 0,
14
+ 'state-in-loop': 0,
15
+ 'inline-function-prop': 0,
16
+ 'deep-nesting': 0,
17
+ 'missing-key': 0,
18
+ 'hooks-rules-violation': 0,
19
+ 'dependency-array-issue': 0,
20
+ 'nested-ternary': 0,
21
+ 'dead-code': 0,
22
+ 'magic-value': 0,
23
+ 'nextjs-client-server-boundary': 0,
24
+ 'nextjs-missing-metadata': 0,
25
+ 'nextjs-image-unoptimized': 0,
26
+ 'nextjs-router-misuse': 0,
27
+ 'rn-inline-style': 0,
28
+ 'rn-missing-accessibility': 0,
29
+ 'rn-performance-issue': 0,
30
+ 'nodejs-callback-hell': 0,
31
+ 'nodejs-unhandled-promise': 0,
32
+ 'nodejs-sync-io': 0,
33
+ 'nodejs-missing-error-handling': 0,
34
+ 'js-var-usage': 0,
35
+ 'js-loose-equality': 0,
36
+ 'js-implicit-coercion': 0,
37
+ 'js-global-pollution': 0,
38
+ 'ts-any-usage': 0,
39
+ 'ts-missing-return-type': 0,
40
+ 'ts-non-null-assertion': 0,
41
+ 'ts-type-assertion': 0,
42
+ 'debug-statement': 0,
43
+ 'todo-comment': 0,
44
+ 'security-xss': 0,
45
+ 'security-eval': 0,
46
+ 'security-secrets': 0,
47
+ 'a11y-missing-alt': 0,
48
+ 'a11y-missing-label': 0,
49
+ 'a11y-interactive-role': 0,
50
+ 'a11y-keyboard': 0,
51
+ 'a11y-semantic': 0,
52
+ 'high-cyclomatic-complexity': 0,
53
+ 'high-cognitive-complexity': 0,
54
+ 'memory-leak-event-listener': 0,
55
+ 'memory-leak-subscription': 0,
56
+ 'memory-leak-timer': 0,
57
+ 'memory-leak-async': 0,
58
+ 'circular-dependency': 0,
59
+ 'barrel-file-import': 0,
60
+ 'namespace-import': 0,
61
+ 'excessive-imports': 0,
62
+ 'unused-export': 0,
63
+ 'dead-import': 0,
64
+ 'server-component-hooks': 0,
65
+ 'server-component-events': 0,
66
+ 'server-component-browser-api': 0,
67
+ 'async-client-component': 0,
68
+ 'mixed-directives': 0,
69
+ // Context API issues
70
+ 'context-overuse': 0,
71
+ 'context-in-loop': 0,
72
+ 'missing-context-memo': 0,
73
+ 'large-context-value': 0,
74
+ // Error Boundary issues
75
+ 'missing-error-boundary': 0,
76
+ 'error-boundary-missing-fallback': 0,
77
+ 'suspense-missing-fallback': 0,
78
+ // Form validation issues
79
+ 'uncontrolled-form': 0,
80
+ 'missing-form-validation': 0,
81
+ 'form-without-onsubmit': 0,
82
+ 'input-without-label': 0,
83
+ // State management issues
84
+ 'redux-in-render': 0,
85
+ 'excessive-redux-selectors': 0,
86
+ 'state-sync-anti-pattern': 0,
87
+ 'derived-state-in-state': 0,
88
+ // Testing gaps
89
+ 'complex-untestable': 0,
90
+ 'side-effect-heavy': 0,
91
+ 'tightly-coupled': 0,
92
+ 'custom-rule': 0,
93
+ };
94
+ return {
95
+ files: [],
96
+ summary: {
97
+ totalFiles: 0,
98
+ totalComponents: 0,
99
+ totalSmells: 0,
100
+ smellsByType,
101
+ smellsBySeverity: {
102
+ error: 0,
103
+ warning: 0,
104
+ info: 0,
105
+ },
106
+ },
107
+ debtScore: {
108
+ score: 100,
109
+ grade: 'A',
110
+ breakdown: {
111
+ useEffectScore: 100,
112
+ propDrillingScore: 100,
113
+ componentSizeScore: 100,
114
+ memoizationScore: 100,
115
+ },
116
+ estimatedRefactorTime: '< 30 minutes',
117
+ },
118
+ ...overrides,
119
+ };
120
+ };
121
+ describe('generateHTMLReport', () => {
122
+ it('should generate valid HTML document', () => {
123
+ const result = createMockResult();
124
+ const html = generateHTMLReport(result, '/root');
125
+ expect(html).toContain('<!DOCTYPE html>');
126
+ expect(html).toContain('<html');
127
+ expect(html).toContain('</html>');
128
+ });
129
+ it('should include title', () => {
130
+ const result = createMockResult();
131
+ const html = generateHTMLReport(result, '/root');
132
+ expect(html).toContain('<title>');
133
+ expect(html).toContain('Code Smell');
134
+ });
135
+ it('should include CSS styles', () => {
136
+ const result = createMockResult();
137
+ const html = generateHTMLReport(result, '/root');
138
+ expect(html).toContain('<style>');
139
+ expect(html).toContain('</style>');
140
+ });
141
+ it('should display technical debt grade', () => {
142
+ const result = createMockResult();
143
+ const html = generateHTMLReport(result, '/root');
144
+ expect(html).toContain('A'); // Grade A
145
+ });
146
+ it('should display summary statistics', () => {
147
+ const result = createMockResult({
148
+ summary: {
149
+ ...createMockResult().summary,
150
+ totalFiles: 10,
151
+ totalComponents: 25,
152
+ totalSmells: 5,
153
+ },
154
+ });
155
+ const html = generateHTMLReport(result, '/root');
156
+ expect(html).toContain('10');
157
+ expect(html).toContain('25');
158
+ });
159
+ it('should handle smells with various severities', () => {
160
+ const result = createMockResult({
161
+ summary: {
162
+ ...createMockResult().summary,
163
+ smellsBySeverity: {
164
+ error: 3,
165
+ warning: 5,
166
+ info: 2,
167
+ },
168
+ totalSmells: 10,
169
+ },
170
+ });
171
+ const html = generateHTMLReport(result, '/root');
172
+ expect(html).toContain('error');
173
+ });
174
+ it('should include breakdown scores', () => {
175
+ const result = createMockResult();
176
+ const html = generateHTMLReport(result, '/root');
177
+ expect(html).toContain('100'); // Perfect scores
178
+ });
179
+ it('should handle files with smells', () => {
180
+ const result = createMockResult({
181
+ files: [
182
+ {
183
+ file: '/root/src/App.tsx',
184
+ components: [
185
+ {
186
+ name: 'App',
187
+ file: '/root/src/App.tsx',
188
+ startLine: 1,
189
+ endLine: 20,
190
+ lineCount: 20,
191
+ useEffectCount: 1,
192
+ useStateCount: 2,
193
+ useMemoCount: 0,
194
+ useCallbackCount: 0,
195
+ propsCount: 3,
196
+ propsDrillingDepth: 0,
197
+ hasExpensiveCalculation: false,
198
+ },
199
+ ],
200
+ smells: [
201
+ {
202
+ type: 'debug-statement',
203
+ severity: 'warning',
204
+ message: 'Console.log found',
205
+ file: '/root/src/App.tsx',
206
+ line: 10,
207
+ column: 5,
208
+ suggestion: 'Remove debug statement',
209
+ },
210
+ ],
211
+ imports: [],
212
+ },
213
+ ],
214
+ });
215
+ const html = generateHTMLReport(result, '/root');
216
+ expect(html).toContain('App.tsx');
217
+ });
218
+ it('should handle all grade levels', () => {
219
+ const grades = ['A', 'B', 'C', 'D', 'F'];
220
+ for (const grade of grades) {
221
+ const result = createMockResult({
222
+ debtScore: {
223
+ ...createMockResult().debtScore,
224
+ grade,
225
+ score: grade === 'A' ? 95 : grade === 'B' ? 85 : grade === 'C' ? 75 : grade === 'D' ? 65 : 50,
226
+ },
227
+ });
228
+ const html = generateHTMLReport(result, '/root');
229
+ expect(html).toContain(grade);
230
+ }
231
+ });
232
+ it('should escape HTML in file paths', () => {
233
+ const result = createMockResult({
234
+ files: [
235
+ {
236
+ file: '/root/src/<Component>.tsx',
237
+ components: [],
238
+ smells: [],
239
+ imports: [],
240
+ },
241
+ ],
242
+ });
243
+ const html = generateHTMLReport(result, '/root');
244
+ // Should still generate valid HTML
245
+ expect(html).toContain('<!DOCTYPE html>');
246
+ });
247
+ it('should include estimated refactor time', () => {
248
+ const result = createMockResult({
249
+ debtScore: {
250
+ ...createMockResult().debtScore,
251
+ estimatedRefactorTime: '2-4 hours',
252
+ },
253
+ });
254
+ const html = generateHTMLReport(result, '/root');
255
+ expect(html).toContain('2-4 hours');
256
+ });
257
+ });
258
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=interactiveFixer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactiveFixer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/interactiveFixer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,231 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { previewFixes } from '../interactiveFixer.js';
3
+ describe('Interactive Fixer', () => {
4
+ let consoleLogSpy;
5
+ beforeEach(() => {
6
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
7
+ });
8
+ afterEach(() => {
9
+ consoleLogSpy.mockRestore();
10
+ });
11
+ const createFixableSmell = (type, file = '/project/src/test.tsx', line = 10) => ({
12
+ type: type,
13
+ severity: 'warning',
14
+ message: 'Test smell',
15
+ file,
16
+ line,
17
+ column: 5,
18
+ suggestion: 'Fix this issue',
19
+ fix: {
20
+ type: 'replace',
21
+ oldCode: 'var x = 1',
22
+ newCode: 'const x = 1',
23
+ },
24
+ });
25
+ const createNonFixableSmell = (type) => ({
26
+ type: type,
27
+ severity: 'warning',
28
+ message: 'Test smell',
29
+ file: '/project/src/test.tsx',
30
+ line: 10,
31
+ column: 5,
32
+ suggestion: 'Fix this issue',
33
+ });
34
+ describe('Fix filtering', () => {
35
+ it('should identify fixable smells', () => {
36
+ const fixable = createFixableSmell('js-var-usage');
37
+ const nonFixable = createNonFixableSmell('large-component');
38
+ expect(fixable.fix).toBeDefined();
39
+ expect(nonFixable.fix).toBeUndefined();
40
+ });
41
+ it('should filter fixable smells from array', () => {
42
+ const smells = [
43
+ createFixableSmell('js-var-usage'),
44
+ createNonFixableSmell('large-component'),
45
+ createFixableSmell('debug-statement'),
46
+ createNonFixableSmell('prop-drilling'),
47
+ ];
48
+ const fixable = smells.filter(s => s.fix !== undefined);
49
+ expect(fixable).toHaveLength(2);
50
+ expect(fixable[0].type).toBe('js-var-usage');
51
+ expect(fixable[1].type).toBe('debug-statement');
52
+ });
53
+ });
54
+ describe('Fix preview generation', () => {
55
+ it('should generate diff preview', () => {
56
+ const smell = createFixableSmell('js-var-usage');
57
+ expect(smell.fix?.oldCode).toBe('var x = 1');
58
+ expect(smell.fix?.newCode).toBe('const x = 1');
59
+ });
60
+ it('should handle multi-line fixes', () => {
61
+ const smell = {
62
+ type: 'debug-statement',
63
+ severity: 'warning',
64
+ message: 'Remove debug',
65
+ file: '/test.tsx',
66
+ line: 1,
67
+ column: 0,
68
+ suggestion: 'Remove console.log',
69
+ fix: {
70
+ type: 'delete',
71
+ oldCode: `console.log('debug');\nconsole.log('more');`,
72
+ newCode: '',
73
+ },
74
+ };
75
+ expect(smell.fix?.oldCode).toContain('\n');
76
+ expect(smell.fix?.newCode).toBe('');
77
+ });
78
+ it('should handle insert type fixes', () => {
79
+ const smell = {
80
+ type: 'missing-key',
81
+ severity: 'error',
82
+ message: 'Missing key prop',
83
+ file: '/test.tsx',
84
+ line: 1,
85
+ column: 0,
86
+ suggestion: 'Add key prop',
87
+ fix: {
88
+ type: 'insert',
89
+ oldCode: '<Item />',
90
+ newCode: '<Item key={id} />',
91
+ },
92
+ };
93
+ expect(smell.fix?.type).toBe('insert');
94
+ });
95
+ });
96
+ describe('Fix grouping by file', () => {
97
+ it('should group smells by file', () => {
98
+ const smells = [
99
+ { ...createFixableSmell('js-var-usage'), file: '/a.tsx' },
100
+ { ...createFixableSmell('debug-statement'), file: '/b.tsx' },
101
+ { ...createFixableSmell('console-log'), file: '/a.tsx' },
102
+ ];
103
+ const grouped = smells.reduce((acc, smell) => {
104
+ const file = smell.file;
105
+ if (!acc[file])
106
+ acc[file] = [];
107
+ acc[file].push(smell);
108
+ return acc;
109
+ }, {});
110
+ expect(Object.keys(grouped)).toHaveLength(2);
111
+ expect(grouped['/a.tsx']).toHaveLength(2);
112
+ expect(grouped['/b.tsx']).toHaveLength(1);
113
+ });
114
+ });
115
+ describe('Fix statistics', () => {
116
+ it('should count applied fixes', () => {
117
+ const smells = [
118
+ createFixableSmell('js-var-usage'),
119
+ createFixableSmell('debug-statement'),
120
+ createFixableSmell('console-log'),
121
+ ];
122
+ const applied = smells.filter((_, i) => i < 2);
123
+ const skipped = smells.filter((_, i) => i >= 2);
124
+ expect(applied).toHaveLength(2);
125
+ expect(skipped).toHaveLength(1);
126
+ });
127
+ it('should categorize by fix type', () => {
128
+ const smells = [
129
+ {
130
+ ...createFixableSmell('a'),
131
+ fix: { type: 'replace', oldCode: 'a', newCode: 'b' },
132
+ },
133
+ {
134
+ ...createFixableSmell('b'),
135
+ fix: { type: 'delete', oldCode: 'c', newCode: '' },
136
+ },
137
+ {
138
+ ...createFixableSmell('c'),
139
+ fix: { type: 'insert', oldCode: '', newCode: 'd' },
140
+ },
141
+ ];
142
+ const byType = smells.reduce((acc, s) => {
143
+ const type = s.fix.type;
144
+ acc[type] = (acc[type] || 0) + 1;
145
+ return acc;
146
+ }, {});
147
+ expect(byType.replace).toBe(1);
148
+ expect(byType.delete).toBe(1);
149
+ expect(byType.insert).toBe(1);
150
+ });
151
+ });
152
+ describe('Fix ordering', () => {
153
+ it('should sort fixes by line number descending for safe application', () => {
154
+ const smells = [
155
+ { ...createFixableSmell('a'), line: 5 },
156
+ { ...createFixableSmell('b'), line: 20 },
157
+ { ...createFixableSmell('c'), line: 10 },
158
+ ];
159
+ // Sort descending so later fixes don't affect earlier line numbers
160
+ const sorted = [...smells].sort((a, b) => b.line - a.line);
161
+ expect(sorted[0].line).toBe(20);
162
+ expect(sorted[1].line).toBe(10);
163
+ expect(sorted[2].line).toBe(5);
164
+ });
165
+ it('should handle multiple fixes on same line by column', () => {
166
+ const smells = [
167
+ { ...createFixableSmell('a'), line: 10, column: 5 },
168
+ { ...createFixableSmell('b'), line: 10, column: 20 },
169
+ { ...createFixableSmell('c'), line: 10, column: 10 },
170
+ ];
171
+ // Sort by column descending for same line
172
+ const sorted = [...smells].sort((a, b) => {
173
+ if (a.line !== b.line)
174
+ return b.line - a.line;
175
+ return b.column - a.column;
176
+ });
177
+ expect(sorted[0].column).toBe(20);
178
+ expect(sorted[1].column).toBe(10);
179
+ expect(sorted[2].column).toBe(5);
180
+ });
181
+ });
182
+ describe('previewFixes', () => {
183
+ it('should show message when no fixable issues', () => {
184
+ const nonFixableSmell = {
185
+ type: 'large-component',
186
+ severity: 'warning',
187
+ message: 'Too large',
188
+ file: '/test.tsx',
189
+ line: 1,
190
+ column: 0,
191
+ suggestion: 'Split component',
192
+ };
193
+ previewFixes([nonFixableSmell], '/project');
194
+ expect(consoleLogSpy).toHaveBeenCalled();
195
+ });
196
+ it('should display fixable issues grouped by type', () => {
197
+ const smells = [
198
+ createFixableSmell('debug-statement', '/project/a.tsx', 10),
199
+ createFixableSmell('debug-statement', '/project/b.tsx', 20),
200
+ createFixableSmell('js-var-usage', '/project/c.tsx', 30),
201
+ ];
202
+ previewFixes(smells, '/project');
203
+ expect(consoleLogSpy).toHaveBeenCalled();
204
+ });
205
+ it('should handle empty smells array', () => {
206
+ previewFixes([], '/project');
207
+ expect(consoleLogSpy).toHaveBeenCalled();
208
+ });
209
+ it('should handle many smells of same type', () => {
210
+ const smells = Array.from({ length: 10 }, (_, i) => createFixableSmell('debug-statement', `/project/file${i}.tsx`, i + 1));
211
+ previewFixes(smells, '/project');
212
+ expect(consoleLogSpy).toHaveBeenCalled();
213
+ });
214
+ it('should handle mixed fixable and non-fixable smells', () => {
215
+ const smells = [
216
+ createFixableSmell('debug-statement', '/project/a.tsx', 10),
217
+ {
218
+ type: 'large-component',
219
+ severity: 'warning',
220
+ message: 'Too large',
221
+ file: '/project/b.tsx',
222
+ line: 1,
223
+ column: 0,
224
+ suggestion: 'Split component',
225
+ },
226
+ ];
227
+ previewFixes(smells, '/project');
228
+ expect(consoleLogSpy).toHaveBeenCalled();
229
+ });
230
+ });
231
+ });