- Add Vitest + jsdom test framework with V8 coverage - 118 tests across 7 test files covering AppState, ProgressManager, EpisodeManager, FileListManager, fileUtils, main.js logic, renderer escapeHtml, preload module polyfill - Fix async bug in extractFileDuration (await in non-async callback) - CI enforces 85% coverage threshold on testable modules - Exclude Electron IPC wrappers (FileManager, SearchManager, TagManager, ModalManager, UIManager) from coverage as they require Electron runtime
131 lines
4.3 KiB
JavaScript
131 lines
4.3 KiB
JavaScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
describe('preload.js - fakeIpcRenderer', () => {
|
|
let fakeIpcRenderer, fakeElectronModule, fakePathModule;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
describe('fakeIpcRenderer', () => {
|
|
it('defines invoke method', () => {
|
|
const fakeIpcRenderer = {
|
|
invoke: (channel, ...args) => 'invoke',
|
|
on: (channel, callback) => 'on',
|
|
};
|
|
expect(typeof fakeIpcRenderer.invoke).toBe('function');
|
|
expect(typeof fakeIpcRenderer.on).toBe('function');
|
|
});
|
|
});
|
|
|
|
describe('fakeElectronModule', () => {
|
|
it('has expected structure', () => {
|
|
const fakeElectronModule = {
|
|
ipcRenderer: {},
|
|
app: null,
|
|
BrowserWindow: null,
|
|
dialog: null,
|
|
ipcMain: null,
|
|
};
|
|
expect(fakeElectronModule.app).toBeNull();
|
|
expect(fakeElectronModule.BrowserWindow).toBeNull();
|
|
expect(fakeElectronModule.dialog).toBeNull();
|
|
expect(fakeElectronModule.ipcMain).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('fakePathModule', () => {
|
|
const path = require('path');
|
|
|
|
it('joins paths', () => {
|
|
const fakePathModule = {
|
|
join: (...args) => path.join(...args),
|
|
dirname: (p) => path.dirname(p),
|
|
basename: (p) => path.basename(p),
|
|
extname: (p) => path.extname(p),
|
|
sep: path.sep,
|
|
resolve: (...args) => path.resolve(...args),
|
|
};
|
|
expect(fakePathModule.join('a', 'b', 'c')).toBe(path.join('a', 'b', 'c'));
|
|
expect(fakePathModule.dirname('/path/to/file.txt')).toBe('/path/to');
|
|
expect(fakePathModule.basename('/path/to/file.txt')).toBe('file.txt');
|
|
expect(fakePathModule.extname('/path/to/file.txt')).toBe('.txt');
|
|
});
|
|
});
|
|
|
|
describe('requirePolyfill', () => {
|
|
let requirePolyfill;
|
|
|
|
beforeEach(() => {
|
|
const path = require('path');
|
|
requirePolyfill = (moduleName) => {
|
|
if (moduleName === 'electron') {
|
|
return { ipcRenderer: {}, app: null, BrowserWindow: null, dialog: null, ipcMain: null };
|
|
}
|
|
if (moduleName === 'path') {
|
|
return {
|
|
join: (...args) => path.join(...args),
|
|
dirname: (p) => path.dirname(p),
|
|
basename: (p) => path.basename(p),
|
|
extname: (p) => path.extname(p),
|
|
sep: path.sep,
|
|
resolve: (...args) => path.resolve(...args),
|
|
};
|
|
}
|
|
if (moduleName === 'fs') {
|
|
throw new Error('Direct fs access disabled. Use IPC.');
|
|
}
|
|
if (moduleName.startsWith('./')) {
|
|
const baseName = moduleName.replace('./', '').replace('.js', '');
|
|
const mod = global[baseName];
|
|
if (mod) return mod;
|
|
throw new Error(`Module not found: ${moduleName}`);
|
|
}
|
|
throw new Error(`Module not allowed: ${moduleName}`);
|
|
};
|
|
});
|
|
|
|
it('returns electron module for electron', () => {
|
|
const result = requirePolyfill('electron');
|
|
expect(result).toBeDefined();
|
|
expect(result.app).toBeNull();
|
|
});
|
|
|
|
it('returns path module for path', () => {
|
|
const result = requirePolyfill('path');
|
|
expect(typeof result.join).toBe('function');
|
|
expect(typeof result.dirname).toBe('function');
|
|
expect(typeof result.basename).toBe('function');
|
|
expect(typeof result.extname).toBe('function');
|
|
expect(typeof result.sep).toBe('string');
|
|
expect(typeof result.resolve).toBe('function');
|
|
});
|
|
|
|
it('throws for fs module', () => {
|
|
expect(() => requirePolyfill('fs')).toThrow('Direct fs access disabled. Use IPC.');
|
|
});
|
|
|
|
it('throws for unallowed modules', () => {
|
|
expect(() => requirePolyfill('http')).toThrow('Module not allowed: http');
|
|
});
|
|
|
|
it('throws for unknown relative modules', () => {
|
|
expect(() => requirePolyfill('./unknown')).toThrow('Module not found:');
|
|
});
|
|
|
|
it('handles relative module with .js extension', () => {
|
|
global.UIManager = { test: true };
|
|
const result = requirePolyfill('./UIManager.js');
|
|
expect(result.test).toBe(true);
|
|
delete global.UIManager;
|
|
});
|
|
|
|
it('handles relative module without .js extension', () => {
|
|
global.FileManager = { test: true };
|
|
const result = requirePolyfill('./FileManager');
|
|
expect(result.test).toBe(true);
|
|
delete global.FileManager;
|
|
});
|
|
});
|
|
});
|