diff --git a/apps/desktop/layer/main/src/ipc/services/integration.test.ts b/apps/desktop/layer/main/src/ipc/services/integration.test.ts new file mode 100644 index 000000000..75af50f4c --- /dev/null +++ b/apps/desktop/layer/main/src/ipc/services/integration.test.ts @@ -0,0 +1,74 @@ +import fsp from "node:fs/promises" +import os from "node:os" + +import type { IpcContext } from "electron-ipc-decorator" +import path from "pathe" +import { afterEach, describe, expect, it, vi } from "vitest" + +import { IntegrationService } from "./integration" + +vi.mock("electron", () => ({ + ipcMain: { + handle: vi.fn(), + }, + shell: { + openExternal: vi.fn(), + }, +})) + +vi.mock("electron-ipc-decorator", () => ({ + IpcMethod: () => (_target: unknown, _propertyKey: string, descriptor: PropertyDescriptor) => + descriptor, + IpcService: class {}, +})) + +vi.mock("~/lib/store", () => ({ + store: { + get: vi.fn(), + set: vi.fn(), + }, +})) + +vi.mock("~/logger", () => ({ + logger: { + debug: vi.fn(), + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + }, +})) + +describe("IntegrationService", () => { + let vaultPath: string | undefined + + afterEach(async () => { + if (!vaultPath) return + + await fsp.rm(vaultPath, { force: true, recursive: true }) + vaultPath = undefined + }) + + it("saves Obsidian titles with path separators as one markdown file", async () => { + vaultPath = await fsp.mkdtemp(path.join(os.tmpdir(), "folo-obsidian-")) + const service = new IntegrationService() + const context = {} as IpcContext + + await expect( + service.saveToObsidian(context, { + url: "https://example.com", + title: "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1/6比例手办", + content: "content", + author: "Folo", + publishedAt: "2026-05-14T04:20:44.405Z", + vaultPath, + }), + ).resolves.toEqual({ success: true }) + + await expect(fsp.readdir(vaultPath)).resolves.toEqual([ + "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1_6比例手办.md", + ]) + await expect( + fsp.stat(path.join(vaultPath, "KAWA DESIGN 少女前线2:追放 索米·雪兔献礼 1")), + ).rejects.toThrow() + }) +}) diff --git a/apps/desktop/layer/main/src/ipc/services/integration.ts b/apps/desktop/layer/main/src/ipc/services/integration.ts index d0164fe04..a44a52aec 100644 --- a/apps/desktop/layer/main/src/ipc/services/integration.ts +++ b/apps/desktop/layer/main/src/ipc/services/integration.ts @@ -14,7 +14,7 @@ import { createObsidianFrontmatter } from "./obsidian-frontmatter" // Taken from https://github.com/rollup/rollup/blob/4f69d33af3b2ec9320c43c9e6c65ea23a02bdde3/src/utils/sanitizeFileName.ts // https://datatracker.ietf.org/doc/html/rfc2396 // eslint-disable-next-line no-control-regex -const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F]/g +const INVALID_CHAR_REGEX = /[\u0000-\u001F"#$%&*+,:;<=>?[\]^`{|}\u007F/\\]/g const DRIVE_LETTER_REGEX = /^[a-z]:/i function sanitizeFileName(name: string): string {