diff --git a/e2e/keep-alive.spec.ts b/e2e/keep-alive.spec.ts index d2cc17f9c..079d49e12 100644 --- a/e2e/keep-alive.spec.ts +++ b/e2e/keep-alive.spec.ts @@ -2,10 +2,21 @@ import { test, expect } from "./fixtures"; import { openOptionsPage } from "./utils"; import type { CDPSession } from "@playwright/test"; -const KEEP_ALIVE_LABEL = "Keep Background and Scheduled Scripts Alive"; const SERVICE_WORKER_URL = "/service_worker.js"; const HEARTBEAT_VALIDATION_WINDOW_MS = 31_000; +const openRuntimeSettings = async (context: Parameters[0], extensionId: string) => { + const page = await openOptionsPage(context, extensionId); + await page + .getByTestId("view-toggle") + .or(page.getByTestId("mobile-search")) + .first() + .waitFor({ state: "visible", timeout: 30_000 }); + await page.goto(`chrome-extension://${extensionId}/src/options.html#/settings`); + await expect(page.getByTestId("setting-page")).toBeVisible({ timeout: 20_000 }); + return page; +}; + type CdpTargetMessage = { sessionId: string; message: string; @@ -53,17 +64,14 @@ const sendTargetCommand = async ( test.describe("Chrome MV3 service worker keep-alive", () => { test("offscreen runtime heartbeat keeps the service worker active", async ({ context, extensionId }) => { - const optionsPage = await openOptionsPage(context, extensionId); - const cdp = await context.newCDPSession(optionsPage); + const optionsPage = await openRuntimeSettings(context, extensionId); try { - await optionsPage.goto(`chrome-extension://${extensionId}/src/options.html#/settings`); - const label = optionsPage.getByText(KEEP_ALIVE_LABEL, { exact: true }); - await label.scrollIntoViewIfNeeded(); - - const keepAliveSwitch = label.locator("xpath=../..").getByRole("switch"); + const keepAliveSwitch = optionsPage.getByTestId("keep-alive-switch"); + await keepAliveSwitch.scrollIntoViewIfNeeded(); await expect(keepAliveSwitch).toBeVisible(); await expect(keepAliveSwitch).toHaveAttribute("aria-checked", "false"); + const cdp = await context.newCDPSession(optionsPage); await expect .poll( @@ -93,24 +101,22 @@ test.describe("Chrome MV3 service worker keep-alive", () => { }); test("disabling the setting allows the service worker to become idle", async ({ context, extensionId }) => { - const optionsPage = await openOptionsPage(context, extensionId); - const cdp = await context.newCDPSession(optionsPage); + const optionsPage = await openRuntimeSettings(context, extensionId); + let cdp: CDPSession | undefined; let offscreenSessionId: string | undefined; let nextCommandId = 1; try { - await optionsPage.goto(`chrome-extension://${extensionId}/src/options.html#/settings`); - const label = optionsPage.getByText(KEEP_ALIVE_LABEL, { exact: true }); - await label.scrollIntoViewIfNeeded(); - - const keepAliveSwitch = label.locator("xpath=../..").getByRole("switch"); + const keepAliveSwitch = optionsPage.getByTestId("keep-alive-switch"); + await keepAliveSwitch.scrollIntoViewIfNeeded(); await expect(keepAliveSwitch).toBeVisible(); await expect(keepAliveSwitch).toHaveAttribute("aria-checked", "false"); + cdp = await context.newCDPSession(optionsPage); await expect .poll( async () => { - const { targetInfos } = await cdp.send("Target.getTargets"); + const { targetInfos } = await cdp!.send("Target.getTargets"); return targetInfos.some((target) => target.url.endsWith("/src/offscreen.html")); }, { timeout: 15_000 } @@ -168,7 +174,7 @@ test.describe("Chrome MV3 service worker keep-alive", () => { .toBe(true); } finally { if (offscreenSessionId) { - await cdp.send("Target.detachFromTarget", { sessionId: offscreenSessionId }); + await cdp!.send("Target.detachFromTarget", { sessionId: offscreenSessionId }); } if (!optionsPage.isClosed()) await optionsPage.close(); } diff --git a/e2e/popup.spec.ts b/e2e/popup.spec.ts index 4e8a55d13..4ba45cf4e 100644 --- a/e2e/popup.spec.ts +++ b/e2e/popup.spec.ts @@ -1,14 +1,9 @@ import { test, expect } from "./fixtures"; import { openPopupPage } from "./utils"; -// new-ui popup(shadcn):标题 h1、全局 Radix Switch、Radix Accordion 分组、 -// 图标按钮(aria-label 设置/更多菜单)、Radix DropdownMenu(role=menuitem)。 +// new-ui popup(shadcn):全局 Radix Switch、Radix Accordion 分组、图标按钮(aria-label 设置/更多菜单)、 +// Radix DropdownMenu(role=menuitem)。 test.describe("Popup 页面", () => { - test("应加载并显示 ScriptCat 标题", async ({ context, extensionId }) => { - const page = await openPopupPage(context, extensionId); - await expect(page.getByText("ScriptCat", { exact: true })).toBeVisible({ timeout: 10_000 }); - }); - test("应显示全局脚本启用/禁用开关", async ({ context, extensionId }) => { const page = await openPopupPage(context, extensionId); // 顶部全局开关为 Radix Switch(role=switch) diff --git a/e2e/script-editor.spec.ts b/e2e/script-editor.spec.ts deleted file mode 100644 index 143e5f95f..000000000 --- a/e2e/script-editor.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { test, expect } from "./fixtures"; -import { openEditorPage, openOptionsPage, saveCurrentEditor } from "./utils"; - -// new-ui 脚本编辑器:路由 #/script/editor 加载空白模板(normal.tpl,含 ==UserScript==); -// Monaco 选择器(.monaco-editor/.view-lines) 为框架级不变;保存成功为 sonner toast。 -test.describe("Script 编辑器", () => { - test("保存后脚本应出现在列表中", async ({ context, extensionId }) => { - const editorPage = await openEditorPage(context, extensionId); - await expect(editorPage.locator(".monaco-editor")).toBeVisible({ timeout: 10_000 }); - await expect(editorPage.locator(".view-lines")).toContainText("==UserScript==", { timeout: 10_000 }); - - await saveCurrentEditor(context, extensionId, editorPage); - - const listPage = await openOptionsPage(context, extensionId); - // 保存后列表非空(无空状态) - await expect(listPage.getByTestId("script-list-empty")).toHaveCount(0, { timeout: 10_000 }); - }); -}); diff --git a/e2e/script-management.spec.ts b/e2e/script-management.spec.ts index 5988fbbe8..f702b8eb0 100644 --- a/e2e/script-management.spec.ts +++ b/e2e/script-management.spec.ts @@ -18,13 +18,6 @@ async function createScriptAndGoToList(context: BrowserContext, extensionId: str } test.describe("脚本管理", () => { - test("创建脚本后应出现在列表中", async ({ context, extensionId }) => { - const page = await createScriptAndGoToList(context, extensionId); - // 列表非空(无空状态) - await expect(page.getByTestId("script-list-empty")).toHaveCount(0, { timeout: 10_000 }); - await expect(page.getByRole("switch").first()).toBeVisible({ timeout: 10_000 }); - }); - test("应能切换脚本的启用/禁用", async ({ context, extensionId }) => { const page = await createScriptAndGoToList(context, extensionId); diff --git a/packages/filesystem/s3/client.test.ts b/packages/filesystem/s3/client.test.ts index 94bdf9ca3..bd9504278 100644 --- a/packages/filesystem/s3/client.test.ts +++ b/packages/filesystem/s3/client.test.ts @@ -14,18 +14,6 @@ describe("S3Error", () => { expect(err.message).toBe("The specified key does not exist"); expect(err.statusCode).toBe(404); }); - - it("应当可被 try/catch 捕获并通过 instanceof 判断", () => { - try { - throw new S3Error("AccessDenied", "Access Denied", 403); - } catch (e) { - expect(e).toBeInstanceOf(S3Error); - if (e instanceof S3Error) { - expect(e.code).toBe("AccessDenied"); - expect(e.statusCode).toBe(403); - } - } - }); }); // ---- S3Client 构造函数与 getter 方法 ---- diff --git a/packages/filesystem/s3/s3.test.ts b/packages/filesystem/s3/s3.test.ts index 99484cf8f..e9bbb39d1 100644 --- a/packages/filesystem/s3/s3.test.ts +++ b/packages/filesystem/s3/s3.test.ts @@ -123,21 +123,6 @@ describe("S3FileSystem", () => { // ---- open ---- describe("open", () => { - it("应当返回 S3FileReader", async () => { - const fileInfo: FileInfo = { - name: "test.txt", - path: "/docs", - size: 100, - digest: "abc", - createtime: 1000, - updatetime: 2000, - }; - const reader = await fs.open(fileInfo); - - expect(reader).toBeDefined(); - expect(reader.read).toBeTypeOf("function"); - }); - it("S3FileReader.read 应调用 client.request GET", async () => { const fileInfo: FileInfo = { name: "hello.txt", @@ -205,13 +190,6 @@ describe("S3FileSystem", () => { // ---- create ---- describe("create", () => { - it("应当返回 S3FileWriter", async () => { - const writer = await fs.create("test.txt"); - - expect(writer).toBeDefined(); - expect(writer.write).toBeTypeOf("function"); - }); - it("S3FileWriter.write 应调用 client.request PUT", async () => { (mockClient.request as ReturnType).mockResolvedValue(createMockResponse({ ok: true })); diff --git a/packages/message/message_queue.test.ts b/packages/message/message_queue.test.ts index 9db04729f..5d652c71a 100644 --- a/packages/message/message_queue.test.ts +++ b/packages/message/message_queue.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { MessageQueue, MessageQueueGroup, type IMessageQueue } from "./message_queue"; +import { MessageQueue, type IMessageQueue } from "./message_queue"; const nextTick = () => Promise.resolve().then(() => {}); @@ -16,11 +16,6 @@ describe("MessageQueueGroup", () => { }); describe("基本功能测试", () => { - it.concurrent("应该能够创建分组", () => { - const group = messageQueue.group("api-group"); - expect(group).toBeInstanceOf(MessageQueueGroup); - }); - it.concurrent("应该能够在分组中订阅和发布消息", () => { const group = messageQueue.group("api-sendBasic"); const handler = vi.fn(); @@ -269,36 +264,4 @@ describe("MessageQueueGroup", () => { expect(handler).toHaveBeenCalledTimes(1); }); }); - - describe("边界情况测试", () => { - it.concurrent("没有中间件的分组应该正常工作", () => { - const group = messageQueue.group("api-groupNoMiddleware"); - const handler = vi.fn(); - - group.subscribe("test-groupNoMiddleware", handler); - group.emit("test-groupNoMiddleware", { data: "test-groupNoMiddleware" }); - - expect(handler).toHaveBeenCalledWith({ data: "test-groupNoMiddleware" }); - }); - - it.concurrent("应该能够处理复杂的数据类型", () => { - const group = messageQueue.group("api-complexPayload"); - const handler = vi.fn(); - - const complexData = { - array: [1, 2, 3], - object: { nested: true }, - number: 42, - string: "test-complexPayload", - boolean: true, - null: null, - undefined: undefined, - }; - - group.subscribe("test-complexPayload", handler); - group.emit("test-complexPayload", complexData); - - expect(handler).toHaveBeenCalledWith(complexData); - }); - }); }); diff --git a/packages/message/server.test.ts b/packages/message/server.test.ts index b58037491..b7b1ff007 100644 --- a/packages/message/server.test.ts +++ b/packages/message/server.test.ts @@ -66,21 +66,6 @@ describe("Server", () => { expect(response.data).toBe("sync response"); }); - it.concurrent("应该能够处理异步函数", async () => { - const mockHandler = vi.fn().mockResolvedValue("async response"); - - server.on("on-async", mockHandler); - - const response = await client.sendMessage({ - action: "api/on-async", - data: { param: "value-async" }, - }); - - expect(mockHandler).toHaveBeenCalledWith({ param: "value-async" }, expect.any(SenderRuntime)); - expect(response.code).toBe(0); - expect(response.data).toBe("async response"); - }); - it.concurrent("应该能够处理函数抛出的错误", async () => { const error = new Error("test error"); const mockHandler = vi.fn().mockImplementation(() => { @@ -397,25 +382,6 @@ describe("Server", () => { expect(handler).toHaveBeenCalledTimes(1); }); - it("没有中间件的 Group 应该正常工作", async () => { - const group = server.group("api"); - - const handler = vi.fn(async (params: any) => { - return { data: params }; - }); - - group.on("nomiddle", handler); - - const response = await client.sendMessage({ - action: "api/api/nomiddle", - data: { message: "hello" }, - }); - - expect(response.code).toBe(0); - expect(response.data).toEqual({ data: { message: "hello" } }); - expect(handler).toHaveBeenCalledTimes(1); - }); - it("中间件应该能够处理异步错误", async () => { const errorMiddleware = vi.fn(async (params: any, con: any, next: any) => { if (params.throwError) { @@ -655,30 +621,6 @@ describe("Server", () => { expect(response.data).toBe("empty response"); }); - it.concurrent("应该能够处理复杂的数据类型", async () => { - const complexData = { - array: [1, 2, 3], - object: { nested: true }, - number: 42, - string: "test", - boolean: true, - null: null, - undefined: undefined, - }; - - const mockHandler = vi.fn().mockImplementation((params) => params); - - server.on("on-complex", mockHandler); - - const response = await client.sendMessage({ - action: "api/on-complex", - data: complexData, - }); - - expect(response.code).toBe(0); - expect(response.data).toEqual(complexData); - }); - it.concurrent("应该能够处理返回 undefined 的函数", async () => { const mockHandler = vi.fn().mockReturnValue(undefined); diff --git a/packages/message/window_message.test.ts b/packages/message/window_message.test.ts index 6f3d636b4..00be4f8d7 100644 --- a/packages/message/window_message.test.ts +++ b/packages/message/window_message.test.ts @@ -133,13 +133,6 @@ describe("ServiceWorkerMessageSend", () => { }); describe("ServiceWorkerClientMessage", () => { - it("controller 可用时直接使用", () => { - const clientMsg = new ServiceWorkerClientMessage(); - - expect((clientMsg as any).sw).not.toBeNull(); - expect((clientMsg as any).sw.postMessage).toBe(swPostMessageMock); - }); - it("controller 为 null 时通过 ready 获取 active SW", async () => { const readyPostMessage = vi.fn(); Object.defineProperty(navigator, "serviceWorker", { @@ -334,19 +327,6 @@ describe("ServiceWorkerMessageSend ↔ ServiceWorkerClientMessage 双向通信", return { swSend, clientMsg }; } - it("sendMessage: client→SW 请求并收到响应", async () => { - const { swSend, clientMsg } = createWiredPair(); - - // SW 端注册处理器 - swSend.onMessage((msg: any, sendResponse: any) => { - sendResponse({ code: 0, data: (msg.data as string) + " world" }); - return true; - }); - - const result = await clientMsg.sendMessage({ action: "test/echo", data: "hello" }); - expect(result).toEqual({ code: 0, data: "hello world" }); - }); - it("connect: 建立连接后双向通信", async () => { const { swSend, clientMsg } = createWiredPair(); @@ -402,26 +382,6 @@ describe("ServiceWorkerMessageSend ↔ ServiceWorkerClientMessage 双向通信", expect(serverDisconnected).toBe(true); }); - it("sendMessage: 支持传输复杂对象(模拟结构化克隆场景)", async () => { - const { swSend, clientMsg } = createWiredPair(); - - swSend.onMessage((msg: any, sendResponse: any) => { - // 原样返回,验证数据完整性 - sendResponse({ code: 0, data: msg.data }); - return true; - }); - - const complexData = { - array: [1, 2, 3], - nested: { a: { b: "deep" } }, - nullVal: null, - boolVal: true, - }; - - const result = await clientMsg.sendMessage({ action: "test/complex", data: complexData }); - expect((result as any).data).toEqual(complexData); - }); - it("与 Server 集成: forwardMessage 路径", async () => { const swSend = new ServiceWorkerMessageSend(); const clientMsg = new ServiceWorkerClientMessage(); diff --git a/src/app/service/agent/core/compact_prompt.test.ts b/src/app/service/agent/core/compact_prompt.test.ts index 31bf49415..572e6f9a9 100644 --- a/src/app/service/agent/core/compact_prompt.test.ts +++ b/src/app/service/agent/core/compact_prompt.test.ts @@ -19,15 +19,6 @@ describe("extractSummary", () => { it("handles empty tags", () => { expect(extractSummary("")).toBe(""); }); - - it("handles multiline content inside ", () => { - const response = ` -Line 1 -Line 2 -Line 3 -`; - expect(extractSummary(response)).toBe("Line 1\nLine 2\nLine 3"); - }); }); describe("buildCompactUserPrompt", () => { diff --git a/src/app/service/agent/core/content_utils.test.ts b/src/app/service/agent/core/content_utils.test.ts index 2dd6cce39..eb66fe331 100644 --- a/src/app/service/agent/core/content_utils.test.ts +++ b/src/app/service/agent/core/content_utils.test.ts @@ -8,10 +8,6 @@ describe("content_utils", () => { expect(getTextContent("hello world")).toBe("hello world"); }); - it("returns empty string for empty string", () => { - expect(getTextContent("")).toBe(""); - }); - it("extracts text from ContentBlock[]", () => { const blocks: ContentBlock[] = [ { type: "text", text: "Hello " }, @@ -29,10 +25,6 @@ describe("content_utils", () => { expect(getTextContent(blocks)).toBe(""); }); - it("returns empty string for empty ContentBlock[]", () => { - expect(getTextContent([])).toBe(""); - }); - it("handles audio blocks (skipped in text extraction)", () => { const blocks: ContentBlock[] = [ { type: "text", text: "Listen: " }, @@ -58,11 +50,6 @@ describe("content_utils", () => { ]; expect(normalizeContent(blocks)).toBe(blocks); }); - - it("returns empty array as-is", () => { - const blocks: ContentBlock[] = []; - expect(normalizeContent(blocks)).toBe(blocks); - }); }); describe("isContentBlocks", () => { diff --git a/src/app/service/agent/core/mcp_tool_executor.test.ts b/src/app/service/agent/core/mcp_tool_executor.test.ts index d01415e63..e8e5c1e74 100644 --- a/src/app/service/agent/core/mcp_tool_executor.test.ts +++ b/src/app/service/agent/core/mcp_tool_executor.test.ts @@ -19,16 +19,6 @@ describe("MCPToolExecutor", () => { expect(client.callTool).toHaveBeenCalledWith("search", { query: "hello" }); }); - it("应正确传递工具名", async () => { - const client = createMockClient({ data: [1, 2, 3] }); - const executor = new MCPToolExecutor(client, "fetch_data"); - - const result = await executor.execute({ limit: 10 }); - - expect(result).toEqual({ data: [1, 2, 3] }); - expect(client.callTool).toHaveBeenCalledWith("fetch_data", { limit: 10 }); - }); - it("callTool 抛出异常时应向上传播", async () => { const client = { callTool: vi.fn().mockRejectedValue(new Error("MCP error")), @@ -92,15 +82,6 @@ describe("MCPToolExecutor", () => { expect(result).toEqual(mcpContent); }); - it("非数组结果应原样返回", async () => { - const client = createMockClient("plain string result"); - const executor = new MCPToolExecutor(client, "simple_tool"); - - const result = await executor.execute({}); - - expect(result).toBe("plain string result"); - }); - it("image 缺少 mimeType 时应默认为 image/png", async () => { const mcpContent = [{ type: "image", data: "abc123" }]; const client = createMockClient(mcpContent); diff --git a/src/app/service/agent/core/session_tool_registry.test.ts b/src/app/service/agent/core/session_tool_registry.test.ts index f821086e0..97a5704d0 100644 --- a/src/app/service/agent/core/session_tool_registry.test.ts +++ b/src/app/service/agent/core/session_tool_registry.test.ts @@ -259,28 +259,6 @@ describe("SessionToolRegistry", () => { expect(resA[0].result).toBe("fetched"); expect(resB[0].result).toBe("fetched"); }); - - it("session 释放(GC)后 parent 不受影响", () => { - const parent = new ToolRegistry(); - parent.registerBuiltin( - builtinDef, - createExecutor(async () => "") - ); - - // 创建临时 session 并让其超出作用域 - { - const session = new SessionToolRegistry(parent); - session.register( - "session", - taskDef, - createExecutor(async () => "") - ); - expect(session.listSessionTools()).toHaveLength(1); - } - - // parent 无任何 session 工具痕迹 - expect(parent.getDefinitions().map((d) => d.name)).toEqual(["web_fetch"]); - }); }); describe("脚本工具 miss-then-callback", () => { diff --git a/src/app/service/agent/core/skill_script_executor.test.ts b/src/app/service/agent/core/skill_script_executor.test.ts index a6d90d710..033bae2f1 100644 --- a/src/app/service/agent/core/skill_script_executor.test.ts +++ b/src/app/service/agent/core/skill_script_executor.test.ts @@ -233,19 +233,12 @@ return result;`, }); }); -describe("getSkillScriptNameByUuid", () => { - it("未注册的 UUID 应返回空字符串", () => { - expect(getSkillScriptNameByUuid("skillscript-unknown-uuid")).toBe(""); - }); - - it("空字符串应返回空字符串", () => { - expect(getSkillScriptNameByUuid("")).toBe(""); - }); -}); - describe("getSkillScriptGrantsByUuid", () => { - it("未注册的 UUID 应返回空数组", () => { - expect(getSkillScriptGrantsByUuid("skillscript-unknown-uuid")).toEqual([]); + it("未注册的 UUID 应返回空工具名和权限列表", () => { + for (const uuid of ["unregistered", ""]) { + expect(getSkillScriptNameByUuid(uuid)).toBe(""); + expect(getSkillScriptGrantsByUuid(uuid)).toEqual([]); + } }); it("执行期间应能通过 UUID 获取 grants", async () => { diff --git a/src/app/service/service_worker/script.ts b/src/app/service/service_worker/script.ts index d5c3912b6..e135b1717 100644 --- a/src/app/service/service_worker/script.ts +++ b/src/app/service/service_worker/script.ts @@ -321,7 +321,7 @@ export class ScriptService { action: { type: "redirect" as chrome.declarativeNetRequest.RuleActionType, redirect: { - regexSubstitution: `${installPageURL}?url=\\1`, + regexSubstitution: `${installPageURL}?byWebRequest=1&url=\\1`, }, }, condition: condition, diff --git a/src/locales/locales.test.ts b/src/locales/locales.test.ts index 1ac16cf16..4dbba146f 100644 --- a/src/locales/locales.test.ts +++ b/src/locales/locales.test.ts @@ -120,17 +120,4 @@ describe.concurrent("i18nDescription", () => { const result = i18nDescription(script); expect(result).toBe(""); }); - - it("description 字段为空数组时返回 空字串", () => { - i18n.language = "en-US"; - - const script = { - metadata: { - description: [], - } as SCMetadata, - }; - - const result = i18nDescription(script); - expect(result).toBe(""); - }); }); diff --git a/src/pages/components/use-is-mobile.test.ts b/src/pages/components/use-is-mobile.test.ts index 07e3e369f..4a8d8f69f 100644 --- a/src/pages/components/use-is-mobile.test.ts +++ b/src/pages/components/use-is-mobile.test.ts @@ -32,18 +32,6 @@ function stubMatchMedia(initialMatches: boolean, matchesOnSubscribe?: boolean) { } describe("useIsMobile 视口断点", () => { - it("视口 < 768px 时返回 true", () => { - stubMatchMedia(true); - const { result } = renderHook(() => useIsMobile()); - expect(result.current).toBe(true); - }); - - it("视口 ≥ 768px 时返回 false", () => { - stubMatchMedia(false); - const { result } = renderHook(() => useIsMobile()); - expect(result.current).toBe(false); - }); - it("监听 change 事件,视口变化时更新返回值", () => { const mql = stubMatchMedia(false); const { result } = renderHook(() => useIsMobile()); diff --git a/src/pages/confirm/confirm-options.test.ts b/src/pages/confirm/confirm-options.test.ts index 22739c1a3..f717938a4 100644 --- a/src/pages/confirm/confirm-options.test.ts +++ b/src/pages/confirm/confirm-options.test.ts @@ -1,11 +1,5 @@ import { describe, it, expect } from "vitest"; -import { - resolveConfirmType, - availableDurations, - canApplyToAll, - isSiteAccess, - isHighSensitive, -} from "./confirm-options"; +import { resolveConfirmType, availableDurations, canApplyToAll, isHighSensitive } from "./confirm-options"; import type { ConfirmParam } from "@App/app/service/service_worker/permission_verify"; const cp = (over: Partial = {}): ConfirmParam => ({ permission: "cors", ...over }); @@ -55,12 +49,3 @@ describe("授权选项 · 高敏感权限警示", () => { expect(isHighSensitive(cp({ permission: "file_storage" }))).toBe(false); }); }); - -describe("授权选项 · 站点访问识别", () => { - it("extension-site-access 应识别为站点访问(单按钮变体)", () => { - expect(isSiteAccess(cp({ permission: "extension-site-access" }))).toBe(true); - }); - it("其它权限不是站点访问", () => { - expect(isSiteAccess(cp({ permission: "cors" }))).toBe(false); - }); -}); diff --git a/src/pages/install/components/InstallStates.test.tsx b/src/pages/install/components/InstallStates.test.tsx index c8f561c00..ede3749f8 100644 --- a/src/pages/install/components/InstallStates.test.tsx +++ b/src/pages/install/components/InstallStates.test.tsx @@ -39,32 +39,23 @@ describe("InstallError 加载失败状态屏", () => { expect(screen.getByText("Error: Fetch failed with status 404")).toBeInTheDocument(); }); - it("保留顶部品牌栏(对照设计稿,失败态不丢失外壳)", () => { - render( {}} />); - expect(screen.getByTestId("install-top-bar")).toBeInTheDocument(); - }); - - it("提供 onRetry 时渲染重试按钮并可点击", () => { + it("提供重试和自定义标题时可分别触发重试与关闭", () => { const onRetry = vi.fn(); - render( {}} />); - fireEvent.click(screen.getByText("重试").closest("button")!); - expect(onRetry).toHaveBeenCalledTimes(1); - }); + const onClose = vi.fn(); + const { rerender } = render(); - it("未提供 onRetry 时不渲染重试按钮", () => { - render( {}} />); - expect(screen.queryByText("重试")).not.toBeInTheDocument(); - }); + expect(screen.getByText("无效安装地址")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "重试" })); + fireEvent.click(screen.getByRole("button", { name: "关闭" })); + expect(onRetry).toHaveBeenCalledOnce(); + expect(onClose).toHaveBeenCalledOnce(); - it("点击关闭触发 onClose", () => { - const onClose = vi.fn(); - render(); - fireEvent.click(screen.getByText("关闭").closest("button")!); - expect(onClose).toHaveBeenCalledTimes(1); + rerender(); + expect(screen.queryByRole("button", { name: "重试" })).not.toBeInTheDocument(); }); - it("可自定义标题(用于无效页面)", () => { - render( {}} />); - expect(screen.getByText("无效页面")).toBeInTheDocument(); + it("保留顶部品牌栏(对照设计稿,失败态不丢失外壳)", () => { + render( {}} />); + expect(screen.getByTestId("install-top-bar")).toBeInTheDocument(); }); }); diff --git a/src/pages/install/components/WatchingBanner.test.tsx b/src/pages/install/components/WatchingBanner.test.tsx index ce1cb4085..837746737 100644 --- a/src/pages/install/components/WatchingBanner.test.tsx +++ b/src/pages/install/components/WatchingBanner.test.tsx @@ -8,11 +8,6 @@ beforeAll(() => initTestLanguage("zh-CN")); afterEach(cleanup); describe("WatchingBanner 文件监听横幅", () => { - it("渲染监听横幅容器", () => { - render(); - expect(screen.getByTestId("watching-banner")).toBeInTheDocument(); - }); - it("提供最后同步时间时渲染时间戳区", () => { render(); expect(screen.getByTestId("watching-last-sync")).toBeInTheDocument(); diff --git a/src/pages/install/useInstallData.test.ts b/src/pages/install/useInstallData.test.ts index 3dce98b64..4f9185289 100644 --- a/src/pages/install/useInstallData.test.ts +++ b/src/pages/install/useInstallData.test.ts @@ -248,8 +248,8 @@ describe("useInstallData 数据流编排", () => { expect(state.view.oldCode).toBe("// old code"); }); - describe("安装成功后离开安装页:独立新标签应关闭,同标签内被重定向而来应返回上一页", () => { - const setupReady = async () => { + describe("安装成功后离开安装页:独立新标签应关闭,网页链接接管的原标签应返回上一页", () => { + const setupReady = async (paramOptions: Record = {}) => { window.history.replaceState({}, "", "/install.html?uuid=u1"); const metadata = { name: ["示例脚本"], version: ["1.0.0"], match: ["https://e.com/*"] }; const info: ScriptInfo = { @@ -260,7 +260,7 @@ describe("useInstallData 数据流编排", () => { metadata, source: "user", }; - (scriptClient.getInstallInfo as Mock).mockResolvedValue([false, info, {}]); + (scriptClient.getInstallInfo as Mock).mockResolvedValue([false, info, paramOptions]); (getTempCode as Mock).mockResolvedValue("// code"); (prepareScriptByCode as Mock).mockResolvedValue({ script: makeAction(metadata) }); (scriptClient.install as Mock).mockResolvedValue(undefined); @@ -269,11 +269,11 @@ describe("useInstallData 数据流编排", () => { return result; }; - it("history.length 为 1(以新标签打开)时应 window.close()", async () => { + it("独立新标签即使 history.length > 1 也应 window.close()", async () => { const result = await setupReady(); const closeSpy = vi.spyOn(window, "close").mockImplementation(() => {}); const backSpy = vi.spyOn(window.history, "back").mockImplementation(() => {}); - vi.spyOn(window.history, "length", "get").mockReturnValue(1); + vi.spyOn(window.history, "length", "get").mockReturnValue(2); await act(async () => { await result.current.install(); @@ -285,11 +285,11 @@ describe("useInstallData 数据流编排", () => { expect(backSpy).not.toHaveBeenCalled(); }); - it("history.length > 1(同一标签被就地重定向而来)时应 history.back() 而非关闭标签", async () => { - const result = await setupReady(); + it("byWebRequest 入口即使 history.length 为 1 也应 history.back() 而非关闭标签", async () => { + const result = await setupReady({ byWebRequest: true }); const closeSpy = vi.spyOn(window, "close").mockImplementation(() => {}); const backSpy = vi.spyOn(window.history, "back").mockImplementation(() => {}); - vi.spyOn(window.history, "length", "get").mockReturnValue(2); + vi.spyOn(window.history, "length", "get").mockReturnValue(1); await act(async () => { await result.current.install(); diff --git a/src/pages/install/useInstallData.ts b/src/pages/install/useInstallData.ts index b54a24f28..277692426 100644 --- a/src/pages/install/useInstallData.ts +++ b/src/pages/install/useInstallData.ts @@ -121,19 +121,19 @@ const buildScriptInfo = (uuid: string, code: string, url: string, metadata: SCMe source: "user", }); -// 安装页可能是专为安装打开的新标签(history.length === 1,关闭无损), -// 也可能是由 declarativeNetRequest 就地重定向而来的用户原浏览标签(history.length > 1), -// 后者若直接 window.close() 会连带关掉用户本来在看的页面,应改为返回上一页。 +// 安装页可能是专为安装打开的新标签,也可能由网页脚本链接接管用户原标签。 +// history.length 无法区分两者:扩展新标签也可能继承多条历史,因此必须使用入口携带的 +// byWebRequest 信号;后者若直接 window.close() 会连带关掉用户本来在看的页面。 // install()/close() 等可能在短时间内被重复触发(如用户连续点击、close 与 install 的 // setTimeout 前后脚打到),leaveInstallPageRunning 防止 back()/close() 被并发调用多次; // 推到 requestAnimationFrame 里执行,让触发它的那次交互(如按钮点击态)先完成一帧渲染。 let leaveInstallPageRunning = false; -const leaveInstallPage = () => { +const leaveInstallPage = (byWebRequest: boolean) => { if (leaveInstallPageRunning) return; leaveInstallPageRunning = true; requestAnimationFrame(() => { leaveInstallPageRunning = false; - if (window.history.length > 1) { + if (byWebRequest) { window.history.back(); } else { window.close(); @@ -182,6 +182,7 @@ export function useInstallData(): UseInstallData { const infoRef = useRef(null); const handleRef = useRef(null); const skillUuidRef = useRef(null); + const byWebRequestRef = useRef(false); useEffect(() => { const params = new URLSearchParams(location.search); @@ -191,6 +192,7 @@ export function useInstallData(): UseInstallData { const fid = params.get("file"); const urlIdx = location.search.indexOf("url="); const rawUrl = !uuid && urlIdx !== -1 ? location.search.slice(urlIdx + 4) : null; + byWebRequestRef.current = params.get("byWebRequest") === "1"; let cancelled = false; const failed = (e: unknown) => { @@ -256,6 +258,7 @@ export function useInstallData(): UseInstallData { const code = await getTempCode(uuid); if (code === undefined) throw new Error(t("install:script_info_load_failed")); info.code = code; + byWebRequestRef.current = cached?.[2]?.byWebRequest === true; await loadFromInfo(info, !!cached?.[0], cached?.[2] || {}); } else if (rawUrl) { // .cat.md URL → Skill 安装流程(DNR 把 *.cat.md 重定向到安装页),不走脚本解析;仅 agent 启用时 @@ -347,7 +350,7 @@ export function useInstallData(): UseInstallData { await scriptClient.install({ script, code: info.code }); notify.success(t("install:success")); } - if (closeAfterInstall) setTimeout(() => leaveInstallPage(), 300); + if (closeAfterInstall) setTimeout(() => leaveInstallPage(byWebRequestRef.current), 300); } catch (e) { notify.error(`${t("install:failed")}: ${(e as Error)?.message || String(e)}`); } @@ -360,7 +363,7 @@ export function useInstallData(): UseInstallData { if (opts?.noMoreUpdates && info && !info.userSubscribe) { void scriptClient.setCheckUpdateUrl(info.uuid, false); } - leaveInstallPage(); + leaveInstallPage(byWebRequestRef.current); }, []); // 监听文件变更后自动重装,并刷新视图代码 @@ -417,7 +420,7 @@ export function useInstallData(): UseInstallData { try { await agentClient.completeSkillInstall(uuid); notify.success(t("install:success")); - setTimeout(() => leaveInstallPage(), 300); + setTimeout(() => leaveInstallPage(byWebRequestRef.current), 300); } catch (e) { notify.error(`${t("install:failed")}: ${(e as Error)?.message || String(e)}`); } @@ -426,7 +429,7 @@ export function useInstallData(): UseInstallData { const cancelSkill = useCallback(() => { const uuid = skillUuidRef.current; if (uuid) void agentClient.cancelSkillInstall(uuid); - leaveInstallPage(); + leaveInstallPage(byWebRequestRef.current); }, []); // 重新触发加载(供加载失败后的重试按钮) diff --git a/src/pages/options/layout/Sidebar.test.tsx b/src/pages/options/layout/Sidebar.test.tsx index f89c21d09..4292fcf2e 100644 --- a/src/pages/options/layout/Sidebar.test.tsx +++ b/src/pages/options/layout/Sidebar.test.tsx @@ -36,11 +36,6 @@ const subLabels = () => [ ]; describe("Sidebar 侧边栏 AI Agent 菜单", () => { - it("渲染 AI Agent 子菜单入口", () => { - const { getByText } = renderSidebar(); - expect(getByText(t("agent:title"))).toBeInTheDocument(); - }); - it("默认折叠,点击 AI Agent 后展开显示 7 个子项", () => { const { getByText, queryByTestId, getByTestId } = renderSidebar(); expect(queryByTestId("sidebar-agent-submenu")).toBeNull(); diff --git a/src/pages/options/onboarding/steps.test.ts b/src/pages/options/onboarding/steps.test.ts index 53ebc4468..9d8946b21 100644 --- a/src/pages/options/onboarding/steps.test.ts +++ b/src/pages/options/onboarding/steps.test.ts @@ -2,14 +2,6 @@ import { describe, it, expect } from "vitest"; import { DESKTOP_STEPS, MOBILE_STEPS } from "./steps"; describe("巡览步骤配置", () => { - it("桌面应有 6 个步骤", () => { - expect(DESKTOP_STEPS).toHaveLength(6); - }); - - it("移动端应为更精简的 3 个步骤", () => { - expect(MOBILE_STEPS).toHaveLength(3); - }); - it("每个步骤都应带 guide 命名空间的标题与正文 key", () => { for (const s of [...DESKTOP_STEPS, ...MOBILE_STEPS]) { expect(s.titleKey.startsWith("guide:")).toBe(true); @@ -22,4 +14,9 @@ describe("巡览步骤配置", () => { const allowed = new Set([...DESKTOP_STEPS.map((s) => s.id), "subscribe"]); for (const s of MOBILE_STEPS) expect(allowed.has(s.id)).toBe(true); }); + + it("桌面与移动端分别保留 6 步和 3 步巡览", () => { + expect(DESKTOP_STEPS).toHaveLength(6); + expect(MOBILE_STEPS).toHaveLength(3); + }); }); diff --git a/src/pages/options/routes/Agent/Chat/AskUserBlock.test.tsx b/src/pages/options/routes/Agent/Chat/AskUserBlock.test.tsx index 17fd3e9d8..9af62320e 100644 --- a/src/pages/options/routes/Agent/Chat/AskUserBlock.test.tsx +++ b/src/pages/options/routes/Agent/Chat/AskUserBlock.test.tsx @@ -7,11 +7,6 @@ beforeAll(() => initTestLanguage("zh-CN")); afterEach(() => cleanup()); describe("用户提问块 AskUserBlock", () => { - it("展示问题文本", () => { - render(); - expect(screen.getByText("选择一个颜色")).toBeInTheDocument(); - }); - it("单选点击选项后立即提交该选项", () => { const onRespond = vi.fn(); render(); diff --git a/src/pages/options/routes/Agent/Chat/MessageItem.test.tsx b/src/pages/options/routes/Agent/Chat/MessageItem.test.tsx index 7e26f4c3f..9b8a131e6 100644 --- a/src/pages/options/routes/Agent/Chat/MessageItem.test.tsx +++ b/src/pages/options/routes/Agent/Chat/MessageItem.test.tsx @@ -18,11 +18,6 @@ const msg = (over: Partial & Pick) }); describe("用户消息 UserMessageItem", () => { - it("展示用户文本气泡", () => { - render(); - expect(screen.getByText("你好世界")).toBeInTheDocument(); - }); - it("编辑后保存触发 onEdit 携带新文本", () => { const onEdit = vi.fn(); render(); diff --git a/src/pages/options/routes/Agent/Chat/SubAgentBlock.test.tsx b/src/pages/options/routes/Agent/Chat/SubAgentBlock.test.tsx index b037f8bf8..d7b222841 100644 --- a/src/pages/options/routes/Agent/Chat/SubAgentBlock.test.tsx +++ b/src/pages/options/routes/Agent/Chat/SubAgentBlock.test.tsx @@ -19,11 +19,6 @@ const state = (over?: Partial): SubAgentState => ({ }); describe("子代理块 SubAgentBlock", () => { - it("展示子代理描述", () => { - render(); - expect(screen.getByText("搜索资料")).toBeInTheDocument(); - }); - it("依据 isRunning 标注运行/完成状态", () => { const { rerender } = render(); expect(screen.getByTestId("subagent-status").dataset.running).toBe("true"); diff --git a/src/pages/options/routes/Agent/Chat/TaskListBlock.test.tsx b/src/pages/options/routes/Agent/Chat/TaskListBlock.test.tsx index 38e464b3d..ea18481b2 100644 --- a/src/pages/options/routes/Agent/Chat/TaskListBlock.test.tsx +++ b/src/pages/options/routes/Agent/Chat/TaskListBlock.test.tsx @@ -30,9 +30,4 @@ describe("任务清单块 TaskListBlock", () => { expect(screen.getByTestId("task-a").dataset.status).toBe("completed"); expect(screen.getByTestId("task-b").dataset.status).toBe("pending"); }); - - it("展示每个任务的标题", () => { - render(); - expect(screen.getByText("抓取首页")).toBeInTheDocument(); - }); }); diff --git a/src/pages/options/routes/Agent/Chat/ToolCallBlock.test.tsx b/src/pages/options/routes/Agent/Chat/ToolCallBlock.test.tsx index 8bb90b4f1..1a86f570d 100644 --- a/src/pages/options/routes/Agent/Chat/ToolCallBlock.test.tsx +++ b/src/pages/options/routes/Agent/Chat/ToolCallBlock.test.tsx @@ -16,11 +16,6 @@ const tc = (overrides?: Partial): ToolCall => ({ }); describe("工具调用块 ToolCallBlock", () => { - it("始终展示工具名称", () => { - render(); - expect(screen.getByText("web_search")).toBeInTheDocument(); - }); - it("默认折叠,不展示参数", () => { render(); expect(screen.queryByText(/"query":"天气"/)).toBeNull(); diff --git a/src/pages/options/routes/Agent/Mcp/McpCard.test.tsx b/src/pages/options/routes/Agent/Mcp/McpCard.test.tsx index 05f71cbe3..e54de40b3 100644 --- a/src/pages/options/routes/Agent/Mcp/McpCard.test.tsx +++ b/src/pages/options/routes/Agent/Mcp/McpCard.test.tsx @@ -18,12 +18,6 @@ const server = { function noop() {} describe("McpCard MCP 服务器卡片", () => { - it("展示名称与 URL", () => { - render(); - expect(screen.getByText("本地工具")).toBeInTheDocument(); - expect(screen.getByText("http://localhost:8080/mcp")).toBeInTheDocument(); - }); - it("点击开关触发 onToggle", () => { const onToggle = vi.fn(); render(); diff --git a/src/pages/options/routes/Agent/components/AgentCardMenu.test.tsx b/src/pages/options/routes/Agent/components/AgentCardMenu.test.tsx deleted file mode 100644 index 315cf4c07..000000000 --- a/src/pages/options/routes/Agent/components/AgentCardMenu.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, it, expect, vi, afterEach } from "vitest"; -import { render, cleanup, screen, fireEvent } from "@testing-library/react"; -import { Pencil } from "lucide-react"; -import { AgentCardMenu } from "./AgentCardMenu"; - -afterEach(() => cleanup()); - -describe("AgentCardMenu 卡片菜单", () => { - it("点击菜单项触发 onSelect", () => { - const onSelect = vi.fn(); - render(); - // Radix 触发器在 pointerdown(左键) 时展开菜单——真实点击即包含此事件 - fireEvent.pointerDown(screen.getByTestId("card-menu"), { button: 0 }); - fireEvent.click(screen.getByTestId("card-menu-edit")); - expect(onSelect).toHaveBeenCalledTimes(1); - }); -}); diff --git a/src/pages/options/routes/Agent/components/agentDocs.test.ts b/src/pages/options/routes/Agent/components/agentDocs.test.ts index 025e906e6..1e4f57257 100644 --- a/src/pages/options/routes/Agent/components/agentDocs.test.ts +++ b/src/pages/options/routes/Agent/components/agentDocs.test.ts @@ -10,11 +10,4 @@ describe("agentDocUrl 文档深链", () => { expect(agentDocUrl("opfs")).toBe("https://docs.scriptcat.org/docs/dev/agent/agent-opfs"); expect(agentDocUrl("settings")).toBe("https://docs.scriptcat.org/docs/dev/agent/agent"); }); - - it("文档链接均不是站点根(确保深链)", () => { - for (const page of ["provider", "skills", "mcp", "tasks", "opfs", "settings"] as const) { - expect(agentDocUrl(page)).not.toBe("https://docs.scriptcat.org"); - expect(agentDocUrl(page)).toContain("/docs/dev/agent/"); - } - }); }); diff --git a/src/pages/options/routes/Setting/sections/RuntimeSection.tsx b/src/pages/options/routes/Setting/sections/RuntimeSection.tsx index 5fb6bb420..4a1d9ec3e 100644 --- a/src/pages/options/routes/Setting/sections/RuntimeSection.tsx +++ b/src/pages/options/routes/Setting/sections/RuntimeSection.tsx @@ -155,7 +155,7 @@ export function RuntimeSection({ register }: { register: (id: string) => (el: HT label={t("settings:keep_scripts_alive.title")} description={t("settings:keep_scripts_alive.description")} > - + )} @@ -165,7 +165,7 @@ export function RuntimeSection({ register }: { register: (id: string) => (el: HT label={t("settings:keep_scripts_alive.title")} description={t("settings:keep_scripts_alive.description")} > - + )} diff --git a/src/pkg/backup/backup.test.ts b/src/pkg/backup/backup.test.ts index fec61d9c9..a58eb5b87 100644 --- a/src/pkg/backup/backup.test.ts +++ b/src/pkg/backup/backup.test.ts @@ -114,75 +114,6 @@ describe.concurrent("backup", () => { expect(resp).toEqual(data); }); - it.concurrent("export and import script - name and version only", async () => { - const zipFile = createJSZip(); - const fs = new ZipFileSystem(zipFile); - const data: BackupData = { - script: [ - { - code: `// ==UserScript== - // @name New Userscript - // @version 1 - // ==/UserScript== - - console.log('hello world')`, - options: { - options: {}, - meta: { - name: "test", - modified: 1, - file_url: "", - }, - settings: { - enabled: true, - position: 1, - }, - }, - resources: [ - { - meta: { name: "test1", mimetype: "text/plain" }, - base64: "data:text/plain;base64,aGVsbG8gd29ybGQ=", - source: "hello world", - }, - ], - requires: [ - { - meta: { name: "test2", mimetype: "text/plain" }, - base64: "data:text/plain;base64,aGVsbG8gd29ybGQ=", - source: "hello world", - }, - ], - requiresCss: [ - { - meta: { name: "test3", mimetype: "application/javascript" }, - base64: "data:application/javascript;base64,aGVsbG8gd29ybGQ=", - source: "hello world", - }, - ], - storage: { - ts: ts0 + 2, - data: { - num: 1, - str: "data", - bool: false, - }, - }, - lastModificationDate: expect.any(Number), - }, - ], - subscribe: [], - } as unknown as BackupData; - await new BackupExport(fs).export(data); - expect(data.script[0].storage.data.num).toEqual("n1"); - expect(data.script[0].storage.data.str).toEqual("sdata"); - expect(data.script[0].storage.data.bool).toEqual("bfalse"); - const resp = await parseBackupZipFile(zipFile); - data.script[0].storage.data.num = 1; - data.script[0].storage.data.str = "data"; - data.script[0].storage.data.bool = false; - expect(resp).toEqual(data); - }); - it.concurrent("export and import script - 2 scripts", async () => { const zipFile = createJSZip(); const fs = new ZipFileSystem(zipFile); diff --git a/src/pkg/utils/async_queue.test.ts b/src/pkg/utils/async_queue.test.ts index 22a70ce60..42623db50 100644 --- a/src/pkg/utils/async_queue.test.ts +++ b/src/pkg/utils/async_queue.test.ts @@ -347,24 +347,4 @@ describe.concurrent("stackAsyncTask 测试", () => { expect(order).toEqual([0, 1, 2, 3, 4]); expect(results).toEqual([0, 1, 2, 3, 4]); }); - - /* ------------------- 7. 跨 key 链接(正确 await 返回值) ------------------- */ - it.concurrent("【7】跨 key 链接:内部任务返回值可被外层 await(不 await stackAsyncTask)", async () => { - const kOuter = generateKey("outer"); - const kInner = generateKey("inner"); - - const pOuter = stackAsyncTask(kOuter, async () => { - const pInner = stackAsyncTask(kInner, async () => { - return "inner-data"; - }); - const data = await pInner; // 正确:await 返回值 - return `outer(${data})`; - }); - - setupBlockingTask(kOuter).resolve(); - setupBlockingTask(kInner).resolve(); - await flush(); - - await expect(pOuter).resolves.toBe("outer(inner-data)"); - }); }); diff --git a/src/pkg/utils/match.test.ts b/src/pkg/utils/match.test.ts index 6a13ceea4..a7f7ccbfd 100644 --- a/src/pkg/utils/match.test.ts +++ b/src/pkg/utils/match.test.ts @@ -205,9 +205,6 @@ describe.concurrent("UrlMatch-google", () => { expect(url.urlMatch("https://www.google.com/foo/baz/bar")).toEqual(["ok1", "ok2", "ok3"]); expect(url.urlMatch("https://docs.google.com/foobar")).toEqual(["ok1", "ok2", "ok3"]); }); - it.concurrent("match4", () => { - expect(url.urlMatch("https://example.org/foo/bar.html")).toEqual(["ok1", "ok2", "ok4"]); - }); it.concurrent("match5", () => { expect(url.urlMatch("http://127.0.0.1/")).toEqual(["ok5"]); expect(url.urlMatch("http://127.0.0.1/foo/bar.html")).toEqual(["ok5"]); diff --git a/src/pkg/utils/message_value.test.ts b/src/pkg/utils/message_value.test.ts index f0c10e01a..77f66d4da 100644 --- a/src/pkg/utils/message_value.test.ts +++ b/src/pkg/utils/message_value.test.ts @@ -55,13 +55,6 @@ describe.concurrent("encodeRValue 编码函数", () => { expect(encoded[0]).toBe(RType.STANDARD); expect(encoded[1]).toBe(big); }); - - it.concurrent("应正确处理联合类型的编码", () => { - const value: string | null = "联合类型测试"; - const encoded = encodeRValue(value); - expect(encoded[0]).toBe(RType.STANDARD); - expect(encoded[1]).toBe(value); - }); }); describe.concurrent("decodeRValue 解码函数", () => { @@ -145,13 +138,4 @@ describe.concurrent("encodeRValue 与 decodeRValue 组合行为", () => { } }); }); - - it.concurrent("应对联合类型值进行正确的往返编码解码", () => { - type Union = string | number | null | undefined; - const values: Union[] = [undefined, null, 1, 0, 123, "abc", ""]; - - const roundTrip = values.map((v) => decodeRValue(encodeRValue(v))); - - expect(roundTrip).toEqual(values); - }); }); diff --git a/src/pkg/utils/regex_to_glob.test.ts b/src/pkg/utils/regex_to_glob.test.ts index db7232715..81bd2cc7d 100644 --- a/src/pkg/utils/regex_to_glob.test.ts +++ b/src/pkg/utils/regex_to_glob.test.ts @@ -404,19 +404,5 @@ describe.concurrent("regexToGlob - comprehensive test suite (regrouped & comment ok("(cat|car|cap)\\.txt", "ca?.txt"); // prior pattern with fixed suffix ok("v(?:\\d{2}|latest)", "v??*"); // min 2 digits, or 'latest' -> '??*' }); - - it.concurrent("8.8 invalid regex from additional remain null", () => { - // 继续确保无效正则 → null - // Still invalid → null - // 注:呼叫regexToGlob前,已经用 new RegExp 生成,所以不会出现非法RegEx字串 - bad("(ab"); - bad("test\\"); - bad("([a-z]"); // unbalanced () and [] - // bad("(?P\\w+)"); // unsupported named group (PCRE-style) - // bad("(?'name'\\w+)"); // unsupported named group (alternate syntax) - // bad("(?|a|b)"); // branch reset group (PCRE), unsupported - // bad("a**"); // consecutive quantifiers invalid - // bad("*"); // bare quantifier invalid - }); }); }); diff --git a/src/pkg/utils/script.test.ts b/src/pkg/utils/script.test.ts index d21e95365..2c1cf044b 100644 --- a/src/pkg/utils/script.test.ts +++ b/src/pkg/utils/script.test.ts @@ -386,41 +386,6 @@ console.log('Hello World'); expect(result?.author).toEqual([""]); }); - it.concurrent("正確解析元数据(空version)", () => { - const code = ` -// ==UserScript== -// @name 测试脚本 -// @namespace http://tampermonkey.net/ -// @match https://example.org/* -// @match https://test.com/* -// @match https://demo.com/* -// @description -// @early-start -// @author -// @match https://example.com/* -// @grant - GM_setValue -// @grant GM_getValue -// ==/UserScript== -console.log('Hello World'); -`; - - const result = parseMetadata(code); - expect(result).not.toBeNull(); - expect(result?.name).toEqual(["测试脚本"]); - expect(result?.namespace).toEqual(["http://tampermonkey.net/"]); - expect(result?.match).toEqual([ - "https://example.org/*", - "https://test.com/*", - "https://demo.com/*", - "https://example.com/*", - ]); - expect(result?.["early-start"]).toEqual([""]); - expect(result?.grant).toEqual(["", "GM_getValue"]); - expect(result?.description).toEqual([""]); - expect(result?.author).toEqual([""]); - }); - it.concurrent("正確解析元数据(換行空白1)", () => { const code = ` // ==UserScript== diff --git a/src/pkg/utils/skill-md.test.ts b/src/pkg/utils/skill-md.test.ts index 82a7685a0..1b93194d4 100644 --- a/src/pkg/utils/skill-md.test.ts +++ b/src/pkg/utils/skill-md.test.ts @@ -158,36 +158,6 @@ Prompt.`; expect(result.metadata.references).toBeUndefined(); }); - it("应正确解析完整的 SKILL.cat.md(含 version + scripts + references + config)", () => { - const content = `--- -name: price-compare -description: 多平台比价 -version: 2.0.0 -scripts: - - compare.js -references: - - api_docs.md -config: - api_key: - title: API Key - type: text - secret: true ---- - -# Price Compare - -比价工具使用说明。`; - - const result = parseSkillMd(content)!; - expect(result.metadata.name).toBe("price-compare"); - expect(result.metadata.version).toBe("2.0.0"); - expect(result.metadata.scripts).toEqual(["compare.js"]); - expect(result.metadata.references).toEqual(["api_docs.md"]); - expect(result.metadata.config).toBeDefined(); - expect(result.metadata.config!.api_key.secret).toBe(true); - expect(result.prompt).toContain("# Price Compare"); - }); - it("scripts 中过滤非字符串值", () => { const content = `--- name: filter-test @@ -324,29 +294,4 @@ Prompt.`; const result = parseSkillMd(content)!; expect(result.metadata.config).toBeUndefined(); }); - - it("应正确处理多行 prompt 内容", () => { - const content = `--- -name: multi-line -description: test ---- - -# Title - -Paragraph 1. - -## Subtitle - -- item 1 -- item 2 - -\`\`\`js -console.log("hello"); -\`\`\``; - - const result = parseSkillMd(content)!; - expect(result.prompt).toContain("# Title"); - expect(result.prompt).toContain("- item 1"); - expect(result.prompt).toContain('console.log("hello");'); - }); }); diff --git a/src/pkg/utils/skill-zip.test.ts b/src/pkg/utils/skill-zip.test.ts index cf1dd2bc0..6273397be 100644 --- a/src/pkg/utils/skill-zip.test.ts +++ b/src/pkg/utils/skill-zip.test.ts @@ -190,59 +190,4 @@ description: 淘宝购物助手 expect(toolMeta!.params[0].name).toBe("pageType"); expect(toolMeta!.params[1].name).toBe("tabId"); }); - - it("ZIP 解析输出结构与 installSkill 参数签名一致", async () => { - const zipData = await createTestZip({ - "SKILL.md": `---\nname: sig-test\ndescription: Signature test\n---\nPrompt.`, - "scripts/helper.js": VALID_SKILLSCRIPT_CODE, - "references/doc.md": "Doc content", - }); - - const result = await parseSkillZip(zipData); - - // 验证结构:skillMd 是 string,scripts 是 {name, code}[],references 是 {name, content}[] - expect(typeof result.skillMd).toBe("string"); - expect(Array.isArray(result.scripts)).toBe(true); - expect(Array.isArray(result.references)).toBe(true); - - for (const s of result.scripts) { - expect(typeof s.name).toBe("string"); - expect(typeof s.code).toBe("string"); - expect(s.name).toBeTruthy(); - expect(s.code).toBeTruthy(); - } - - for (const r of result.references) { - expect(typeof r.name).toBe("string"); - expect(typeof r.content).toBe("string"); - expect(r.name).toBeTruthy(); - expect(r.content).toBeTruthy(); - } - }); - - it("嵌套目录 ZIP 的完整流程:解析 → 验证 SKILL.md → 验证 SkillScript", async () => { - const zipData = await createTestZip({ - "taobao-skill/SKILL.md": `---\nname: nested-skill\ndescription: 嵌套目录测试\n---\n嵌套 Skill 提示词。`, - "taobao-skill/scripts/extract.js": VALID_SKILLSCRIPT_CODE, - "taobao-skill/references/guide.txt": "使用指南内容", - }); - - const zipResult = await parseSkillZip(zipData); - - // Step 1: SKILL.md 正确 - const parsed = parseSkillMd(zipResult.skillMd); - expect(parsed).not.toBeNull(); - expect(parsed!.metadata.name).toBe("nested-skill"); - - // Step 2: SkillScript 正确 - expect(zipResult.scripts).toHaveLength(1); - const toolMeta = parseSkillScriptMetadata(zipResult.scripts[0].code); - expect(toolMeta).not.toBeNull(); - expect(toolMeta!.name).toBe("taobao_extract"); - - // Step 3: references 正确 - expect(zipResult.references).toHaveLength(1); - expect(zipResult.references[0].name).toBe("guide.txt"); - expect(zipResult.references[0].content).toBe("使用指南内容"); - }); }); diff --git a/src/pkg/utils/skill_script.test.ts b/src/pkg/utils/skill_script.test.ts index 0faddd502..ad4d36754 100644 --- a/src/pkg/utils/skill_script.test.ts +++ b/src/pkg/utils/skill_script.test.ts @@ -71,20 +71,6 @@ return args.value * 2; expect(meta.params[1].required).toBe(false); }); - it("应正确解析无参数的工具", () => { - const code = ` -// ==SkillScript== -// @name ping -// @description 测试连通性 -// ==/SkillScript== -return "pong"; -`; - const meta = parseSkillScriptMetadata(code)!; - expect(meta.name).toBe("ping"); - expect(meta.params).toHaveLength(0); - expect(meta.grants).toHaveLength(0); - }); - it("应正确解析多个 @grant", () => { const code = ` // ==SkillScript== @@ -100,19 +86,6 @@ return "ok"; expect(meta.grants).toEqual(["GM.xmlHttpRequest", "GM.getValue", "GM.setValue"]); }); - it("应正确解析单个 @require URL", () => { - const code = ` -// ==SkillScript== -// @name xlsx_tool -// @description 生成 Excel -// @require https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js -// ==/SkillScript== -return XLSX.utils.book_new(); -`; - const meta = parseSkillScriptMetadata(code)!; - expect(meta.requires).toEqual(["https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"]); - }); - it("应正确解析多个 @require URL", () => { const code = ` // ==SkillScript== @@ -329,21 +302,4 @@ return x;`; const body = getSkillScriptBody(code); expect(body).toBe("const x = 1;\nreturn x;"); }); - - it("应保留元数据头后面的所有代码", () => { - const code = `// ==SkillScript== -// @name test -// @description 测试 -// @param city string [required] 城市 -// @grant GM.xmlHttpRequest -// ==/SkillScript== - -const result = await GM.xmlHttpRequest({url: "http://example.com/" + args.city}); -const data = JSON.parse(result.responseText); -return data;`; - const body = getSkillScriptBody(code); - expect(body).toContain("const result = await GM.xmlHttpRequest"); - expect(body).toContain("return data;"); - expect(body).not.toContain("==SkillScript=="); - }); }); diff --git a/src/pkg/utils/url-utils.test.ts b/src/pkg/utils/url-utils.test.ts index 13e29f457..263bea744 100644 --- a/src/pkg/utils/url-utils.test.ts +++ b/src/pkg/utils/url-utils.test.ts @@ -21,10 +21,6 @@ describe.concurrent("prettyUrl", () => { it.concurrent("should decode Emoji domains", () => { expect(prettyUrl("https://xn--vi8h.la/path")).toBe("https://🍕.la/path"); }); - - it.concurrent("should handle mixed Latin and Foreign scripts", () => { - expect(prettyUrl("http://xn--maana-pta.com")).toBe("http://mañana.com/"); - }); }); describe.concurrent("Path and Percent Encoding", () => { diff --git a/src/pkg/utils/url_matcher.test.ts b/src/pkg/utils/url_matcher.test.ts index bc3f36c81..62e7781fc 100644 --- a/src/pkg/utils/url_matcher.test.ts +++ b/src/pkg/utils/url_matcher.test.ts @@ -999,13 +999,4 @@ describe.concurrent("embeddedPatternChecker", () => { const code3 = embeddedPatternCheckerString('"https://example.com/secret/data"', JSON.stringify(reduced)); expect(eval(code3)).toBe(false); }); - - it.concurrent("embeddedPatternCheckerString 生成可执行代码", () => { - const patterns = extractUrlPatterns(["@match *://example.com/*"]); - const reduced = patterns.map(({ ruleType, ruleContent }) => ({ ruleType, ruleContent })); - const codeStr = embeddedPatternCheckerString("location.href", JSON.stringify(reduced)); - // 验证生成的是一个函数调用表达式字符串(IIFE 形式) - expect(typeof codeStr).toBe("string"); - expect(codeStr).toContain("location.href"); - }); }); diff --git a/src/pkg/utils/utils.test.ts b/src/pkg/utils/utils.test.ts index 3add3ea08..2a3762870 100644 --- a/src/pkg/utils/utils.test.ts +++ b/src/pkg/utils/utils.test.ts @@ -572,10 +572,6 @@ describe.concurrent("normalizeResponseHeaders", () => { expect(normalizeResponseHeaders("")).toBe(""); }); - it.concurrent("returns empty string for falsy-like empty string (only case possible with string type)", () => { - expect(normalizeResponseHeaders(String(""))).toBe(""); - }); - it.concurrent("keeps valid header lines and outputs name:value joined with CRLF", () => { const input = "Content-Type: text/plain\nX-Test: abc\n"; expect(normalizeResponseHeaders(input)).toBe("Content-Type:text/plain\r\nX-Test:abc"); @@ -601,11 +597,6 @@ describe.concurrent("normalizeResponseHeaders", () => { expect(normalizeResponseHeaders(input)).toBe("X-名前:値"); }); - it.concurrent("does not include a trailing CRLF at the end of output", () => { - const input = "A: 1\nB: 2\n"; - expect(normalizeResponseHeaders(input).endsWith("\r\n")).toBe(false); - }); - it.concurrent("standard test", () => { const input = `content-type: text/html; charset=utf-8\r\n server: Apache/2.4.41 (Ubuntu)\r\n diff --git a/vitest.config.ts b/vitest.config.ts index f21eb862f..01f9c8ef7 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -31,7 +31,7 @@ const ISOLATED = [ "src/app/service/content/exec_script.test.ts", ]; -const BASE_EXCLUDE = ["**/node_modules/**", "**/.claude/**", "e2e/**"]; +const BASE_EXCLUDE = ["**/node_modules/**", "**/.claude/**", "**/.dev-kit/**", "e2e/**"]; // 页面层(React 渲染,含 .ts 的 renderHook 测试)用例的真实 solo 成本在覆盖率下可达 100–200ms, // 乘上 worker 并行负载后 340ms 预算必然偶发超时(本地满载观测峰值 ~630ms);