import { describe, it, expect, vi, beforeEach, Mock } from "vitest"; // Mock modules with inline factories to avoid vitest hoisting issues. vi.mock("electron", () => { const dialogMocks = { showOpenDialog: vi.fn(), showSaveDialog: vi.fn(), }; return { dialog: dialogMocks }; }); vi.mock("node:fs", () => { const fsMocks = { existsSync: vi.fn(), readFileSync: vi.fn(), writeFileSync: vi.fn(), createReadStream: vi.fn(), mkdirSync: vi.fn(), }; return { default: fsMocks, existsSync: fsMocks.existsSync, readFileSync: fsMocks.readFileSync, writeFileSync: fsMocks.writeFileSync, createReadStream: fsMocks.createReadStream, mkdirSync: fsMocks.mkdirSync, }; }); vi.mock("fs/promises", () => ({ readFile: vi.fn(), writeFile: vi.fn(), stat: vi.fn(), rm: vi.fn(), })); import { dialog } from "electron"; import fs from "node:fs"; import * as fsp from "fs/promises"; import path from "node:path"; describe("File Operations and Utilities", () => { beforeEach(() => { vi.clearAllMocks(); }); describe("select-file IPC handler", () => { it("should handle successful file selection", async () => { const mockResult = { canceled: false, filePaths: ["/path/to/file1.txt", "/path/to/file2.pdf"], }; (dialog.showOpenDialog as Mock).mockResolvedValue(mockResult); const result = await dialog.showOpenDialog({} as any, { properties: ["openFile", "multiSelections"], }); expect(result.canceled).toBe(false); expect(result.filePaths).toHaveLength(2); expect(result.filePaths[0]).toContain(".txt"); expect(result.filePaths[1]).toContain(".pdf"); }); it("should handle cancelled file selection", async () => { const mockResult = { canceled: true, filePaths: [], }; (dialog.showOpenDialog as Mock).mockResolvedValue(mockResult); const result = await dialog.showOpenDialog({} as any, { properties: ["openFile", "multiSelections"], }); expect(result.canceled).toBe(true); expect(result.filePaths).toHaveLength(0); }); it("should handle file selection with filters", async () => { const options = { properties: ["openFile"] as const, filters: [ { name: "Text Files", extensions: ["txt", "md"] }, { name: "PDF Files", extensions: ["pdf"] }, { name: "All Files", extensions: ["*"] }, ], }; expect(options.filters).toHaveLength(3); expect(options.filters[0].extensions).toContain("txt"); expect(options.filters[1].extensions).toContain("pdf"); }); it("should process successful file selection result", () => { const result = { canceled: false, filePaths: ["/path/to/selected/file.txt"], }; if (!result.canceled && result.filePaths.length > 0) { const firstFile = result.filePaths[0]; const fileName = path.basename(firstFile); const fileExt = path.extname(firstFile); expect(fileName).toBe("file.txt"); expect(fileExt).toBe(".txt"); } }); }); describe("read-file IPC handler", () => { it("should successfully read file content", async () => { const mockContent = "This is the file content\nWith multiple lines"; (fsp.readFile as Mock).mockResolvedValue(mockContent); const content = await fsp.readFile("/path/to/file.txt", "utf-8"); expect(content).toBe(mockContent); expect(content).toContain("multiple lines"); }); it("should handle file read errors", async () => { const error = new Error("ENOENT: no such file or directory"); (fsp.readFile as Mock).mockRejectedValue(error); try { await fsp.readFile("/nonexistent/file.txt", "utf-8"); } catch (e) { expect(e).toBeInstanceOf(Error); expect((e as Error).message).toContain("no such file or directory"); } }); it("should handle different file encodings", async () => { const mockContent = Buffer.from("Binary content"); (fsp.readFile as Mock).mockResolvedValue(mockContent); const content = await fsp.readFile("/path/to/binary.bin"); expect(Buffer.isBuffer(content)).toBe(true); }); it("should validate file path", () => { const filePath = path.normalize("/path/to/file.txt"); const isAbsolute = path.isAbsolute(filePath); const normalizedPath = path.normalize(filePath); expect(isAbsolute).toBe(true); expect(normalizedPath).toBe(filePath); }); }); describe("reveal-in-folder IPC handler", () => { it("should handle valid file path", () => { const filePath = "/Users/test/Documents/file.txt"; const isValid = path.isAbsolute(filePath) && filePath.length > 0; expect(isValid).toBe(true); }); it("should handle invalid file path", () => { const filePath = ""; const isValid = path.isAbsolute(filePath) && filePath.length > 0; expect(isValid).toBe(false); }); it("should normalize file path", () => { const filePath = "/Users/test/../test/Documents/./file.txt"; const normalized = path.normalize(filePath); expect(normalized).toBe(path.normalize("/Users/test/Documents/file.txt")); }); it("should extract directory from file path", () => { const filePath = "/Users/test/Documents/file.txt"; const directory = path.dirname(filePath); expect(path.normalize(directory)).toBe( path.normalize("/Users/test/Documents") ); }); }); describe("File System Utilities", () => { it("should check file existence", () => { (fs.existsSync as Mock).mockReturnValue(true); const exists = fs.existsSync("/path/to/file.txt"); expect(exists).toBe(true); }); it("should handle non-existent files", () => { (fs.existsSync as Mock).mockReturnValue(false); const exists = fs.existsSync("/path/to/nonexistent.txt"); expect(exists).toBe(false); }); it("should create directory path", () => { const dirPath = "/path/to/new/directory"; const mockMkdirSync = vi.fn(); vi.mocked(fs).mkdirSync = mockMkdirSync; fs.mkdirSync(dirPath, { recursive: true }); expect(mockMkdirSync).toHaveBeenCalledWith(dirPath, { recursive: true }); }); it("should handle path operations", () => { const filePath = "/Users/test/Documents/file.txt"; const basename = path.basename(filePath); const dirname = path.dirname(filePath); const extname = path.extname(filePath); const parsed = path.parse(filePath); expect(basename).toBe("file.txt"); expect(path.normalize(dirname)).toBe( path.normalize("/Users/test/Documents") ); expect(extname).toBe(".txt"); expect(parsed.name).toBe("file"); expect(parsed.ext).toBe(".txt"); }); }); describe("File Validation", () => { it("should validate file extension", () => { const allowedExtensions = [".txt", ".md", ".json", ".pdf"]; const filePath = "/path/to/document.pdf"; const fileExt = path.extname(filePath); const isAllowed = allowedExtensions.includes(fileExt); expect(isAllowed).toBe(true); }); it("should reject invalid file extension", () => { const allowedExtensions = [".txt", ".md", ".json"]; const filePath = "/path/to/executable.exe"; const fileExt = path.extname(filePath); const isAllowed = allowedExtensions.includes(fileExt); expect(isAllowed).toBe(false); }); it("should validate file size", () => { const maxSize = 10 * 1024 * 1024; // 10MB const mockStats = { size: 5 * 1024 * 1024 }; // 5MB const isValidSize = mockStats.size <= maxSize; expect(isValidSize).toBe(true); }); it("should reject files that are too large", () => { const maxSize = 10 * 1024 * 1024; // 10MB const mockStats = { size: 20 * 1024 * 1024 }; // 20MB const isValidSize = mockStats.size <= maxSize; expect(isValidSize).toBe(false); }); }); describe("File Content Processing", () => { it("should process text file content", () => { const content = "Line 1\nLine 2\nLine 3"; const lines = content.split("\n"); expect(lines).toHaveLength(3); expect(lines[0]).toBe("Line 1"); expect(lines[2]).toBe("Line 3"); }); it("should handle empty file content", () => { const content = ""; const lines = content.split("\n"); expect(lines).toHaveLength(1); expect(lines[0]).toBe(""); }); it("should process CSV-like content", () => { const content = "name,age,email\nJohn,30,john@example.com\nJane,25,jane@example.com"; const lines = content.split("\n"); const headers = lines[0].split(","); expect(headers).toEqual(["name", "age", "email"]); expect(lines).toHaveLength(3); }); it("should handle binary file detection", () => { const textContent = "This is regular text content"; const binaryContent = Buffer.from([0x00, 0x01, 0x02, 0xff]); const isText = typeof textContent === "string"; const isBinary = Buffer.isBuffer(binaryContent); expect(isText).toBe(true); expect(isBinary).toBe(true); }); }); describe("File Stream Operations", () => { it("should create readable stream", () => { const mockCreateReadStream = vi.fn().mockReturnValue({ pipe: vi.fn(), on: vi.fn(), destroy: vi.fn(), }); vi.mocked(fs).createReadStream = mockCreateReadStream; const stream = fs.createReadStream("/path/to/file.txt"); expect(mockCreateReadStream).toHaveBeenCalledWith("/path/to/file.txt"); expect(stream.pipe).toBeDefined(); expect(stream.on).toBeDefined(); }); it("should handle stream errors", () => { const mockStream = { on: vi.fn((event, callback) => { if (event === "error") { setTimeout(() => callback(new Error("Stream error")), 0); } }), destroy: vi.fn(), }; let errorReceived = false; mockStream.on("error", (error) => { errorReceived = true; expect(error.message).toBe("Stream error"); }); setTimeout(() => { expect(errorReceived).toBe(true); }, 10); }); it("should cleanup stream resources", () => { const mockStream = { destroy: vi.fn(), on: vi.fn(), }; // Simulate cleanup if (mockStream && typeof mockStream.destroy === "function") { mockStream.destroy(); } expect(mockStream.destroy).toHaveBeenCalled(); }); }); });