From 03cf6ae9e79abaf23f80932ac90d373a1cb23500 Mon Sep 17 00:00:00 2001 From: hugo Date: Tue, 17 Feb 2026 09:33:07 +0100 Subject: [PATCH] fix: test stabilization for windows --- src/main/engine/MediaEngine.ts | 3 +- tests/engine/MediaEngine.test.ts | 20 +++++----- tests/engine/MetaEngine.test.ts | 14 ++++--- tests/engine/ProjectEngine.test.ts | 38 +++++++++++-------- .../MilkdownMarkdownRoundTrip.test.ts | 14 ++++--- 5 files changed, 52 insertions(+), 37 deletions(-) diff --git a/src/main/engine/MediaEngine.ts b/src/main/engine/MediaEngine.ts index c709d06..0a213ae 100644 --- a/src/main/engine/MediaEngine.ts +++ b/src/main/engine/MediaEngine.ts @@ -960,7 +960,8 @@ export class MediaEngine extends EventEmitter { const dbMedia = await db.select().from(media).where(eq(media.id, id)).get(); if (!dbMedia?.filePath) return null; const dataDir = this.getDataDir(); - return path.relative(dataDir, dbMedia.filePath); + const relativePath = path.relative(dataDir, dbMedia.filePath); + return relativePath.replace(/\\/g, '/'); } async rebuildDatabaseFromFiles(): Promise { diff --git a/tests/engine/MediaEngine.test.ts b/tests/engine/MediaEngine.test.ts index 73ce026..e662d7c 100644 --- a/tests/engine/MediaEngine.test.ts +++ b/tests/engine/MediaEngine.test.ts @@ -29,6 +29,7 @@ import { MediaEngine, MediaData } from '../../src/main/engine/MediaEngine'; const mockMedia = new Map(); const mockPostMedia = new Map(); const mockFiles = new Map(); +const normalizePath = (value: string): string => value.replace(/\\/g, '/'); // Track database operations for testing let mediaDeleteCalled = false; @@ -126,7 +127,8 @@ vi.mock('../../src/main/database', () => ({ // Mock fs/promises vi.mock('fs/promises', () => ({ readFile: vi.fn(async (path: string) => { - const content = mockFiles.get(path); + const normalizedPath = normalizePath(path); + const content = mockFiles.get(normalizedPath); if (!content) { const error = new Error(`ENOENT: no such file or directory, open '${path}'`); (error as any).code = 'ENOENT'; @@ -135,29 +137,29 @@ vi.mock('fs/promises', () => ({ return content; }), writeFile: vi.fn(async (path: string, content: Buffer | string) => { - mockFiles.set(path, content); + mockFiles.set(normalizePath(path), content); }), unlink: vi.fn(async (path: string) => { - mockFiles.delete(path); + mockFiles.delete(normalizePath(path)); }), mkdir: vi.fn(async () => {}), readdir: vi.fn(async () => []), stat: vi.fn(async (path: string) => ({ - isFile: () => mockFiles.has(path), - isDirectory: () => !mockFiles.has(path), - size: mockFiles.get(path)?.length || 0, + isFile: () => mockFiles.has(normalizePath(path)), + isDirectory: () => !mockFiles.has(normalizePath(path)), + size: mockFiles.get(normalizePath(path))?.length || 0, })), access: vi.fn(async (path: string) => { - if (!mockFiles.has(path)) { + if (!mockFiles.has(normalizePath(path))) { const error = new Error(`ENOENT`); (error as any).code = 'ENOENT'; throw error; } }), copyFile: vi.fn(async (src: string, dest: string) => { - const content = mockFiles.get(src); + const content = mockFiles.get(normalizePath(src)); if (content) { - mockFiles.set(dest, content); + mockFiles.set(normalizePath(dest), content); } }), })); diff --git a/tests/engine/MetaEngine.test.ts b/tests/engine/MetaEngine.test.ts index 012042e..28653c5 100644 --- a/tests/engine/MetaEngine.test.ts +++ b/tests/engine/MetaEngine.test.ts @@ -10,6 +10,7 @@ */ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; +import * as path from 'path'; // Mock data stores const mockFiles = new Map(); @@ -749,17 +750,20 @@ describe('MetaEngine', () => { }); it('should use custom dataDir when provided in setProjectContext', () => { - metaEngine.setProjectContext('project-with-custom-dir', '/custom/data/path'); + const customDataDir = path.join('custom', 'data', 'path'); + metaEngine.setProjectContext('project-with-custom-dir', customDataDir); const metaDir = metaEngine.getMetaDir(); - expect(metaDir).toContain('/custom/data/path'); + expect(normalizePath(metaDir)).toContain(normalizePath(customDataDir)); }); it('should sync dataPath from database to project.json if different', async () => { const metaDir = metaEngine.getMetaDir(); + const oldPath = path.join('old', 'path', 'from', 'file'); + const newPath = path.join('new', 'path', 'from', 'database'); mockFiles.set(normalizePath(`${metaDir}/project.json`), JSON.stringify({ name: 'Project', - dataPath: '/old/path/from/file', + dataPath: oldPath, })); // Database has the currently selected (authoritative) path @@ -767,7 +771,7 @@ describe('MetaEngine', () => { id: 'test-project', name: 'Project', description: null, - dataPath: '/new/path/from/database', + dataPath: newPath, slug: 'project', createdAt: new Date(), updatedAt: new Date(), @@ -779,7 +783,7 @@ describe('MetaEngine', () => { const savedProjectJson = mockFiles.get(normalizePath(`${metaDir}/project.json`)); expect(savedProjectJson).toBeDefined(); const parsed = JSON.parse(savedProjectJson!); - expect(parsed.dataPath).toBe('/new/path/from/database'); + expect(normalizePath(parsed.dataPath)).toBe(normalizePath(newPath)); expect(mockLocalDb.update).not.toHaveBeenCalled(); }); }); diff --git a/tests/engine/ProjectEngine.test.ts b/tests/engine/ProjectEngine.test.ts index 6cd0294..240e783 100644 --- a/tests/engine/ProjectEngine.test.ts +++ b/tests/engine/ProjectEngine.test.ts @@ -6,9 +6,12 @@ */ import { describe, it, expect, beforeEach, vi } from 'vitest'; +import * as path from 'path'; import { ProjectEngine, ProjectData } from '../../src/main/engine/ProjectEngine'; import { resetMockCounters } from '../utils/factories'; +const normalizePath = (value: string): string => value.replace(/\\/g, '/'); + // Create mock data stores const mockProjects = new Map(); @@ -550,7 +553,7 @@ describe('ProjectEngine', () => { describe('Custom dataPath', () => { it('should create project with custom dataPath', async () => { - const customPath = '/Users/test/Documents/MyBlog'; + const customPath = path.join('Users', 'test', 'Documents', 'MyBlog'); const project = await projectEngine.createProject({ name: 'Custom Path Project', dataPath: customPath, @@ -561,7 +564,8 @@ describe('ProjectEngine', () => { it('should create meta and thumbnails directories in custom dataPath', async () => { const fs = await import('fs/promises'); - const customPath = '/Users/test/Documents/MyBlog'; + const customPath = path.join('Users', 'test', 'Documents', 'MyBlog'); + const normalizedCustomPath = normalizePath(customPath); await projectEngine.createProject({ name: 'Custom Dirs Project', @@ -569,17 +573,18 @@ describe('ProjectEngine', () => { }); const mkdirCalls = vi.mocked(fs.mkdir).mock.calls; - const createdPaths = mkdirCalls.map(call => call[0]); + const createdPaths = mkdirCalls.map(call => normalizePath(String(call[0]))); // Should create meta/ and thumbnails/ in custom dataPath - expect(createdPaths).toContainEqual(expect.stringContaining(customPath)); - expect(createdPaths.some(p => String(p).includes(customPath) && String(p).includes('meta'))).toBe(true); - expect(createdPaths.some(p => String(p).includes(customPath) && String(p).includes('thumbnails'))).toBe(true); + expect(createdPaths).toContainEqual(expect.stringContaining(normalizedCustomPath)); + expect(createdPaths.some(p => p.includes(normalizedCustomPath) && p.includes('meta'))).toBe(true); + expect(createdPaths.some(p => p.includes(normalizedCustomPath) && p.includes('thumbnails'))).toBe(true); }); it('should create posts and media directories in custom dataPath', async () => { const fs = await import('fs/promises'); - const customPath = '/Users/test/Documents/MyBlog'; + const customPath = path.join('Users', 'test', 'Documents', 'MyBlog'); + const normalizedCustomPath = normalizePath(customPath); await projectEngine.createProject({ name: 'Custom Data Project', @@ -587,11 +592,11 @@ describe('ProjectEngine', () => { }); const mkdirCalls = vi.mocked(fs.mkdir).mock.calls; - const createdPaths = mkdirCalls.map(call => call[0]); + const createdPaths = mkdirCalls.map(call => normalizePath(String(call[0]))); // Should create posts/ and media/ in custom dataPath - expect(createdPaths.some(p => String(p).includes(customPath) && String(p).includes('posts'))).toBe(true); - expect(createdPaths.some(p => String(p).includes(customPath) && String(p).includes('media'))).toBe(true); + expect(createdPaths.some(p => p.includes(normalizedCustomPath) && p.includes('posts'))).toBe(true); + expect(createdPaths.some(p => p.includes(normalizedCustomPath) && p.includes('media'))).toBe(true); }); it('should create meta and thumbnails in internal storage when no dataPath', async () => { @@ -611,7 +616,7 @@ describe('ProjectEngine', () => { it('should use getDataDir with custom dataPath', () => { const projectId = 'test-id'; - const customPath = '/Users/test/MyBlog'; + const customPath = path.join('Users', 'test', 'MyBlog'); const dataDir = projectEngine.getDataDir(projectId, customPath); @@ -826,7 +831,7 @@ describe('ProjectEngine', () => { name: 'My Project', slug: 'my-project', description: 'A test project', - dataPath: '/custom/path', + dataPath: path.join('custom', 'path'), createdAt: new Date('2024-01-01'), updatedAt: new Date('2024-06-01'), isActive: true, @@ -848,7 +853,7 @@ describe('ProjectEngine', () => { expect(result?.name).toBe('My Project'); expect(result?.slug).toBe('my-project'); expect(result?.description).toBe('A test project'); - expect(result?.dataPath).toBe('/custom/path'); + expect(result?.dataPath).toBe(path.join('custom', 'path')); expect(result?.isActive).toBe(true); }); @@ -1181,7 +1186,7 @@ describe('ProjectEngine', () => { id: 'resolved-project', name: 'Resolved Project', slug: 'resolved', - dataPath: '/custom/data/path', + dataPath: path.join('custom', 'data', 'path'), createdAt: new Date(), updatedAt: new Date(), isActive: false, @@ -1198,8 +1203,9 @@ describe('ProjectEngine', () => { const paths = await projectEngine.getProjectPathsResolved('resolved-project'); - expect(paths.posts).toContain('/custom/data/path'); - expect(paths.media).toContain('/custom/data/path'); + const normalizedBasePath = normalizePath(projectWithPath.dataPath); + expect(normalizePath(paths.posts)).toContain(normalizedBasePath); + expect(normalizePath(paths.media)).toContain(normalizedBasePath); }); it('should use internal path when project has no dataPath', async () => { diff --git a/tests/renderer/components/MilkdownMarkdownRoundTrip.test.ts b/tests/renderer/components/MilkdownMarkdownRoundTrip.test.ts index cb70ed1..f4256b0 100644 --- a/tests/renderer/components/MilkdownMarkdownRoundTrip.test.ts +++ b/tests/renderer/components/MilkdownMarkdownRoundTrip.test.ts @@ -11,6 +11,7 @@ import { visit } from 'unist-util-visit'; import { normalizeMilkdownMarkdown } from '../../../src/renderer/utils/markdownEscape'; const wxrRefDir = path.join(__dirname, '../../assets/wxr-ref'); +const normalizeLineEndingsToLf = (value: string): string => value.replace(/\r\n/g, '\n'); const remarkTightListsPlugin: Plugin<[Record], Root> = () => { return (tree: Root) => { @@ -42,13 +43,14 @@ describe('Milkdown markdown round trip', () => { const files = fs.readdirSync(wxrRefDir).filter((file) => file.endsWith('.md')); for (const file of files) { - const raw = fs.readFileSync(path.join(wxrRefDir, file), 'utf-8'); + const raw = normalizeLineEndingsToLf(fs.readFileSync(path.join(wxrRefDir, file), 'utf-8')); const { content } = matter(raw); + const normalizedContent = normalizeLineEndingsToLf(content); const editor = await Editor.make() .config((ctx) => { ctx.set(rootCtx, root); - ctx.set(defaultValueCtx, content); + ctx.set(defaultValueCtx, normalizedContent); ctx.set(remarkStringifyOptionsCtx, { bullet: '-', listItemIndent: 'one', @@ -59,16 +61,16 @@ describe('Milkdown markdown round trip', () => { .use(gfm) .create(); - const serialized = editor.action((ctx) => { + const serialized = normalizeLineEndingsToLf(editor.action((ctx) => { const parser = ctx.get(parserCtx); const serializer = ctx.get(serializerCtx); - const doc = parser(content); + const doc = parser(normalizedContent); return normalizeMilkdownMarkdown(serializer(doc)); - }); + })); await editor.destroy(); - expect(serialized, `round trip mismatch for ${file}`).toBe(content); + expect(serialized, `round trip mismatch for ${file}`).toBe(normalizedContent); } }, 30000); });