fix: 修正 markdown-to-jsx 导入方式 + 新增 formatDateLabel 日期工具函数
- TextPart: default import → named import - MaterialCard: 使用 formatDateLabel 显示今天/昨天/日期 - 清理旧测试文件,新增 ResourceTable 测试
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user