import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; const mockFiles = new Map(); const mockDirs = new Set(); const normalizePath = (value: string): string => value.replace(/\\/g, '/'); vi.mock('fs/promises', () => ({ readFile: vi.fn(async (filePath: string) => { const normalizedPath = normalizePath(filePath); if (mockFiles.has(normalizedPath)) { return mockFiles.get(normalizedPath); } const err = new Error(`ENOENT: no such file or directory, open '${filePath}'`) as NodeJS.ErrnoException; err.code = 'ENOENT'; throw err; }), writeFile: vi.fn(async (filePath: string, content: string) => { mockFiles.set(normalizePath(filePath), content); }), mkdir: vi.fn(async (dirPath: string) => { mockDirs.add(normalizePath(dirPath)); }), })); vi.mock('electron', () => ({ app: { getPath: vi.fn(() => '/mock/userData'), }, })); import { MenuEngine } from '../../src/main/engine/MenuEngine'; describe('MenuEngine', () => { let menuEngine: MenuEngine; beforeEach(() => { vi.clearAllMocks(); mockFiles.clear(); mockDirs.clear(); menuEngine = new MenuEngine(); menuEngine.setProjectContext('project-1'); }); afterEach(() => { vi.restoreAllMocks(); }); it('returns an empty menu when no OPML file exists', async () => { const result = await menuEngine.getMenu(); expect(result.items).toHaveLength(1); expect(result.items[0]).toMatchObject({ id: 'menu-home', title: 'Home', kind: 'home', pageSlug: 'home', }); }); it('parses nested OPML outlines into menu items', async () => { const menuPath = normalizePath(`${menuEngine.getMetaDir()}/menu.opml`); mockFiles.set( menuPath, `\n\n Blog Menu\n \n \n \n \n \n \n`, ); const result = await menuEngine.getMenu(); expect(result.items).toHaveLength(2); expect(result.items[0]).toMatchObject({ id: 'menu-home', title: 'Home', kind: 'home', pageSlug: 'home', }); expect(result.items[1]).toMatchObject({ id: 'docs', title: 'Docs', kind: 'submenu', }); expect(result.items[1].children[0]).toMatchObject({ id: 'about', title: 'About', kind: 'page', pageSlug: 'about', }); }); it('writes menu state as OPML and can read it back', async () => { const saved = await menuEngine.saveMenu({ items: [ { id: 'top', title: 'Top', kind: 'submenu', children: [ { id: 'page-1', title: 'First Page', kind: 'page', pageSlug: 'first-page', children: [], }, ], }, ], }); expect(saved.items.some((item) => item.id === 'menu-home')).toBe(true); expect(saved.items.some((item) => item.title === 'Top')).toBe(true); const roundTrip = await menuEngine.getMenu(); expect(roundTrip).toEqual(saved); }); it('keeps Home entry when payload tries to remove it', async () => { const saved = await menuEngine.saveMenu({ items: [ { id: 'custom-page', title: 'Custom', kind: 'page', pageSlug: 'custom', children: [], }, ], }); expect(saved.items.some((item) => item.id === 'menu-home')).toBe(true); }); });