onbuzz 3.6.1 → 3.6.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 (83) hide show
  1. package/package.json +1 -1
  2. package/src/__test-utils__/fixtures/malformedJson.js +31 -0
  3. package/src/__test-utils__/globalSetup.js +9 -0
  4. package/src/__test-utils__/globalTeardown.js +12 -0
  5. package/src/__test-utils__/mockFactories.js +101 -0
  6. package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -0
  7. package/src/analyzers/__tests__/ConfigValidator.test.js +362 -0
  8. package/src/analyzers/__tests__/ESLintAnalyzer.test.js +271 -0
  9. package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -0
  10. package/src/analyzers/__tests__/PrettierFormatter.test.js +197 -0
  11. package/src/analyzers/__tests__/PythonAnalyzer.test.js +208 -0
  12. package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -0
  13. package/src/analyzers/__tests__/SparrowAnalyzer.test.js +270 -0
  14. package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -0
  15. package/src/core/__tests__/agentPool.test.js +601 -0
  16. package/src/core/__tests__/agentScheduler.test.js +576 -0
  17. package/src/core/__tests__/contextManager.test.js +252 -0
  18. package/src/core/__tests__/flowExecutor.test.js +262 -0
  19. package/src/core/__tests__/messageProcessor.test.js +627 -0
  20. package/src/core/__tests__/orchestrator.test.js +257 -0
  21. package/src/core/__tests__/stateManager.test.js +375 -0
  22. package/src/core/agentPool.js +11 -1
  23. package/src/index.js +25 -9
  24. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +3 -5
  25. package/src/services/__tests__/agentActivityService.test.js +319 -0
  26. package/src/services/__tests__/apiKeyManager.test.js +206 -0
  27. package/src/services/__tests__/benchmarkService.test.js +184 -0
  28. package/src/services/__tests__/budgetService.test.js +211 -0
  29. package/src/services/__tests__/contextInjectionService.test.js +205 -0
  30. package/src/services/__tests__/conversationCompactionService.test.js +280 -0
  31. package/src/services/__tests__/credentialVault.test.js +469 -0
  32. package/src/services/__tests__/errorHandler.test.js +314 -0
  33. package/src/services/__tests__/fileAttachmentService.test.js +278 -0
  34. package/src/services/__tests__/flowContextService.test.js +199 -0
  35. package/src/services/__tests__/memoryService.test.js +450 -0
  36. package/src/services/__tests__/modelRouterService.test.js +388 -0
  37. package/src/services/__tests__/modelsService.test.js +261 -0
  38. package/src/services/__tests__/portRegistry.test.js +123 -0
  39. package/src/services/__tests__/projectDetector.test.js +34 -0
  40. package/src/services/__tests__/promptService.test.js +242 -0
  41. package/src/services/__tests__/qualityInspector.test.js +97 -0
  42. package/src/services/__tests__/scheduleService.test.js +308 -0
  43. package/src/services/__tests__/serviceRegistry.test.js +74 -0
  44. package/src/services/__tests__/skillsService.test.js +402 -0
  45. package/src/services/__tests__/tokenCountingService.test.js +48 -0
  46. package/src/tools/__tests__/agentCommunicationTool.test.js +500 -0
  47. package/src/tools/__tests__/agentDelayTool.test.js +342 -0
  48. package/src/tools/__tests__/asyncToolManager.test.js +344 -0
  49. package/src/tools/__tests__/baseTool.test.js +420 -0
  50. package/src/tools/__tests__/codeMapTool.test.js +348 -0
  51. package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -0
  52. package/src/tools/__tests__/fileTreeTool.test.js +274 -0
  53. package/src/tools/__tests__/filesystemTool.test.js +717 -0
  54. package/src/tools/__tests__/helpTool.test.js +204 -0
  55. package/src/tools/__tests__/jobDoneTool.test.js +296 -0
  56. package/src/tools/__tests__/memoryTool.test.js +297 -0
  57. package/src/tools/__tests__/seekTool.test.js +282 -0
  58. package/src/tools/__tests__/skillsTool.test.js +226 -0
  59. package/src/tools/__tests__/staticAnalysisTool.test.js +509 -0
  60. package/src/tools/__tests__/taskManagerTool.test.js +725 -0
  61. package/src/tools/__tests__/terminalTool.test.js +384 -0
  62. package/src/tools/__tests__/userPromptTool.test.js +297 -0
  63. package/src/tools/__tests__/webTool.e2e.test.js +25 -11
  64. package/src/tools/webTool.js +6 -12
  65. package/src/types/__tests__/agent.test.js +499 -0
  66. package/src/types/__tests__/contextReference.test.js +606 -0
  67. package/src/types/__tests__/conversation.test.js +555 -0
  68. package/src/types/__tests__/toolCommand.test.js +584 -0
  69. package/src/types/contextReference.js +1 -1
  70. package/src/utilities/__tests__/attachmentValidator.test.js +80 -0
  71. package/src/utilities/__tests__/configManager.test.js +397 -0
  72. package/src/utilities/__tests__/constants.test.js +49 -0
  73. package/src/utilities/__tests__/directoryAccessManager.test.js +388 -0
  74. package/src/utilities/__tests__/fileProcessor.test.js +104 -0
  75. package/src/utilities/__tests__/jsonRepair.test.js +104 -0
  76. package/src/utilities/__tests__/logger.test.js +129 -0
  77. package/src/utilities/__tests__/platformUtils.test.js +87 -0
  78. package/src/utilities/__tests__/structuredFileValidator.test.js +263 -0
  79. package/src/utilities/__tests__/tagParser.test.js +887 -0
  80. package/src/utilities/__tests__/toolConstants.test.js +94 -0
  81. package/src/utilities/tagParser.js +2 -2
  82. package/src/tools/browserTool.js +0 -897
  83. package/src/utilities/platformUtils.test.js +0 -98
@@ -0,0 +1,270 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { createMockLogger } from '../../__test-utils__/mockFactories.js';
3
+
4
+ // Mock fs/promises
5
+ const mockAccess = jest.fn();
6
+ const mockStat = jest.fn();
7
+ jest.unstable_mockModule('fs/promises', () => ({
8
+ default: { access: mockAccess, stat: mockStat },
9
+ access: mockAccess,
10
+ stat: mockStat
11
+ }));
12
+
13
+ // Mock sparrow-sast
14
+ const mockScan = jest.fn();
15
+ const mockGetRegistry = jest.fn();
16
+ const mockGetAllAnalyzers = jest.fn();
17
+ jest.unstable_mockModule('sparrow-sast', () => ({
18
+ default: {
19
+ scan: mockScan,
20
+ getRegistry: mockGetRegistry,
21
+ getAllAnalyzers: mockGetAllAnalyzers,
22
+ Language: {
23
+ Python: 'python',
24
+ JavaScript: 'javascript',
25
+ TypeScript: 'typescript',
26
+ Go: 'go',
27
+ Java: 'java',
28
+ Ruby: 'ruby',
29
+ Rust: 'rust',
30
+ PHP: 'php',
31
+ CSharp: 'csharp',
32
+ Bash: 'bash',
33
+ HTML: 'html',
34
+ CSS: 'css'
35
+ }
36
+ },
37
+ scan: mockScan,
38
+ getRegistry: mockGetRegistry,
39
+ getAllAnalyzers: mockGetAllAnalyzers,
40
+ Language: {
41
+ Python: 'python',
42
+ JavaScript: 'javascript',
43
+ TypeScript: 'typescript'
44
+ }
45
+ }));
46
+
47
+ const { default: SparrowAnalyzer } = await import('../SparrowAnalyzer.js');
48
+
49
+ describe('SparrowAnalyzer', () => {
50
+ let analyzer;
51
+ let logger;
52
+
53
+ beforeEach(() => {
54
+ logger = createMockLogger();
55
+ analyzer = new SparrowAnalyzer(logger);
56
+ analyzer.initialized = false;
57
+ analyzer.sparrow = null;
58
+ jest.clearAllMocks();
59
+ });
60
+
61
+ // ── Constructor ──
62
+ test('constructor initializes with defaults', () => {
63
+ expect(analyzer.logger).toBe(logger);
64
+ expect(analyzer.sparrow).toBeNull();
65
+ expect(analyzer.initialized).toBe(false);
66
+ });
67
+
68
+ // ── getSupportedLanguages ──
69
+ test('getSupportedLanguages returns all supported languages', () => {
70
+ const langs = analyzer.getSupportedLanguages();
71
+ expect(langs).toContain('python');
72
+ expect(langs).toContain('javascript');
73
+ expect(langs).toContain('typescript');
74
+ expect(langs).toContain('go');
75
+ expect(langs.length).toBe(12);
76
+ });
77
+
78
+ // ── getSupportedExtensions ──
79
+ test('getSupportedExtensions returns file extensions', () => {
80
+ const exts = analyzer.getSupportedExtensions();
81
+ expect(exts).toContain('.py');
82
+ expect(exts).toContain('.js');
83
+ expect(exts).toContain('.ts');
84
+ expect(exts).toContain('.go');
85
+ });
86
+
87
+ // ── isSupported ──
88
+ test('isSupported returns true for supported files', () => {
89
+ expect(analyzer.isSupported('test.py')).toBe(true);
90
+ expect(analyzer.isSupported('test.js')).toBe(true);
91
+ expect(analyzer.isSupported('test.ts')).toBe(true);
92
+ expect(analyzer.isSupported('test.go')).toBe(true);
93
+ });
94
+
95
+ test('isSupported returns false for unsupported files', () => {
96
+ expect(analyzer.isSupported('test.txt')).toBe(false);
97
+ expect(analyzer.isSupported('test.md')).toBe(false);
98
+ });
99
+
100
+ // ── ensureInitialized ──
101
+ test('ensureInitialized loads sparrow module', async () => {
102
+ await analyzer.ensureInitialized();
103
+ expect(analyzer.initialized).toBe(true);
104
+ expect(analyzer.sparrow).toBeDefined();
105
+ });
106
+
107
+ test('ensureInitialized skips if already initialized', async () => {
108
+ analyzer.initialized = true;
109
+ analyzer.sparrow = { scan: jest.fn() };
110
+ await analyzer.ensureInitialized();
111
+ // No error thrown
112
+ });
113
+
114
+ // ── transformIssue ──
115
+ test('transformIssue converts sparrow issue to standard format', () => {
116
+ const issue = {
117
+ id: 'SQL_INJECTION',
118
+ filepath: '/project/test.py',
119
+ range: { start: { row: 4, column: 0 }, end: { row: 4, column: 20 } },
120
+ severity: 'error',
121
+ category: 'security',
122
+ message: 'Possible SQL injection'
123
+ };
124
+
125
+ const result = analyzer.transformIssue(issue, '/project/test.py');
126
+ expect(result.id).toBe('SQL_INJECTION');
127
+ expect(result.line).toBe(5); // 0-based to 1-based
128
+ expect(result.column).toBe(1);
129
+ expect(result.endLine).toBe(5);
130
+ expect(result.severity).toBe('error');
131
+ expect(result.source).toBe('sparrow');
132
+ expect(result.fixable).toBe(false);
133
+ });
134
+
135
+ test('transformIssue handles missing range', () => {
136
+ const issue = {
137
+ id: 'TEST',
138
+ filepath: '/project/test.py',
139
+ message: 'Test issue'
140
+ };
141
+
142
+ const result = analyzer.transformIssue(issue, '/project/test.py');
143
+ expect(result.line).toBe(1);
144
+ expect(result.column).toBe(1);
145
+ });
146
+
147
+ // ── groupByCategory ──
148
+ test('groupByCategory groups issues correctly', () => {
149
+ const issues = [
150
+ { category: 'security' },
151
+ { category: 'security' },
152
+ { category: 'performance' },
153
+ { category: undefined }
154
+ ];
155
+ const groups = analyzer.groupByCategory(issues);
156
+ expect(groups.security).toBe(2);
157
+ expect(groups.performance).toBe(1);
158
+ expect(groups.other).toBe(1);
159
+ });
160
+
161
+ // ── groupByLanguage ──
162
+ test('groupByLanguage groups file results by language', () => {
163
+ const fileResults = [
164
+ { file: 'test.py', issues: [] },
165
+ { file: 'app.js', issues: [] },
166
+ { file: 'util.js', issues: [] },
167
+ { file: 'main.go', issues: [] }
168
+ ];
169
+ const groups = analyzer.groupByLanguage(fileResults);
170
+ expect(groups.python).toBe(1);
171
+ expect(groups.javascript).toBe(2);
172
+ expect(groups.go).toBe(1);
173
+ });
174
+
175
+ test('groupByLanguage maps unknown extensions to other', () => {
176
+ const fileResults = [{ file: 'readme.txt', issues: [] }];
177
+ const groups = analyzer.groupByLanguage(fileResults);
178
+ expect(groups.other).toBe(1);
179
+ });
180
+
181
+ // ── scanFile ──
182
+ test('scanFile returns skipped result for unsupported file', async () => {
183
+ mockAccess.mockResolvedValue(undefined);
184
+
185
+ const result = await analyzer.scanFile('/project/readme.txt');
186
+ expect(result.success).toBe(true);
187
+ expect(result.skipped).toBe(true);
188
+ expect(result.issues).toEqual([]);
189
+ });
190
+
191
+ test('scanFile returns issues for supported file', async () => {
192
+ mockAccess.mockResolvedValue(undefined);
193
+ mockScan.mockResolvedValue([
194
+ { id: 'XSS', filepath: '/project/test.js', message: 'XSS vulnerability', severity: 'error', category: 'security' }
195
+ ]);
196
+
197
+ const result = await analyzer.scanFile('/project/test.js');
198
+ expect(result.success).toBe(true);
199
+ expect(result.issues.length).toBe(1);
200
+ expect(result.summary.total).toBe(1);
201
+ });
202
+
203
+ test('scanFile handles scan errors', async () => {
204
+ mockAccess.mockRejectedValue(new Error('File not found'));
205
+
206
+ const result = await analyzer.scanFile('/project/test.js');
207
+ expect(result.success).toBe(false);
208
+ expect(result.error).toBeDefined();
209
+ });
210
+
211
+ // ── scanProject ──
212
+ test('scanProject scans directory and returns grouped results', async () => {
213
+ mockStat.mockResolvedValue({ isDirectory: () => true });
214
+ mockScan.mockResolvedValue([
215
+ { id: 'XSS', filepath: '/project/app.js', message: 'XSS', severity: 'error', category: 'security' },
216
+ { id: 'SQLI', filepath: '/project/db.py', message: 'SQL injection', severity: 'critical', category: 'security' }
217
+ ]);
218
+
219
+ const result = await analyzer.scanProject('/project');
220
+ expect(result.success).toBe(true);
221
+ expect(result.isDirectory).toBe(true);
222
+ expect(result.files.length).toBe(2);
223
+ expect(result.summary.totalFiles).toBe(2);
224
+ expect(result.summary.totalIssues).toBe(2);
225
+ });
226
+
227
+ test('scanProject handles scan errors', async () => {
228
+ mockStat.mockRejectedValue(new Error('Path not found'));
229
+
230
+ const result = await analyzer.scanProject('/nonexistent');
231
+ expect(result.success).toBe(false);
232
+ expect(result.error).toBeDefined();
233
+ });
234
+
235
+ test('scanProject applies language filter', async () => {
236
+ mockStat.mockResolvedValue({ isDirectory: () => true });
237
+ mockScan.mockResolvedValue([]);
238
+
239
+ const result = await analyzer.scanProject('/project', { languages: ['python', 'javascript'] });
240
+ expect(result.success).toBe(true);
241
+ });
242
+
243
+ // ── getCheckersInfo ──
244
+ test('getCheckersInfo returns analyzer info', async () => {
245
+ mockGetRegistry.mockReturnValue({});
246
+ mockGetAllAnalyzers.mockReturnValue([
247
+ { name: 'sql-injection', language: 'python', category: 'security', severity: 'error' }
248
+ ]);
249
+
250
+ const info = await analyzer.getCheckersInfo();
251
+ expect(info.length).toBe(1);
252
+ expect(info[0].name).toBe('sql-injection');
253
+ });
254
+
255
+ test('getCheckersInfo returns empty on error', async () => {
256
+ mockGetRegistry.mockReturnValue(null);
257
+
258
+ const info = await analyzer.getCheckersInfo();
259
+ expect(info).toEqual([]);
260
+ });
261
+
262
+ test('getCheckersInfo handles missing getRegistry', async () => {
263
+ // sparrow without getRegistry
264
+ analyzer.sparrow = {};
265
+ analyzer.initialized = true;
266
+
267
+ const info = await analyzer.getCheckersInfo();
268
+ expect(info).toEqual([]);
269
+ });
270
+ });
@@ -0,0 +1,187 @@
1
+ import { jest, describe, test, expect, beforeEach } from '@jest/globals';
2
+ import { createMockLogger } from '../../__test-utils__/mockFactories.js';
3
+ import TypeScriptAnalyzer from '../TypeScriptAnalyzer.js';
4
+
5
+ describe('TypeScriptAnalyzer', () => {
6
+ let analyzer;
7
+ let logger;
8
+
9
+ beforeEach(() => {
10
+ logger = createMockLogger();
11
+ analyzer = new TypeScriptAnalyzer(logger);
12
+ });
13
+
14
+ // ── Constructor ──
15
+ test('constructor sets logger and compiler options', () => {
16
+ expect(analyzer.logger).toBe(logger);
17
+ expect(analyzer.compilerOptions).toBeDefined();
18
+ expect(analyzer.compilerOptions.noEmit).toBe(true);
19
+ expect(analyzer.compilerOptions.strict).toBe(true);
20
+ expect(analyzer.compilerOptions.allowJs).toBe(false);
21
+ });
22
+
23
+ test('constructor works without logger', () => {
24
+ const a = new TypeScriptAnalyzer();
25
+ expect(a.logger).toBeNull();
26
+ });
27
+
28
+ // ── analyze ──
29
+ test('analyze returns empty array for valid TypeScript', async () => {
30
+ const content = 'const x: number = 42;\n';
31
+ const result = await analyzer.analyze('test.ts', content);
32
+ expect(Array.isArray(result)).toBe(true);
33
+ });
34
+
35
+ test('analyze returns diagnostics for syntax errors', async () => {
36
+ const content = 'const x: number = ;\nfunction( { {{\n';
37
+ const result = await analyzer.analyze('broken.ts', content);
38
+ expect(result.length).toBeGreaterThan(0);
39
+ expect(result[0]).toHaveProperty('severity');
40
+ expect(result[0]).toHaveProperty('message');
41
+ expect(result[0]).toHaveProperty('file');
42
+ });
43
+
44
+ test('analyze returns diagnostics with proper rule format', async () => {
45
+ const content = 'const x: number = "string";\n';
46
+ const result = await analyzer.analyze('types.ts', content);
47
+ // Should find type error or at least return an array
48
+ expect(Array.isArray(result)).toBe(true);
49
+ for (const diag of result) {
50
+ expect(diag.rule).toMatch(/^TS\d+$/);
51
+ }
52
+ });
53
+
54
+ test('analyze returns error diagnostic when analysis throws', async () => {
55
+ // Force an error by corrupting the analyzer
56
+ const badAnalyzer = new TypeScriptAnalyzer(logger);
57
+ badAnalyzer.compilerOptions = null; // Will cause createProgram to fail
58
+
59
+ const result = await badAnalyzer.analyze('test.ts', 'const x = 1;');
60
+ // Either returns normally or returns an error diagnostic
61
+ expect(Array.isArray(result)).toBe(true);
62
+ });
63
+
64
+ test('analyze handles empty content', async () => {
65
+ const result = await analyzer.analyze('empty.ts', '');
66
+ expect(Array.isArray(result)).toBe(true);
67
+ });
68
+
69
+ test('analyze handles TSX content', async () => {
70
+ const content = 'const el = <div>hello</div>;\n';
71
+ const result = await analyzer.analyze('component.tsx', content);
72
+ expect(Array.isArray(result)).toBe(true);
73
+ });
74
+
75
+ // ── getSyntacticDiagnostics ──
76
+ test('getSyntacticDiagnostics returns empty for valid source', async () => {
77
+ const ts = (await import('typescript')).default;
78
+ const sourceFile = ts.createSourceFile('test.ts', 'const x = 1;', ts.ScriptTarget.Latest, true);
79
+ const result = analyzer.getSyntacticDiagnostics(sourceFile);
80
+ expect(Array.isArray(result)).toBe(true);
81
+ });
82
+
83
+ // ── getSemanticDiagnostics ──
84
+ test('getSemanticDiagnostics returns array for valid content', async () => {
85
+ const result = await analyzer.getSemanticDiagnostics('test.ts', 'const x: number = 1;');
86
+ expect(Array.isArray(result)).toBe(true);
87
+ });
88
+
89
+ test('getSemanticDiagnostics detects type errors', async () => {
90
+ const content = 'const x: number = "hello";';
91
+ const result = await analyzer.getSemanticDiagnostics('types.ts', content);
92
+ expect(Array.isArray(result)).toBe(true);
93
+ // Should have at least one type error
94
+ if (result.length > 0) {
95
+ expect(result[0]).toHaveProperty('severity');
96
+ }
97
+ });
98
+
99
+ // ── formatDiagnostic ──
100
+ test('formatDiagnostic handles diagnostic with file and position', async () => {
101
+ const ts = (await import('typescript')).default;
102
+ const sourceFile = ts.createSourceFile('test.ts', 'const x = 1;\nconst y = 2;', ts.ScriptTarget.Latest, true);
103
+
104
+ const diagnostic = {
105
+ file: sourceFile,
106
+ start: 14, // position in source
107
+ category: ts.DiagnosticCategory.Error,
108
+ messageText: 'Test error message',
109
+ code: 2322
110
+ };
111
+
112
+ const result = analyzer.formatDiagnostic(diagnostic, sourceFile);
113
+ expect(result.line).toBeGreaterThan(0);
114
+ expect(result.column).toBeGreaterThan(0);
115
+ expect(result.severity).toBe('error');
116
+ expect(result.rule).toBe('TS2322');
117
+ expect(result.message).toBe('Test error message');
118
+ });
119
+
120
+ test('formatDiagnostic handles diagnostic without file', async () => {
121
+ const ts = (await import('typescript')).default;
122
+ const sourceFile = ts.createSourceFile('test.ts', 'const x = 1;', ts.ScriptTarget.Latest, true);
123
+
124
+ const diagnostic = {
125
+ start: 0,
126
+ category: ts.DiagnosticCategory.Warning,
127
+ messageText: 'Warning message',
128
+ code: 1000
129
+ };
130
+
131
+ const result = analyzer.formatDiagnostic(diagnostic, sourceFile);
132
+ expect(result.severity).toBe('warning');
133
+ expect(result.category).toBe('syntax'); // code 1000 is in syntax range
134
+ });
135
+
136
+ test('formatDiagnostic handles suggestion category', async () => {
137
+ const ts = (await import('typescript')).default;
138
+
139
+ const diagnostic = {
140
+ category: ts.DiagnosticCategory.Suggestion,
141
+ messageText: 'Suggestion',
142
+ code: 9999
143
+ };
144
+
145
+ const result = analyzer.formatDiagnostic(diagnostic, null);
146
+ expect(result.severity).toBe('info');
147
+ });
148
+
149
+ test('formatDiagnostic categorizes import errors by message', async () => {
150
+ const ts = (await import('typescript')).default;
151
+
152
+ const diagnostic = {
153
+ category: ts.DiagnosticCategory.Error,
154
+ messageText: 'Cannot find module "foo"',
155
+ code: 5000
156
+ };
157
+
158
+ const result = analyzer.formatDiagnostic(diagnostic, null);
159
+ expect(result.category).toBe('import');
160
+ });
161
+
162
+ test('formatDiagnostic categorizes type errors by code range', async () => {
163
+ const ts = (await import('typescript')).default;
164
+
165
+ const diagnostic = {
166
+ category: ts.DiagnosticCategory.Error,
167
+ messageText: 'Some error about assignment',
168
+ code: 2500
169
+ };
170
+
171
+ const result = analyzer.formatDiagnostic(diagnostic, null);
172
+ expect(result.category).toBe('type');
173
+ });
174
+
175
+ test('formatDiagnostic categorizes type errors by message content', async () => {
176
+ const ts = (await import('typescript')).default;
177
+
178
+ const diagnostic = {
179
+ category: ts.DiagnosticCategory.Error,
180
+ messageText: 'Property of type X',
181
+ code: 5000
182
+ };
183
+
184
+ const result = analyzer.formatDiagnostic(diagnostic, null);
185
+ expect(result.category).toBe('type');
186
+ });
187
+ });