732 lines
25 KiB
React
732 lines
25 KiB
React
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
} from "@testing-library/react";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { Refine } from "@refinedev/core";
|
|
import { MemoryRouter, Route, Routes } from "react-router";
|
|
import { AccountEditPage } from "./AccountEditPage";
|
|
import { AccountList } from "./AccountsPage";
|
|
import { CreatorCompetitorsPage } from "./CreatorCompetitorsPage";
|
|
import { CreatorSettingsPage } from "./CreatorSettingsPage";
|
|
import { CreatorWorkbenchPage } from "./CreatorWorkbenchPage";
|
|
|
|
const profile = {
|
|
id: "account-a",
|
|
name: "主账号",
|
|
platform: "douyin",
|
|
platform_account_key: "sec-a",
|
|
authorization_kind: "owned",
|
|
authorization_status: "authorized",
|
|
runtime_status: "running",
|
|
business_status: "normal",
|
|
login_status: "logged_in",
|
|
real_name_status: "recorded",
|
|
big_account: false,
|
|
cooldown_seconds: 86400,
|
|
updated_at: "2026-01-01T00:00:00Z",
|
|
};
|
|
const settings = {
|
|
lookback_days: 30,
|
|
new_work_interval_seconds: 1800,
|
|
metric_initial_interval_seconds: 3600,
|
|
metric_multiplier: 2,
|
|
metric_max_interval_seconds: 86400,
|
|
metric_age_seconds: 2592000,
|
|
ai_provider: "",
|
|
ai_model: "",
|
|
ai_configured: false,
|
|
transcription_provider: "",
|
|
transcription_model: "",
|
|
transcription_configured: false,
|
|
};
|
|
|
|
function provider(overrides = {}) {
|
|
return {
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-accounts"
|
|
? [profile]
|
|
: resource === "creator-competitors"
|
|
? []
|
|
: resource === "creator-works"
|
|
? []
|
|
: [],
|
|
total: resource === "creator-accounts" ? 1 : 0,
|
|
}),
|
|
),
|
|
creatorGet: vi.fn((path) =>
|
|
Promise.resolve(path === "/creator/settings" ? settings : []),
|
|
),
|
|
creatorUpdate: vi.fn().mockResolvedValue(settings),
|
|
creatorAction: vi.fn().mockResolvedValue({ ...profile, big_account: true }),
|
|
creatorCreate: vi.fn().mockResolvedValue({ id: "created", ...profile }),
|
|
create: vi.fn().mockResolvedValue({
|
|
data: {
|
|
id: "competitor-a",
|
|
account_id: "account-a",
|
|
platform: "douyin",
|
|
platform_account_key: "sec-b",
|
|
homepage_url: "https://www.douyin.com/user/sec-b",
|
|
enabled: true,
|
|
},
|
|
}),
|
|
getOne: vi.fn(),
|
|
update: vi.fn(),
|
|
delete: vi.fn(),
|
|
getMany: vi.fn(),
|
|
getManyReference: vi.fn(),
|
|
updateMany: vi.fn(),
|
|
deleteMany: vi.fn(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function renderPage(element, dataProvider) {
|
|
return render(
|
|
<QueryClientProvider client={new QueryClient()}>
|
|
<Refine dataProvider={dataProvider}>
|
|
<MemoryRouter>{element}</MemoryRouter>
|
|
</Refine>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
function renderAccountPage(dataProvider) {
|
|
return render(
|
|
<QueryClientProvider client={new QueryClient()}>
|
|
<Refine dataProvider={dataProvider}>
|
|
<MemoryRouter initialEntries={["/accounts/account-a/edit"]}>
|
|
<Routes>
|
|
<Route path="/accounts/:id/edit" element={<AccountEditPage />} />
|
|
</Routes>
|
|
</MemoryRouter>
|
|
</Refine>
|
|
</QueryClientProvider>,
|
|
);
|
|
}
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("creator pages", () => {
|
|
it("loads and saves collection settings", async () => {
|
|
const dataProvider = provider();
|
|
renderPage(<CreatorSettingsPage />, dataProvider);
|
|
expect(await screen.findByText("采集设置")).toBeTruthy();
|
|
fireEvent.change(screen.getByLabelText("竞品回溯天数"), {
|
|
target: { value: "14" },
|
|
});
|
|
fireEvent.click(screen.getByRole("button", { name: "保存设置" }));
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorUpdate).toHaveBeenCalledWith(
|
|
"/creator/settings",
|
|
expect.objectContaining({ lookback_days: 14 }),
|
|
),
|
|
);
|
|
expect(dataProvider.creatorUpdate).not.toHaveBeenCalledWith(
|
|
"/creator/settings",
|
|
expect.objectContaining({ updated_at: expect.anything() }),
|
|
);
|
|
});
|
|
|
|
it("verifies account identity and toggles big-account mode", async () => {
|
|
const dataProvider = provider({
|
|
creatorAction: vi.fn((path) =>
|
|
Promise.resolve(
|
|
path.endsWith("/verify")
|
|
? { ...profile, login_status: "logged_in" }
|
|
: { ...profile, big_account: true },
|
|
),
|
|
),
|
|
});
|
|
renderAccountPage(dataProvider);
|
|
expect(
|
|
await screen.findByRole("button", { name: "开启大号模式" }),
|
|
).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "核验浏览器身份" }));
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorAction).toHaveBeenCalledWith(
|
|
"/creator/accounts/account-a/verify",
|
|
),
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: "开启大号模式" }));
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorAction).toHaveBeenCalledWith(
|
|
"/creator/accounts/account-a/big-account",
|
|
{ enabled: true },
|
|
),
|
|
);
|
|
expect(await screen.findByText("已开启大号模式。")).toBeTruthy();
|
|
});
|
|
|
|
it("only offers owned accounts as execution small accounts", async () => {
|
|
const ownedProfile = { ...profile, big_account: true };
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-accounts"
|
|
? [
|
|
ownedProfile,
|
|
{
|
|
...profile,
|
|
id: "authorized-a",
|
|
name: "监测账号",
|
|
authorization_kind: "authorized",
|
|
},
|
|
]
|
|
: [],
|
|
total: resource === "creator-accounts" ? 2 : 0,
|
|
}),
|
|
),
|
|
});
|
|
renderAccountPage(dataProvider);
|
|
const executionAccount = await screen.findByRole("combobox", {
|
|
name: "执行小号",
|
|
});
|
|
fireEvent.click(executionAccount);
|
|
expect(screen.queryByRole("option", { name: "监测账号" })).toBeNull();
|
|
expect(
|
|
screen.getByText("这里只显示自有账号;监测账号不能作为小号。"),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it("displays the CreatorHub login screen returned by the gateway", async () => {
|
|
const dataProvider = provider({
|
|
creatorAction: vi.fn((path) =>
|
|
path.endsWith("/login-qr")
|
|
? Promise.resolve({
|
|
status: "manual_login",
|
|
content_type: "image/png",
|
|
image_base64: "cG5n",
|
|
qr_detected: true,
|
|
expires_at: "2026-01-01T00:02:00Z",
|
|
})
|
|
: Promise.resolve({ ...profile, login_status: "logged_in" }),
|
|
),
|
|
});
|
|
renderAccountPage(dataProvider);
|
|
fireEvent.click(
|
|
await screen.findByRole("button", { name: "显示登录二维码" }),
|
|
);
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorAction).toHaveBeenCalledWith(
|
|
"/creator/accounts/account-a/login-qr",
|
|
),
|
|
);
|
|
expect(
|
|
screen
|
|
.getByRole("img", { name: "抖音登录二维码或人工验证画面" })
|
|
.getAttribute("src"),
|
|
).toBe("data:image/png;base64,cG5n");
|
|
expect(
|
|
await screen.findByText("请使用抖音 App 扫描下方二维码。"),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it("shows the environment reason when login QR startup is unavailable", async () => {
|
|
const dataProvider = provider({
|
|
creatorAction: vi.fn().mockRejectedValue({
|
|
status: 503,
|
|
message: "creator capability is unavailable",
|
|
body: {
|
|
error: "creator capability is unavailable",
|
|
reason: "creator capability is unavailable: start account environment: gateway unavailable",
|
|
},
|
|
}),
|
|
});
|
|
renderAccountPage(dataProvider);
|
|
fireEvent.click(
|
|
await screen.findByRole("button", { name: "显示登录二维码" }),
|
|
);
|
|
expect(
|
|
await screen.findByText(
|
|
"环境不可用(503):creator capability is unavailable: start account environment: gateway unavailable",
|
|
),
|
|
).toBeTruthy();
|
|
});
|
|
|
|
it("shows retryable account and strategy loading failures", async () => {
|
|
const dataProvider = provider({
|
|
getList: vi.fn().mockRejectedValue(new Error("accounts offline")),
|
|
creatorGet: vi.fn().mockRejectedValue(new Error("creator offline")),
|
|
});
|
|
renderAccountPage(dataProvider);
|
|
expect(await screen.findByText("accounts offline")).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "重试" }));
|
|
await waitFor(() => expect(dataProvider.getList).toHaveBeenCalledTimes(2));
|
|
});
|
|
|
|
it("shows settings save conflicts", async () => {
|
|
const dataProvider = provider({
|
|
creatorUpdate: vi.fn().mockRejectedValue({
|
|
status: 409,
|
|
body: { reason: "version changed" },
|
|
}),
|
|
});
|
|
renderPage(<CreatorSettingsPage />, dataProvider);
|
|
expect(await screen.findByText("采集设置")).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "保存设置" }));
|
|
expect(await screen.findByText("冲突(409):设置保存失败")).toBeTruthy();
|
|
});
|
|
|
|
it("saves settings successfully", async () => {
|
|
const dataProvider = provider({
|
|
creatorUpdate: vi.fn().mockResolvedValue({}),
|
|
});
|
|
renderPage(<CreatorSettingsPage />, dataProvider);
|
|
expect(await screen.findByText("采集设置")).toBeTruthy();
|
|
fireEvent.click(screen.getByLabelText("已完成 AI 配置审批"));
|
|
fireEvent.click(screen.getByRole("button", { name: "保存设置" }));
|
|
expect(await screen.findByText("采集与 AI 配置已保存。")).toBeTruthy();
|
|
});
|
|
|
|
it("does not render stale data using another tab's shape", async () => {
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-comments"
|
|
? [
|
|
{
|
|
id: "comment-a",
|
|
author_name: "评论用户",
|
|
author_uid: "peer-a",
|
|
content: "你好",
|
|
platform: "douyin",
|
|
work_id: "work-a",
|
|
},
|
|
]
|
|
: resource === "creator-rules"
|
|
? [
|
|
{
|
|
id: "rule-a",
|
|
name: "规则",
|
|
include_keywords: ["你好"],
|
|
enabled: true,
|
|
topic: "主题",
|
|
},
|
|
]
|
|
: resource === "creator-leads"
|
|
? [
|
|
{
|
|
id: "lead-a",
|
|
comment: {
|
|
id: "comment-a",
|
|
author_name: "线索用户",
|
|
author_uid: "peer-a",
|
|
content: "线索",
|
|
},
|
|
rule_ids: ["rule-a"],
|
|
},
|
|
]
|
|
: [],
|
|
}),
|
|
),
|
|
});
|
|
renderPage(<CreatorWorkbenchPage />, dataProvider);
|
|
expect(await screen.findByText("评论用户")).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("tab", { name: "线索" }));
|
|
expect(await screen.findByText("线索用户", { exact: false })).toBeTruthy();
|
|
});
|
|
|
|
it("opens share-link monitoring and shows the works table", async () => {
|
|
const dataProvider = provider();
|
|
renderPage(<CreatorCompetitorsPage />, dataProvider);
|
|
expect(await screen.findByRole("button", { name: "添加监听" })).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "添加监听" }));
|
|
expect(await screen.findByLabelText("分享内容或链接", { exact: false })).toBeTruthy();
|
|
expect(await screen.findByText("暂无符合条件的作品。")).toBeTruthy();
|
|
expect(screen.queryByRole("heading", { name: "已监测账号" })).toBeNull();
|
|
expect(dataProvider.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("merges owned and monitored accounts and filters by source", async () => {
|
|
const owned = {
|
|
id: "account-a",
|
|
name: "自有账号",
|
|
platform: "douyin",
|
|
platform_account_key: "owned-a",
|
|
tags: ["主账号"],
|
|
authorization_status: "authorized",
|
|
runtime_status: "active",
|
|
};
|
|
const competitor = {
|
|
id: "competitor-a",
|
|
nickname: "监测作者",
|
|
platform: "douyin",
|
|
platform_account_key: "monitor-a",
|
|
tags: ["重点"],
|
|
sync_status: "succeeded",
|
|
enabled: true,
|
|
};
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "accounts"
|
|
? [owned]
|
|
: resource === "creator-competitors"
|
|
? [competitor]
|
|
: resource === "creator-accounts"
|
|
? [profile]
|
|
: [],
|
|
total:
|
|
resource === "accounts" || resource === "creator-competitors"
|
|
? 1
|
|
: 0,
|
|
}),
|
|
),
|
|
});
|
|
renderPage(<AccountList />, dataProvider);
|
|
expect(await screen.findByRole("heading", { name: "账号管理" })).toBeTruthy();
|
|
expect(await screen.findByText("自有账号")).toBeTruthy();
|
|
expect(screen.getByText("监测作者")).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("combobox", { name: "账号类型" }));
|
|
fireEvent.click(await screen.findByRole("option", { name: "监测" }));
|
|
expect(screen.queryByText("自有账号")).toBeNull();
|
|
expect(screen.getByText("监测作者")).toBeTruthy();
|
|
});
|
|
|
|
it("saves tags for a monitored account", async () => {
|
|
const competitor = {
|
|
id: "competitor-a",
|
|
nickname: "监测作者",
|
|
platform: "douyin",
|
|
platform_account_key: "monitor-a",
|
|
tags: [],
|
|
enabled: true,
|
|
};
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data: resource === "creator-competitors" ? [competitor] : [],
|
|
total: resource === "creator-competitors" ? 1 : 0,
|
|
}),
|
|
),
|
|
creatorUpdate: vi.fn().mockResolvedValue({
|
|
id: "competitor-a",
|
|
tags: ["重点监测"],
|
|
}),
|
|
});
|
|
renderPage(<AccountList />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("button", { name: "编辑标签" }));
|
|
const input = screen.getByRole("textbox", { name: "标签" });
|
|
fireEvent.change(input, { target: { value: "重点监测" } });
|
|
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
|
|
fireEvent.click(screen.getByRole("button", { name: "保存标签" }));
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorUpdate).toHaveBeenCalledWith(
|
|
"/creator/competitors/competitor-a",
|
|
{ tags: ["重点监测"] },
|
|
),
|
|
);
|
|
});
|
|
|
|
it("explains when a monitored account has no owned collection account", async () => {
|
|
const competitor = {
|
|
id: "competitor-a",
|
|
nickname: "监测作者",
|
|
platform: "douyin",
|
|
platform_account_key: "monitor-a",
|
|
enabled: true,
|
|
};
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-competitors"
|
|
? [competitor]
|
|
: resource === "accounts"
|
|
? []
|
|
: resource === "creator-accounts"
|
|
? []
|
|
: [],
|
|
total: resource === "creator-competitors" ? 1 : 0,
|
|
}),
|
|
),
|
|
});
|
|
renderPage(<AccountList />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("button", { name: "同步" }));
|
|
const warning = await screen.findByRole("alert");
|
|
expect(warning.textContent).toContain("没有可用于同步抖音监测账号的已登录自有账号");
|
|
});
|
|
|
|
it("extracts a share URL and requires author confirmation before queuing", async () => {
|
|
const dataProvider = provider({
|
|
create: vi.fn(({ resource, variables }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-competitor-preview"
|
|
? {
|
|
platform: variables.platform,
|
|
platform_account_key: "sec-b",
|
|
nickname: "竞品作者",
|
|
homepage_url: "https://www.douyin.com/user/sec-b",
|
|
}
|
|
: {
|
|
id: "competitor-a",
|
|
account_id: "account-a",
|
|
platform: "douyin",
|
|
platform_account_key: "sec-b",
|
|
homepage_url: "https://www.douyin.com/user/sec-b",
|
|
enabled: true,
|
|
},
|
|
}),
|
|
),
|
|
});
|
|
renderPage(<CreatorCompetitorsPage />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("button", { name: "添加监听" }));
|
|
fireEvent.change(screen.getByLabelText("分享内容或链接", { exact: false }), {
|
|
target: { value: "复制打开抖音 https://v.douyin.com/share-a/。" },
|
|
});
|
|
const tagInput = screen.getByLabelText("TAGS");
|
|
fireEvent.change(tagInput, { target: { value: "重点监测" } });
|
|
fireEvent.keyDown(tagInput, { key: "Enter", code: "Enter" });
|
|
expect(await screen.findByText(/已提取链接:https:\/\/v\.douyin\.com\/share-a/)).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "解析并预览作者" }));
|
|
expect(await screen.findByText("作者预览")).toBeTruthy();
|
|
expect(dataProvider.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
resource: "creator-competitor-preview",
|
|
variables: {
|
|
account_id: "account-a",
|
|
platform: "douyin",
|
|
share_url: "https://v.douyin.com/share-a/",
|
|
},
|
|
}),
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: "确认作者并加入队列" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "确认加入监听" }));
|
|
await waitFor(() => expect(dataProvider.create).toHaveBeenCalledTimes(2));
|
|
expect(dataProvider.create).toHaveBeenLastCalledWith(
|
|
expect.objectContaining({
|
|
resource: "creator-competitors",
|
|
variables: {
|
|
account_id: "account-a",
|
|
platform: "douyin",
|
|
share_url: "https://v.douyin.com/share-a/",
|
|
tags: ["重点监测"],
|
|
},
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("detects Xiaohongshu from a pasted share link", async () => {
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-accounts"
|
|
? [{ ...profile, platform: "xiaohongshu" }]
|
|
: [],
|
|
total: resource === "creator-accounts" ? 1 : 0,
|
|
}),
|
|
),
|
|
create: vi.fn(({ resource, variables }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-competitor-preview"
|
|
? {
|
|
platform: variables.platform,
|
|
platform_account_key: "xhs-b",
|
|
nickname: "小红书作者",
|
|
homepage_url: "https://www.xiaohongshu.com/user/profile/xhs-b",
|
|
}
|
|
: { id: "competitor-xhs", ...variables, enabled: true },
|
|
}),
|
|
),
|
|
});
|
|
renderPage(<CreatorCompetitorsPage />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("button", { name: "添加监听" }));
|
|
fireEvent.change(screen.getByLabelText("分享内容或链接", { exact: false }), {
|
|
target: {
|
|
value: "小红书分享 https://www.xiaohongshu.com/explore/note-a?xsec_token=t",
|
|
},
|
|
});
|
|
expect(
|
|
await screen.findByText(/已提取链接:https:\/\/www\.xiaohongshu\.com\/explore\/note-a/),
|
|
).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "解析并预览作者" }));
|
|
await waitFor(() =>
|
|
expect(dataProvider.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
resource: "creator-competitor-preview",
|
|
variables: expect.objectContaining({
|
|
platform: "xiaohongshu",
|
|
share_url: "https://www.xiaohongshu.com/explore/note-a?xsec_token=t",
|
|
}),
|
|
}),
|
|
),
|
|
);
|
|
});
|
|
|
|
it("keeps account password out of the returned profile and exposes big-account action", async () => {
|
|
const dataProvider = provider();
|
|
renderAccountPage(dataProvider);
|
|
expect(await screen.findByRole("heading", { name: "主账号" })).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("button", { name: "开启大号模式" }));
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorAction).toHaveBeenCalledWith(
|
|
"/creator/accounts/account-a/big-account",
|
|
{ enabled: true },
|
|
),
|
|
);
|
|
expect(screen.getByLabelText("更新密码")).toBeTruthy();
|
|
});
|
|
|
|
it("allows switching private-message accounts across platforms", async () => {
|
|
const xhsProfile = {
|
|
...profile,
|
|
id: "account-xhs",
|
|
name: "小红书账号",
|
|
platform: "xiaohongshu",
|
|
platform_account_key: "xhs-a",
|
|
};
|
|
const conversation = {
|
|
id: "conversation-a",
|
|
account_id: profile.id,
|
|
platform: profile.platform,
|
|
peer_uid: "peer-a",
|
|
peer_name: "客户",
|
|
};
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-accounts"
|
|
? [profile, xhsProfile]
|
|
: resource === "creator-conversations"
|
|
? [conversation]
|
|
: [],
|
|
total: 1,
|
|
}),
|
|
),
|
|
creatorGet: vi.fn(() => Promise.resolve([])),
|
|
});
|
|
renderPage(<CreatorWorkbenchPage />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("tab", { name: "私信" }));
|
|
expect(await screen.findByText("客户")).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("combobox", { name: "发送账号" }));
|
|
expect(screen.getByRole("option", { name: "小红书账号" })).toBeTruthy();
|
|
});
|
|
|
|
it("shows the private-message business update connection state", async () => {
|
|
const creatorSubscribe = vi.fn(() => new Promise(() => {}));
|
|
const conversation = {
|
|
id: "conversation-a",
|
|
account_id: profile.id,
|
|
platform: profile.platform,
|
|
peer_uid: "peer-a",
|
|
peer_name: "客户",
|
|
};
|
|
const dataProvider = provider({
|
|
creatorSubscribe,
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-accounts"
|
|
? [profile]
|
|
: resource === "creator-conversations"
|
|
? [conversation]
|
|
: [],
|
|
total: resource === "creator-conversations" ? 1 : 0,
|
|
}),
|
|
),
|
|
});
|
|
renderPage(<CreatorWorkbenchPage />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("tab", { name: "私信" }));
|
|
expect(await screen.findByText("后台更新连接中")).toBeTruthy();
|
|
expect(creatorSubscribe).toHaveBeenCalledWith(
|
|
"/creator/updates",
|
|
expect.any(Function),
|
|
expect.any(AbortSignal),
|
|
expect.any(Function),
|
|
);
|
|
});
|
|
|
|
it("subscribes to realtime updates on non-DM operational tabs", async () => {
|
|
const creatorSubscribe = vi.fn(() => new Promise(() => {}));
|
|
const dataProvider = provider({ creatorSubscribe });
|
|
renderPage(<CreatorWorkbenchPage />, dataProvider);
|
|
await waitFor(() => expect(creatorSubscribe).toHaveBeenCalledTimes(1));
|
|
fireEvent.click(await screen.findByRole("tab", { name: "事件监听" }));
|
|
await waitFor(() => expect(creatorSubscribe).toHaveBeenCalledTimes(2));
|
|
fireEvent.click(screen.getByRole("tab", { name: "操作记录" }));
|
|
await waitFor(() => expect(creatorSubscribe).toHaveBeenCalledTimes(3));
|
|
});
|
|
|
|
it("isolates private messages by account and confirms one durable send", async () => {
|
|
const conversation = {
|
|
id: "conversation-a",
|
|
account_id: profile.id,
|
|
platform: profile.platform,
|
|
peer_uid: "peer-a",
|
|
peer_name: "客户",
|
|
};
|
|
const dataProvider = provider({
|
|
getList: vi.fn(({ resource }) =>
|
|
Promise.resolve({
|
|
data:
|
|
resource === "creator-accounts"
|
|
? [profile]
|
|
: resource === "creator-conversations"
|
|
? [conversation]
|
|
: resource === "creator-rules"
|
|
? []
|
|
: [],
|
|
total:
|
|
resource === "creator-conversations" ||
|
|
resource === "creator-accounts"
|
|
? 1
|
|
: 0,
|
|
}),
|
|
),
|
|
creatorGet: vi.fn((path) =>
|
|
Promise.resolve(
|
|
path.includes("/messages")
|
|
? []
|
|
: path === "/creator/settings"
|
|
? settings
|
|
: [],
|
|
),
|
|
),
|
|
creatorAction: vi
|
|
.fn()
|
|
.mockResolvedValue({ state: "succeeded", id: "operation-a" }),
|
|
});
|
|
renderPage(<CreatorWorkbenchPage />, dataProvider);
|
|
fireEvent.click(await screen.findByRole("tab", { name: "私信" }));
|
|
expect(await screen.findByText("客户")).toBeTruthy();
|
|
fireEvent.click(screen.getByRole("combobox", { name: "发送账号" }));
|
|
fireEvent.click(await screen.findByRole("option", { name: "主账号" }));
|
|
fireEvent.change(screen.getByRole("textbox", { name: "人工私信文案" }), {
|
|
target: { value: "你好" },
|
|
});
|
|
fireEvent.click(screen.getByRole("button", { name: "确认并发送" }));
|
|
fireEvent.click(
|
|
await screen.findByRole("button", { name: "确认并记录操作" }),
|
|
);
|
|
await waitFor(() =>
|
|
expect(dataProvider.creatorCreate).toHaveBeenCalledWith(
|
|
"/creator/operations",
|
|
expect.objectContaining({
|
|
action: "dm",
|
|
account_id: profile.id,
|
|
target_uid: conversation.peer_uid,
|
|
text: "你好",
|
|
idempotency_key: expect.any(String),
|
|
}),
|
|
),
|
|
);
|
|
});
|
|
});
|