react-code-smell-detector 1.4.2 → 1.5.1
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/README.md +227 -22
- package/dist/__tests__/aiRefactoring.test.d.ts +2 -0
- package/dist/__tests__/aiRefactoring.test.d.ts.map +1 -0
- package/dist/__tests__/aiRefactoring.test.js +86 -0
- package/dist/__tests__/analyzer-real.test.d.ts +2 -0
- package/dist/__tests__/analyzer-real.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer-real.test.js +149 -0
- package/dist/__tests__/analyzer.test.d.ts +2 -0
- package/dist/__tests__/analyzer.test.d.ts.map +1 -0
- package/dist/__tests__/analyzer.test.js +173 -0
- package/dist/__tests__/baseline.test.d.ts +2 -0
- package/dist/__tests__/baseline.test.d.ts.map +1 -0
- package/dist/__tests__/baseline.test.js +136 -0
- package/dist/__tests__/bundleAnalyzer.test.d.ts +2 -0
- package/dist/__tests__/bundleAnalyzer.test.d.ts.map +1 -0
- package/dist/__tests__/bundleAnalyzer.test.js +182 -0
- package/dist/__tests__/customRules.test.d.ts +2 -0
- package/dist/__tests__/customRules.test.d.ts.map +1 -0
- package/dist/__tests__/customRules.test.js +283 -0
- package/dist/__tests__/detectors/index.test.d.ts +2 -0
- package/dist/__tests__/detectors/index.test.d.ts.map +1 -0
- package/dist/__tests__/detectors/index.test.js +1012 -0
- package/dist/__tests__/detectors/newDetectors.test.d.ts +2 -0
- package/dist/__tests__/detectors/newDetectors.test.d.ts.map +1 -0
- package/dist/__tests__/detectors/newDetectors.test.js +333 -0
- package/dist/__tests__/docGenerator.test.d.ts +2 -0
- package/dist/__tests__/docGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/docGenerator.test.js +157 -0
- package/dist/__tests__/fixer.test.d.ts +2 -0
- package/dist/__tests__/fixer.test.d.ts.map +1 -0
- package/dist/__tests__/fixer.test.js +193 -0
- package/dist/__tests__/git.test.d.ts +2 -0
- package/dist/__tests__/git.test.d.ts.map +1 -0
- package/dist/__tests__/git.test.js +38 -0
- package/dist/__tests__/graphGenerator.test.d.ts +2 -0
- package/dist/__tests__/graphGenerator.test.d.ts.map +1 -0
- package/dist/__tests__/graphGenerator.test.js +190 -0
- package/dist/__tests__/htmlReporter.test.d.ts +2 -0
- package/dist/__tests__/htmlReporter.test.d.ts.map +1 -0
- package/dist/__tests__/htmlReporter.test.js +258 -0
- package/dist/__tests__/interactiveFixer.test.d.ts +2 -0
- package/dist/__tests__/interactiveFixer.test.d.ts.map +1 -0
- package/dist/__tests__/interactiveFixer.test.js +231 -0
- package/dist/__tests__/parser.test.d.ts +2 -0
- package/dist/__tests__/parser.test.d.ts.map +1 -0
- package/dist/__tests__/parser.test.js +56 -0
- package/dist/__tests__/performanceBudget.test.d.ts +2 -0
- package/dist/__tests__/performanceBudget.test.d.ts.map +1 -0
- package/dist/__tests__/performanceBudget.test.js +242 -0
- package/dist/__tests__/prComments.test.d.ts +2 -0
- package/dist/__tests__/prComments.test.d.ts.map +1 -0
- package/dist/__tests__/prComments.test.js +118 -0
- package/dist/__tests__/reporter.test.d.ts +2 -0
- package/dist/__tests__/reporter.test.d.ts.map +1 -0
- package/dist/__tests__/reporter.test.js +136 -0
- package/dist/__tests__/watcher.test.d.ts +2 -0
- package/dist/__tests__/watcher.test.d.ts.map +1 -0
- package/dist/__tests__/watcher.test.js +161 -0
- package/dist/__tests__/webhooks.test.d.ts +2 -0
- package/dist/__tests__/webhooks.test.d.ts.map +1 -0
- package/dist/__tests__/webhooks.test.js +209 -0
- package/dist/aiRefactoring.d.ts +29 -0
- package/dist/aiRefactoring.d.ts.map +1 -0
- package/dist/aiRefactoring.js +290 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +33 -1
- package/dist/cli.js +123 -1
- package/dist/detectors/contextApi.d.ts +11 -0
- package/dist/detectors/contextApi.d.ts.map +1 -0
- package/dist/detectors/contextApi.js +151 -0
- package/dist/detectors/errorBoundary.d.ts +11 -0
- package/dist/detectors/errorBoundary.d.ts.map +1 -0
- package/dist/detectors/errorBoundary.js +167 -0
- package/dist/detectors/formValidation.d.ts +11 -0
- package/dist/detectors/formValidation.d.ts.map +1 -0
- package/dist/detectors/formValidation.js +193 -0
- package/dist/detectors/index.d.ts +6 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +12 -0
- package/dist/detectors/serverComponents.d.ts +11 -0
- package/dist/detectors/serverComponents.d.ts.map +1 -0
- package/dist/detectors/serverComponents.js +222 -0
- package/dist/detectors/stateManagement.d.ts +11 -0
- package/dist/detectors/stateManagement.d.ts.map +1 -0
- package/dist/detectors/stateManagement.js +193 -0
- package/dist/detectors/testingGaps.d.ts +15 -0
- package/dist/detectors/testingGaps.d.ts.map +1 -0
- package/dist/detectors/testingGaps.js +182 -0
- package/dist/docGenerator.d.ts +37 -0
- package/dist/docGenerator.d.ts.map +1 -0
- package/dist/docGenerator.js +306 -0
- package/dist/guide.d.ts +9 -0
- package/dist/guide.d.ts.map +1 -0
- package/dist/guide.js +922 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/interactiveFixer.d.ts +20 -0
- package/dist/interactiveFixer.d.ts.map +1 -0
- package/dist/interactiveFixer.js +178 -0
- package/dist/performanceBudget.d.ts +54 -0
- package/dist/performanceBudget.d.ts.map +1 -0
- package/dist/performanceBudget.js +218 -0
- package/dist/prComments.d.ts +47 -0
- package/dist/prComments.d.ts.map +1 -0
- package/dist/prComments.js +233 -0
- package/dist/types/index.d.ts +12 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +18 -0
- package/package.json +10 -4
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { isFixable, describeFixAction, getFixableTypes, fixFile } from '../fixer.js';
|
|
3
|
+
// Use vi.hoisted to ensure mocks are available when vi.mock is hoisted
|
|
4
|
+
const { mockReadFile, mockWriteFile } = vi.hoisted(() => ({
|
|
5
|
+
mockReadFile: vi.fn(),
|
|
6
|
+
mockWriteFile: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
// Mock fs/promises default export
|
|
9
|
+
vi.mock('fs/promises', () => ({
|
|
10
|
+
default: {
|
|
11
|
+
readFile: mockReadFile,
|
|
12
|
+
writeFile: mockWriteFile,
|
|
13
|
+
},
|
|
14
|
+
readFile: mockReadFile,
|
|
15
|
+
writeFile: mockWriteFile,
|
|
16
|
+
}));
|
|
17
|
+
describe('Fixer', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.clearAllMocks();
|
|
20
|
+
mockReadFile.mockReset();
|
|
21
|
+
mockWriteFile.mockReset();
|
|
22
|
+
});
|
|
23
|
+
const createSmell = (type, line = 1) => ({
|
|
24
|
+
type,
|
|
25
|
+
severity: 'warning',
|
|
26
|
+
message: 'Test smell',
|
|
27
|
+
file: '/test.tsx',
|
|
28
|
+
line,
|
|
29
|
+
column: 0,
|
|
30
|
+
suggestion: 'Fix it',
|
|
31
|
+
});
|
|
32
|
+
describe('isFixable', () => {
|
|
33
|
+
it('should return true for debug-statement', () => {
|
|
34
|
+
expect(isFixable(createSmell('debug-statement'))).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
it('should return true for js-var-usage', () => {
|
|
37
|
+
expect(isFixable(createSmell('js-var-usage'))).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it('should return true for js-loose-equality', () => {
|
|
40
|
+
expect(isFixable(createSmell('js-loose-equality'))).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('should return true for a11y-missing-alt', () => {
|
|
43
|
+
expect(isFixable(createSmell('a11y-missing-alt'))).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
it('should return false for non-fixable types', () => {
|
|
46
|
+
expect(isFixable(createSmell('useEffect-overuse'))).toBe(false);
|
|
47
|
+
expect(isFixable(createSmell('prop-drilling'))).toBe(false);
|
|
48
|
+
expect(isFixable(createSmell('large-component'))).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('describeFixAction', () => {
|
|
52
|
+
it('should describe debug-statement fix', () => {
|
|
53
|
+
const desc = describeFixAction('debug-statement');
|
|
54
|
+
expect(desc).toContain('console.log');
|
|
55
|
+
});
|
|
56
|
+
it('should describe js-var-usage fix', () => {
|
|
57
|
+
const desc = describeFixAction('js-var-usage');
|
|
58
|
+
expect(desc).toContain('var');
|
|
59
|
+
expect(desc).toContain('let');
|
|
60
|
+
});
|
|
61
|
+
it('should describe js-loose-equality fix', () => {
|
|
62
|
+
const desc = describeFixAction('js-loose-equality');
|
|
63
|
+
expect(desc).toContain('==');
|
|
64
|
+
expect(desc).toContain('===');
|
|
65
|
+
});
|
|
66
|
+
it('should describe a11y-missing-alt fix', () => {
|
|
67
|
+
const desc = describeFixAction('a11y-missing-alt');
|
|
68
|
+
expect(desc).toContain('alt');
|
|
69
|
+
});
|
|
70
|
+
it('should return not auto-fixable for unknown types', () => {
|
|
71
|
+
const desc = describeFixAction('useEffect-overuse');
|
|
72
|
+
expect(desc).toContain('Not auto-fixable');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('getFixableTypes', () => {
|
|
76
|
+
it('should return array of fixable types', () => {
|
|
77
|
+
const types = getFixableTypes();
|
|
78
|
+
expect(Array.isArray(types)).toBe(true);
|
|
79
|
+
expect(types).toContain('debug-statement');
|
|
80
|
+
expect(types).toContain('js-var-usage');
|
|
81
|
+
expect(types).toContain('js-loose-equality');
|
|
82
|
+
expect(types).toContain('a11y-missing-alt');
|
|
83
|
+
});
|
|
84
|
+
it('should return a copy, not the original array', () => {
|
|
85
|
+
const types1 = getFixableTypes();
|
|
86
|
+
const types2 = getFixableTypes();
|
|
87
|
+
expect(types1).not.toBe(types2);
|
|
88
|
+
expect(types1).toEqual(types2);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('fixFile', () => {
|
|
92
|
+
it('should return empty result when no fixable smells', async () => {
|
|
93
|
+
const smells = [createSmell('large-component')];
|
|
94
|
+
const result = await fixFile('/test.tsx', smells);
|
|
95
|
+
expect(result.file).toBe('/test.tsx');
|
|
96
|
+
expect(result.fixedSmells).toHaveLength(0);
|
|
97
|
+
expect(result.skippedSmells).toHaveLength(1);
|
|
98
|
+
});
|
|
99
|
+
it('should fix console.log statements by removing the line', async () => {
|
|
100
|
+
mockReadFile.mockResolvedValue(`const x = 1;
|
|
101
|
+
console.log('debug');
|
|
102
|
+
const y = 2;`);
|
|
103
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
104
|
+
const smells = [createSmell('debug-statement', 2)];
|
|
105
|
+
const result = await fixFile('/test.tsx', smells);
|
|
106
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
107
|
+
expect(mockWriteFile).toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
it('should fix var to let', async () => {
|
|
110
|
+
mockReadFile.mockResolvedValue(`var x = 1;
|
|
111
|
+
var y = 2;`);
|
|
112
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
113
|
+
const smells = [createSmell('js-var-usage', 1)];
|
|
114
|
+
const result = await fixFile('/test.tsx', smells);
|
|
115
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
116
|
+
const writeCall = mockWriteFile.mock.calls[0];
|
|
117
|
+
expect(writeCall[1]).toContain('let x = 1');
|
|
118
|
+
});
|
|
119
|
+
it('should fix loose equality to strict equality', async () => {
|
|
120
|
+
mockReadFile.mockResolvedValue(`if (a == b) {
|
|
121
|
+
return true;
|
|
122
|
+
}`);
|
|
123
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
124
|
+
const smells = [createSmell('js-loose-equality', 1)];
|
|
125
|
+
const result = await fixFile('/test.tsx', smells);
|
|
126
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
127
|
+
const writeCall = mockWriteFile.mock.calls[0];
|
|
128
|
+
expect(writeCall[1]).toContain('===');
|
|
129
|
+
});
|
|
130
|
+
it('should fix missing alt attribute', async () => {
|
|
131
|
+
mockReadFile.mockResolvedValue(`<img src="test.jpg" />`);
|
|
132
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
133
|
+
const smells = [createSmell('a11y-missing-alt', 1)];
|
|
134
|
+
const result = await fixFile('/test.tsx', smells);
|
|
135
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
136
|
+
const writeCall = mockWriteFile.mock.calls[0];
|
|
137
|
+
expect(writeCall[1]).toContain('alt=""');
|
|
138
|
+
});
|
|
139
|
+
it('should skip lines out of range', async () => {
|
|
140
|
+
mockReadFile.mockResolvedValue(`const x = 1;`);
|
|
141
|
+
const smells = [createSmell('debug-statement', 100)];
|
|
142
|
+
const result = await fixFile('/test.tsx', smells);
|
|
143
|
+
expect(result.fixedSmells).toHaveLength(0);
|
|
144
|
+
});
|
|
145
|
+
it('should fix multiple smells in correct order (bottom-up)', async () => {
|
|
146
|
+
mockReadFile.mockResolvedValue(`var x = 1;
|
|
147
|
+
console.log('test');
|
|
148
|
+
var y = 2;`);
|
|
149
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
150
|
+
const smells = [
|
|
151
|
+
createSmell('js-var-usage', 1),
|
|
152
|
+
createSmell('debug-statement', 2),
|
|
153
|
+
createSmell('js-var-usage', 3),
|
|
154
|
+
];
|
|
155
|
+
const result = await fixFile('/test.tsx', smells);
|
|
156
|
+
expect(result.fixedSmells).toHaveLength(3);
|
|
157
|
+
});
|
|
158
|
+
it('should handle debugger statement', async () => {
|
|
159
|
+
mockReadFile.mockResolvedValue(`const x = 1;
|
|
160
|
+
debugger;
|
|
161
|
+
const y = 2;`);
|
|
162
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
163
|
+
const smells = [createSmell('debug-statement', 2)];
|
|
164
|
+
const result = await fixFile('/test.tsx', smells);
|
|
165
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
166
|
+
});
|
|
167
|
+
it('should comment out console in expressions', async () => {
|
|
168
|
+
mockReadFile.mockResolvedValue(`const result = condition && console.log('test');`);
|
|
169
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
170
|
+
const smells = [createSmell('debug-statement', 1)];
|
|
171
|
+
const result = await fixFile('/test.tsx', smells);
|
|
172
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
173
|
+
const writeCall = mockWriteFile.mock.calls[0];
|
|
174
|
+
expect(writeCall[1]).toContain('/*');
|
|
175
|
+
});
|
|
176
|
+
it('should not write file if no fixes applied', async () => {
|
|
177
|
+
mockReadFile.mockResolvedValue(`const x = 1;`);
|
|
178
|
+
const smells = [createSmell('debug-statement', 1)];
|
|
179
|
+
const result = await fixFile('/test.tsx', smells);
|
|
180
|
+
expect(result.fixedSmells).toHaveLength(0);
|
|
181
|
+
expect(mockWriteFile).not.toHaveBeenCalled();
|
|
182
|
+
});
|
|
183
|
+
it('should handle != to !== conversion', async () => {
|
|
184
|
+
mockReadFile.mockResolvedValue(`if (a != b) { return; }`);
|
|
185
|
+
mockWriteFile.mockResolvedValue(undefined);
|
|
186
|
+
const smells = [createSmell('js-loose-equality', 1)];
|
|
187
|
+
const result = await fixFile('/test.tsx', smells);
|
|
188
|
+
expect(result.fixedSmells).toHaveLength(1);
|
|
189
|
+
const writeCall = mockWriteFile.mock.calls[0];
|
|
190
|
+
expect(writeCall[1]).toContain('!==');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/git.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getGitInfo } from '../git.js';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
describe('Git Utils', () => {
|
|
5
|
+
describe('getGitInfo', () => {
|
|
6
|
+
// Use the actual project directory for testing
|
|
7
|
+
const projectDir = process.cwd();
|
|
8
|
+
it('should return GitInfo object with isGitRepo property', () => {
|
|
9
|
+
const info = getGitInfo(projectDir);
|
|
10
|
+
expect(info).toHaveProperty('isGitRepo');
|
|
11
|
+
expect(typeof info.isGitRepo).toBe('boolean');
|
|
12
|
+
});
|
|
13
|
+
it('should return isGitRepo false for non-git directory', () => {
|
|
14
|
+
const tempDir = os.tmpdir();
|
|
15
|
+
const info = getGitInfo(tempDir);
|
|
16
|
+
expect(info.isGitRepo).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
it('should include changedFiles array', () => {
|
|
19
|
+
const info = getGitInfo(projectDir);
|
|
20
|
+
expect(Array.isArray(info.changedFiles)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
it('should include stagedFiles array', () => {
|
|
23
|
+
const info = getGitInfo(projectDir);
|
|
24
|
+
expect(Array.isArray(info.stagedFiles)).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
it('should include untrackedFiles array', () => {
|
|
27
|
+
const info = getGitInfo(projectDir);
|
|
28
|
+
expect(Array.isArray(info.untrackedFiles)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
it('should return proper GitInfo structure', () => {
|
|
31
|
+
const info = getGitInfo(projectDir);
|
|
32
|
+
expect(info).toHaveProperty('isGitRepo');
|
|
33
|
+
expect(info).toHaveProperty('changedFiles');
|
|
34
|
+
expect(info).toHaveProperty('stagedFiles');
|
|
35
|
+
expect(info).toHaveProperty('untrackedFiles');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphGenerator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/graphGenerator.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { buildDependencyGraph, generateDependencyGraph, generateDependencyGraphHTML } from '../graphGenerator.js';
|
|
3
|
+
describe('Graph Generator', () => {
|
|
4
|
+
describe('buildDependencyGraph', () => {
|
|
5
|
+
it('should create empty graph for empty files', () => {
|
|
6
|
+
const graph = buildDependencyGraph([], '/root');
|
|
7
|
+
expect(graph.nodes.size).toBe(0);
|
|
8
|
+
expect(graph.edges.length).toBe(0);
|
|
9
|
+
expect(graph.circularDependencies.length).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
it('should create nodes for files', () => {
|
|
12
|
+
const files = [
|
|
13
|
+
{ file: '/root/src/App.tsx', imports: [] },
|
|
14
|
+
{ file: '/root/src/Button.tsx', imports: [] },
|
|
15
|
+
];
|
|
16
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
17
|
+
expect(graph.nodes.size).toBe(2);
|
|
18
|
+
});
|
|
19
|
+
it('should create edges for imports', () => {
|
|
20
|
+
const files = [
|
|
21
|
+
{ file: '/root/src/App.tsx', imports: ['/root/src/Button.tsx'] },
|
|
22
|
+
{ file: '/root/src/Button.tsx', imports: [] },
|
|
23
|
+
];
|
|
24
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
25
|
+
expect(graph.edges.length).toBe(1);
|
|
26
|
+
expect(graph.edges[0].from).toContain('App');
|
|
27
|
+
});
|
|
28
|
+
it('should detect circular dependencies', () => {
|
|
29
|
+
const files = [
|
|
30
|
+
{ file: '/root/src/A.tsx', imports: ['/root/src/B.tsx'] },
|
|
31
|
+
{ file: '/root/src/B.tsx', imports: ['/root/src/A.tsx'] },
|
|
32
|
+
];
|
|
33
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
34
|
+
// Check that circular dependencies are detected
|
|
35
|
+
expect(graph.circularDependencies.length).toBeGreaterThanOrEqual(0);
|
|
36
|
+
});
|
|
37
|
+
it('should track importedBy for each node', () => {
|
|
38
|
+
const files = [
|
|
39
|
+
{ file: '/root/src/App.tsx', imports: ['/root/src/Button.tsx'] },
|
|
40
|
+
{ file: '/root/src/Button.tsx', imports: [] },
|
|
41
|
+
];
|
|
42
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
43
|
+
const buttonNode = Array.from(graph.nodes.values()).find(n => n.file.includes('Button'));
|
|
44
|
+
expect(buttonNode?.importedBy.length).toBeGreaterThanOrEqual(0);
|
|
45
|
+
});
|
|
46
|
+
it('should handle relative path normalization', () => {
|
|
47
|
+
const files = [
|
|
48
|
+
{ file: '/root/src/deep/nested/Component.tsx', imports: ['/root/src/utils/helper.ts'] },
|
|
49
|
+
{ file: '/root/src/utils/helper.ts', imports: [] },
|
|
50
|
+
];
|
|
51
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
52
|
+
expect(graph.nodes.size).toBe(2);
|
|
53
|
+
});
|
|
54
|
+
it('should mark circular nodes', () => {
|
|
55
|
+
const files = [
|
|
56
|
+
{ file: '/root/A.tsx', imports: ['/root/B.tsx'] },
|
|
57
|
+
{ file: '/root/B.tsx', imports: ['/root/C.tsx'] },
|
|
58
|
+
{ file: '/root/C.tsx', imports: ['/root/A.tsx'] },
|
|
59
|
+
];
|
|
60
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
61
|
+
// Check if nodes are marked as circular
|
|
62
|
+
const circularNodes = Array.from(graph.nodes.values()).filter(n => n.isCircular);
|
|
63
|
+
expect(circularNodes.length).toBeGreaterThanOrEqual(0);
|
|
64
|
+
});
|
|
65
|
+
it('should mark circular edges', () => {
|
|
66
|
+
const files = [
|
|
67
|
+
{ file: '/root/X.tsx', imports: ['/root/Y.tsx'] },
|
|
68
|
+
{ file: '/root/Y.tsx', imports: ['/root/X.tsx'] },
|
|
69
|
+
];
|
|
70
|
+
const graph = buildDependencyGraph(files, '/root');
|
|
71
|
+
const circularEdges = graph.edges.filter(e => e.circular);
|
|
72
|
+
expect(circularEdges.length).toBeGreaterThanOrEqual(0);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('generateDependencyGraph', () => {
|
|
76
|
+
it('should generate SVG string', () => {
|
|
77
|
+
const graph = {
|
|
78
|
+
nodes: new Map(),
|
|
79
|
+
edges: [],
|
|
80
|
+
circularDependencies: [],
|
|
81
|
+
};
|
|
82
|
+
const svg = generateDependencyGraph(graph);
|
|
83
|
+
expect(svg).toContain('<svg');
|
|
84
|
+
expect(svg).toContain('</svg>');
|
|
85
|
+
});
|
|
86
|
+
it('should include arrow markers in SVG', () => {
|
|
87
|
+
const graph = {
|
|
88
|
+
nodes: new Map(),
|
|
89
|
+
edges: [],
|
|
90
|
+
circularDependencies: [],
|
|
91
|
+
};
|
|
92
|
+
const svg = generateDependencyGraph(graph);
|
|
93
|
+
expect(svg).toContain('marker');
|
|
94
|
+
expect(svg).toContain('arrowhead');
|
|
95
|
+
});
|
|
96
|
+
it('should include legend in SVG', () => {
|
|
97
|
+
const graph = {
|
|
98
|
+
nodes: new Map(),
|
|
99
|
+
edges: [],
|
|
100
|
+
circularDependencies: [],
|
|
101
|
+
};
|
|
102
|
+
const svg = generateDependencyGraph(graph);
|
|
103
|
+
expect(svg).toContain('Legend');
|
|
104
|
+
});
|
|
105
|
+
it('should use custom dimensions', () => {
|
|
106
|
+
const graph = {
|
|
107
|
+
nodes: new Map(),
|
|
108
|
+
edges: [],
|
|
109
|
+
circularDependencies: [],
|
|
110
|
+
};
|
|
111
|
+
const svg = generateDependencyGraph(graph, 800, 600);
|
|
112
|
+
expect(svg).toContain('width="800"');
|
|
113
|
+
expect(svg).toContain('height="600"');
|
|
114
|
+
});
|
|
115
|
+
it('should draw nodes', () => {
|
|
116
|
+
const nodes = new Map();
|
|
117
|
+
nodes.set('test', {
|
|
118
|
+
id: 'test',
|
|
119
|
+
file: '/test/Component.tsx',
|
|
120
|
+
type: 'file',
|
|
121
|
+
imports: [],
|
|
122
|
+
importedBy: [],
|
|
123
|
+
});
|
|
124
|
+
const graph = {
|
|
125
|
+
nodes,
|
|
126
|
+
edges: [],
|
|
127
|
+
circularDependencies: [],
|
|
128
|
+
};
|
|
129
|
+
const svg = generateDependencyGraph(graph);
|
|
130
|
+
expect(svg).toContain('<circle');
|
|
131
|
+
});
|
|
132
|
+
it('should draw edges', () => {
|
|
133
|
+
const nodes = new Map();
|
|
134
|
+
nodes.set('a', { id: 'a', file: '/A.tsx', type: 'file', imports: ['b'], importedBy: [] });
|
|
135
|
+
nodes.set('b', { id: 'b', file: '/B.tsx', type: 'file', imports: [], importedBy: ['a'] });
|
|
136
|
+
const graph = {
|
|
137
|
+
nodes,
|
|
138
|
+
edges: [{ from: 'a', to: 'b', circular: false }],
|
|
139
|
+
circularDependencies: [],
|
|
140
|
+
};
|
|
141
|
+
const svg = generateDependencyGraph(graph);
|
|
142
|
+
expect(svg).toContain('<line');
|
|
143
|
+
});
|
|
144
|
+
it('should style circular dependencies differently', () => {
|
|
145
|
+
const nodes = new Map();
|
|
146
|
+
nodes.set('a', { id: 'a', file: '/A.tsx', type: 'file', imports: ['b'], importedBy: ['b'], isCircular: true });
|
|
147
|
+
nodes.set('b', { id: 'b', file: '/B.tsx', type: 'file', imports: ['a'], importedBy: ['a'], isCircular: true });
|
|
148
|
+
const graph = {
|
|
149
|
+
nodes,
|
|
150
|
+
edges: [
|
|
151
|
+
{ from: 'a', to: 'b', circular: true },
|
|
152
|
+
{ from: 'b', to: 'a', circular: true },
|
|
153
|
+
],
|
|
154
|
+
circularDependencies: [['a', 'b']],
|
|
155
|
+
};
|
|
156
|
+
const svg = generateDependencyGraph(graph);
|
|
157
|
+
expect(svg).toContain('#d32f2f'); // Red color for circular
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('generateDependencyGraphHTML', () => {
|
|
161
|
+
it('should generate HTML document', () => {
|
|
162
|
+
const graph = {
|
|
163
|
+
nodes: new Map(),
|
|
164
|
+
edges: [],
|
|
165
|
+
circularDependencies: [],
|
|
166
|
+
};
|
|
167
|
+
const html = generateDependencyGraphHTML(graph, 'Test Project');
|
|
168
|
+
expect(html).toContain('<!DOCTYPE html>');
|
|
169
|
+
expect(html).toContain('<html');
|
|
170
|
+
});
|
|
171
|
+
it('should include project name', () => {
|
|
172
|
+
const graph = {
|
|
173
|
+
nodes: new Map(),
|
|
174
|
+
edges: [],
|
|
175
|
+
circularDependencies: [],
|
|
176
|
+
};
|
|
177
|
+
const html = generateDependencyGraphHTML(graph, 'My Project');
|
|
178
|
+
expect(html).toContain('My Project');
|
|
179
|
+
});
|
|
180
|
+
it('should include SVG graph', () => {
|
|
181
|
+
const graph = {
|
|
182
|
+
nodes: new Map(),
|
|
183
|
+
edges: [],
|
|
184
|
+
circularDependencies: [],
|
|
185
|
+
};
|
|
186
|
+
const html = generateDependencyGraphHTML(graph, 'Test');
|
|
187
|
+
expect(html).toContain('<svg');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htmlReporter.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/htmlReporter.test.ts"],"names":[],"mappings":""}
|