fix: 修正 markdown-to-jsx 导入方式 + 新增 formatDateLabel 日期工具函数

- TextPart: default import → named import
- MaterialCard: 使用 formatDateLabel 显示今天/昨天/日期
- 清理旧测试文件,新增 ResourceTable 测试
This commit is contained in:
2026-06-03 21:08:00 +08:00
parent 83cc28fe1b
commit eb93de52d8
17 changed files with 252 additions and 1177 deletions

View File

@@ -1,398 +0,0 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import { describe, expect, mock, test } from "bun:test";
import { createElement, useRef } from "react";
import {
useCreateModel,
useDeleteModel,
useTestModelConnection,
useUpdateModel,
} from "../../../src/web/shared/hooks/use-models";
import {
useArchiveProject,
useCreateProject,
useDeleteProject,
useRestoreProject,
useUpdateProject,
} from "../../../src/web/shared/hooks/use-projects";
import {
useCreateProvider,
useDeleteProvider,
useTestProviderConfig,
useUpdateProvider,
} from "../../../src/web/shared/hooks/use-providers";
import { installFetchMock, jsonResponse } from "../test-utils";
const MODEL = {
autoAdapt: true,
capabilities: ["text"] as string[],
createdAt: "2024-01-01T00:00:00.000Z",
customApiKey: null,
customBaseUrl: null,
description: "测试模型",
id: "m1",
modelId: "gpt-4",
name: "测试模型",
providerId: "prov-1",
updatedAt: "2024-01-01T00:00:00.000Z",
};
const PROJECT = {
archivedAt: null,
createdAt: "2024-01-01T00:00:00.000Z",
description: "测试",
id: "p1",
name: "测试项目",
status: "active" as const,
updatedAt: "2024-01-01T00:00:00.000Z",
};
const PROVIDER = {
createdAt: "2024-01-01T00:00:00.000Z",
id: "prov-1",
name: "测试供应商",
type: "openai" as const,
updatedAt: "2024-01-01T00:00:00.000Z",
};
function getLogMessages(spy: ReturnType<typeof mock>) {
return spy.mock.calls.map((c) => c[0] as string).filter((s) => s.includes("[Alfred:INFO]"));
}
function makeQueryClient() {
return new QueryClient({
defaultOptions: { queries: { retry: false } },
});
}
function setupModelFetches(result: unknown) {
installFetchMock((call) => {
if (call.method === "DELETE") return new Response(null, { status: 204 });
if (call.url.includes("test")) return jsonResponse({ modelTestResponse: { message: "ok", ok: true } });
return jsonResponse({ model: result }, { status: 201 });
});
}
function setupProjectFetches(result: unknown) {
installFetchMock((call) => {
if (call.method === "DELETE") return new Response(null, { status: 204 });
if (call.url.includes("archive")) return jsonResponse({ project: result });
if (call.url.includes("restore")) return jsonResponse({ project: result });
return jsonResponse({ project: result }, { status: 201 });
});
}
function setupProviderFetches(result: unknown) {
installFetchMock((call) => {
if (call.method === "DELETE") return new Response(null, { status: 204 });
if (call.url.includes("test")) return jsonResponse({ providerTestResponse: { message: "ok", ok: true } });
return jsonResponse({ provider: result }, { status: 201 });
});
}
function spyConsoleLog() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const spy = mock((..._args: any[]) => {});
const orig = console.log;
console.log = spy;
return { orig, restore: () => (console.log = orig), spy };
}
describe("useProjects onSuccess 日志", () => {
const qc = makeQueryClient();
test("create onSuccess", async () => {
setupProjectFetches(PROJECT);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useCreateProject();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ name: "x" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/项目创建成功/);
restore();
});
test("update onSuccess", async () => {
setupProjectFetches(PROJECT);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useUpdateProject();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ data: { name: "y" }, id: "p1" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/项目更新成功/);
restore();
});
test("delete onSuccess", async () => {
setupProjectFetches(PROJECT);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useDeleteProject();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate("p1"));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/项目删除成功/);
restore();
});
test("archive onSuccess", async () => {
setupProjectFetches(PROJECT);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useArchiveProject();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate("p1"));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/项目归档成功/);
restore();
});
test("restore onSuccess", async () => {
setupProjectFetches(PROJECT);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useRestoreProject();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate("p1"));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/项目恢复成功/);
restore();
});
});
describe("useModels onSuccess 日志", () => {
const qc = makeQueryClient();
test("create onSuccess", async () => {
setupModelFetches(MODEL);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useCreateModel();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ capabilities: ["text"], modelId: "gpt-4", name: "x", providerId: "p1" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/模型创建成功/);
restore();
});
test("update onSuccess", async () => {
setupModelFetches(MODEL);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useUpdateModel();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ data: { name: "y" }, id: "m1" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/模型更新成功/);
restore();
});
test("delete onSuccess", async () => {
setupModelFetches(MODEL);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useDeleteModel();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate("m1"));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/模型删除成功/);
restore();
});
test("test onSuccess", async () => {
setupModelFetches(MODEL);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useTestModelConnection();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ modelId: "gpt-4", providerId: "p1" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
restore();
// useTestModelConnection has no onSuccess logger
const infoCalls = spy.mock.calls.filter((c) => typeof c[0] === "string" && c[0].includes("[Alfred:INFO]"));
expect(infoCalls.length).toBe(0);
});
});
describe("useProviders onSuccess 日志", () => {
const qc = makeQueryClient();
test("create onSuccess", async () => {
setupProviderFetches(PROVIDER);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useCreateProvider();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ apiKey: "k", baseUrl: "http://x", name: "x", type: "openai" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/供应商创建成功/);
restore();
});
test("update onSuccess", async () => {
setupProviderFetches(PROVIDER);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useUpdateProvider();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ data: { name: "y" }, id: "prov-1" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/供应商更新成功/);
restore();
});
test("delete onSuccess", async () => {
setupProviderFetches(PROVIDER);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useDeleteProvider();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate("prov-1"));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
const msgs = getLogMessages(spy);
expect(msgs).toHaveLength(1);
expect(msgs[0]).toMatch(/供应商删除成功/);
restore();
});
test("test onSuccess", async () => {
setupProviderFetches(PROVIDER);
const { restore, spy } = spyConsoleLog();
function T({ onResult }: { onResult: (fn: () => void) => void }) {
const { mutate } = useTestProviderConfig();
const c = useRef(false);
if (!c.current) {
c.current = true;
onResult(() => mutate({ apiKey: "k", baseUrl: "http://x", name: "x", type: "openai" }));
}
return null;
}
render(createElement(QueryClientProvider, { client: qc }, createElement(T, { onResult: (fn) => fn() })));
await new Promise((r) => setTimeout(r, 200));
restore();
// useTestProviderConfig has no onSuccess logger
const infoMsgs = spy.mock.calls.filter((c) => typeof c[0] === "string" && String(c[0]).includes("[Alfred:INFO]"));
expect(infoMsgs.length).toBe(0);
});
});