Explorar o código

fix(test): isolate LSP fs mocks

dhaern hai 3 meses
pai
achega
e4e4bd6f28
Modificáronse 2 ficheiros con 34 adicións e 40 borrados
  1. 16 16
      src/tools/lsp/config.test.ts
  2. 18 24
      src/tools/lsp/utils.test.ts

+ 16 - 16
src/tools/lsp/config.test.ts

@@ -1,41 +1,41 @@
-import { beforeEach, describe, expect, mock, test } from 'bun:test';
+import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test';
+import * as fs from 'node:fs';
 import { join } from 'node:path';
 
-// Mock fs and os BEFORE importing the modules that use them
-mock.module('fs', () => ({
-  existsSync: mock(() => false),
-}));
-
 mock.module('os', () => ({
   homedir: () => '/home/user',
 }));
 
 // Create a mock for which.sync
-const whichSyncMock = mock(() => null);
+const whichSyncMock = mock(((_cmd?: string) => null) as (...args: any[]) => string | null);
+let existsSyncSpy: ReturnType<typeof spyOn> | undefined;
 mock.module('which', () => ({
   sync: whichSyncMock,
   default: { sync: whichSyncMock },
 }));
 
-import { existsSync } from 'node:fs';
 // Now import the code to test
 import { findServerForExtension, isServerInstalled } from './config';
 
 describe('config', () => {
   beforeEach(() => {
-    (existsSync as any).mockClear();
-    (existsSync as any).mockImplementation(() => false);
+    existsSyncSpy = spyOn(fs, 'existsSync').mockImplementation(() => false);
     whichSyncMock.mockClear();
     whichSyncMock.mockReturnValue(null);
   });
 
+  afterEach(() => {
+    existsSyncSpy?.mockRestore();
+    existsSyncSpy = undefined;
+  });
+
   describe('isServerInstalled', () => {
     test('should return false if command is empty', () => {
       expect(isServerInstalled([])).toBe(false);
     });
 
     test('should detect absolute paths', () => {
-      (existsSync as any).mockImplementation(
+      (fs.existsSync as any).mockImplementation(
         (path: string) => path === '/usr/bin/lsp-server',
       );
       expect(isServerInstalled(['/usr/bin/lsp-server'])).toBe(true);
@@ -65,7 +65,7 @@ describe('config', () => {
         'typescript-language-server',
       );
 
-      (existsSync as any).mockImplementation(
+      (fs.existsSync as any).mockImplementation(
         (path: string) => path === localBin,
       );
 
@@ -95,7 +95,7 @@ describe('config', () => {
           ? join('/usr/bin', 'typescript-language-server')
           : null,
       );
-      (existsSync as any).mockImplementation((path: string) =>
+      (fs.existsSync as any).mockImplementation((path: string) =>
         path.includes('bun.lock'),
       );
       const result = findServerForExtension(
@@ -112,7 +112,7 @@ describe('config', () => {
       whichSyncMock.mockImplementation((cmd: string) =>
         cmd === 'deno' ? join('/usr/bin', 'deno') : null,
       );
-      (existsSync as any).mockImplementation((path: string) =>
+      (fs.existsSync as any).mockImplementation((path: string) =>
         path.includes('deno.json'),
       );
       const result = findServerForExtension('.ts', '/workspace/app/src/mod.ts');
@@ -144,7 +144,7 @@ describe('config', () => {
           ? join('/usr/bin', 'typescript-language-server')
           : null,
       );
-      (existsSync as any).mockImplementation((path: string) =>
+      (fs.existsSync as any).mockImplementation((path: string) =>
         path.includes('bun.lock'),
       );
 
@@ -160,7 +160,7 @@ describe('config', () => {
     });
 
     test('should return first applicable not_installed server if no match is launchable', () => {
-      (existsSync as any).mockImplementation((path: string) =>
+      (fs.existsSync as any).mockImplementation((path: string) =>
         path.includes('bun.lock'),
       );
       const result = findServerForExtension(

+ 18 - 24
src/tools/lsp/utils.test.ts

@@ -1,15 +1,5 @@
-import { beforeEach, describe, expect, mock, test } from 'bun:test';
-
-// Mock fs BEFORE importing modules
-mock.module('fs', () => ({
-  readFileSync: mock(() => ''),
-  writeFileSync: mock(),
-  unlinkSync: mock(),
-  existsSync: mock(() => true),
-  statSync: mock(() => ({ isDirectory: () => false })),
-}));
-
-import { readFileSync, unlinkSync, writeFileSync } from 'node:fs';
+import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from 'bun:test';
+import * as fs from 'node:fs';
 import {
   applyWorkspaceEdit,
   filterDiagnosticsBySeverity,
@@ -22,9 +12,13 @@ import {
 
 describe('utils', () => {
   beforeEach(() => {
-    (readFileSync as any).mockClear();
-    (writeFileSync as any).mockClear();
-    (unlinkSync as any).mockClear();
+    spyOn(fs, 'readFileSync').mockImplementation((() => '') as any);
+    spyOn(fs, 'writeFileSync').mockImplementation(() => undefined);
+    spyOn(fs, 'unlinkSync').mockImplementation(() => undefined);
+  });
+
+  afterEach(() => {
+    mock.restore();
   });
 
   describe('uriToPath', () => {
@@ -96,7 +90,7 @@ describe('utils', () => {
     test('should apply single file edit', () => {
       const uri = 'file:///test.ts';
       const filePath = uriToPath(uri);
-      (readFileSync as any).mockReturnValue('line1\nline2\nline3');
+      (fs.readFileSync as any).mockReturnValue('line1\nline2\nline3');
 
       const edit = {
         changes: {
@@ -115,12 +109,12 @@ describe('utils', () => {
       const result = applyWorkspaceEdit(edit as any);
       expect(result.success).toBe(true);
       expect(result.filesModified).toContain(filePath);
-      expect(writeFileSync).toHaveBeenCalled();
+      expect(fs.writeFileSync).toHaveBeenCalled();
     });
 
     test('should handle overlapping edits by sorting them in reverse order', () => {
       const uri = 'file:///test.ts';
-      (readFileSync as any).mockReturnValue('abcde');
+      (fs.readFileSync as any).mockReturnValue('abcde');
 
       const edit = {
         changes: {
@@ -145,7 +139,7 @@ describe('utils', () => {
 
       const result = applyWorkspaceEdit(edit as any);
       expect(result.success).toBe(true);
-      const writtenContent = (writeFileSync as any).mock.calls[0][1];
+      const writtenContent = (fs.writeFileSync as any).mock.calls[0][1];
       expect(writtenContent).toBe('1b3de');
     });
 
@@ -156,7 +150,7 @@ describe('utils', () => {
 
       const result = applyWorkspaceEdit(edit as any);
       expect(result.success).toBe(true);
-      expect(writeFileSync).toHaveBeenCalledWith(
+      expect(fs.writeFileSync).toHaveBeenCalledWith(
         uriToPath('file:///new.ts'),
         '',
         'utf-8',
@@ -166,7 +160,7 @@ describe('utils', () => {
     test('should handle rename file operation', () => {
       const oldUri = 'file:///old.ts';
       const newUri = 'file:///new.ts';
-      (readFileSync as any).mockReturnValue('some content');
+      (fs.readFileSync as any).mockReturnValue('some content');
 
       const edit = {
         documentChanges: [{ kind: 'rename', oldUri, newUri }],
@@ -174,12 +168,12 @@ describe('utils', () => {
 
       const result = applyWorkspaceEdit(edit as any);
       expect(result.success).toBe(true);
-      expect(writeFileSync).toHaveBeenCalledWith(
+      expect(fs.writeFileSync).toHaveBeenCalledWith(
         uriToPath(newUri),
         'some content',
         'utf-8',
       );
-      expect(unlinkSync).toHaveBeenCalledWith(uriToPath(oldUri));
+      expect(fs.unlinkSync).toHaveBeenCalledWith(uriToPath(oldUri));
     });
 
     test('should handle delete file operation', () => {
@@ -190,7 +184,7 @@ describe('utils', () => {
 
       const result = applyWorkspaceEdit(edit as any);
       expect(result.success).toBe(true);
-      expect(unlinkSync).toHaveBeenCalledWith(uriToPath(uri));
+      expect(fs.unlinkSync).toHaveBeenCalledWith(uriToPath(uri));
     });
 
     test('should return error if no edit provided', () => {