feat: add logto auth and backend metrics integration
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { readAuthConfig } from "../src/shared/auth-config";
|
||||
|
||||
describe("auth-config", () => {
|
||||
test("returns the configured Logto settings", () => {
|
||||
expect(readAuthConfig()).toEqual({
|
||||
apiResource: "https://talent-search.intelligrow.cn",
|
||||
appId: "i4jkllbvih0554r4n0fd3",
|
||||
enableDevAuthPanel: true,
|
||||
logtoEndpoint: "https://login-api.intelligrow.cn",
|
||||
scopes: [
|
||||
"openid",
|
||||
"profile",
|
||||
"offline_access",
|
||||
"talent-search:read"
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
test("rejects empty endpoint values", () => {
|
||||
expect(() =>
|
||||
readAuthConfig({
|
||||
logtoEndpoint: ""
|
||||
})
|
||||
).toThrow(/logtoEndpoint/i);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import {
|
||||
isAuthRequestMessage,
|
||||
isAuthResponseMessage
|
||||
} from "../src/shared/auth-messages";
|
||||
|
||||
describe("auth-messages", () => {
|
||||
test("accepts a get-state request", () => {
|
||||
expect(isAuthRequestMessage({ type: "auth:get-state" })).toBe(true);
|
||||
});
|
||||
|
||||
test("rejects unknown auth requests", () => {
|
||||
expect(isAuthRequestMessage({ type: "auth:wat" })).toBe(false);
|
||||
});
|
||||
|
||||
test("accepts a successful auth response envelope", () => {
|
||||
expect(
|
||||
isAuthResponseMessage({
|
||||
ok: true,
|
||||
type: "auth:state",
|
||||
value: { isAuthenticated: false }
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { DEFAULT_BACKEND_METRICS_BASE_URL } from "../src/shared/backend-metrics-config";
|
||||
import {
|
||||
buildBackendMetricsSearchRequestBody,
|
||||
buildBackendMetricsSearchUrl,
|
||||
createBackendMetricsClient,
|
||||
mapBackendMetricsSearchResponse
|
||||
} from "../src/shared/backend-metrics-client";
|
||||
|
||||
describe("backend-metrics-client", () => {
|
||||
test("exports the default backend metrics base url", () => {
|
||||
expect(DEFAULT_BACKEND_METRICS_BASE_URL).toBe("http://192.168.31.29:8083");
|
||||
});
|
||||
|
||||
test("builds the backend search url", () => {
|
||||
expect(buildBackendMetricsSearchUrl("http://192.168.31.29:8083")).toBe(
|
||||
"http://192.168.31.29:8083/api/v1/history/talents/search"
|
||||
);
|
||||
});
|
||||
|
||||
test("builds a star_id batch request body", () => {
|
||||
expect(
|
||||
buildBackendMetricsSearchRequestBody(["7252982749131178039", "7290491710910496809"])
|
||||
).toEqual({
|
||||
page: 1,
|
||||
size: 20,
|
||||
type: "star_id",
|
||||
values: ["7252982749131178039", "7290491710910496809"]
|
||||
});
|
||||
});
|
||||
|
||||
test("maps backend metrics rows into display-ready values", () => {
|
||||
expect(
|
||||
mapBackendMetricsSearchResponse({
|
||||
data: {
|
||||
data: [
|
||||
{
|
||||
avg_a3_increase_cnt: 78366.22448979592,
|
||||
avg_after_view_search_cnt: 9689.959183673469,
|
||||
avg_after_view_search_rate: 0.0036203703369054683,
|
||||
avg_new_a3_rate: 0.034428135017531614,
|
||||
cp_search: 14.460581961550774,
|
||||
cpa3: 1.788046443373538,
|
||||
star_id: "7252982749131178039"
|
||||
}
|
||||
]
|
||||
},
|
||||
success: true
|
||||
})
|
||||
).toEqual([
|
||||
{
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%",
|
||||
starId: "7252982749131178039"
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
test("posts star ids with bearer auth when searching backend metrics", async () => {
|
||||
const fetchImpl = async (_input: string, init?: RequestInit) => ({
|
||||
json: async () => ({
|
||||
data: {
|
||||
data: []
|
||||
},
|
||||
success: true
|
||||
}),
|
||||
ok: true,
|
||||
status: 200,
|
||||
url: "http://192.168.31.29:8083/api/v1/history/talents/search"
|
||||
});
|
||||
const fetchSpy = vi.fn(fetchImpl);
|
||||
const client = createBackendMetricsClient({
|
||||
fetchImpl: fetchSpy,
|
||||
getAccessToken: async () => "test-token"
|
||||
});
|
||||
|
||||
await client.searchByStarIds(["111", "222"]);
|
||||
|
||||
expect(fetchSpy).toHaveBeenCalledWith(
|
||||
"http://192.168.31.29:8083/api/v1/history/talents/search",
|
||||
expect.objectContaining({
|
||||
body: JSON.stringify({
|
||||
page: 1,
|
||||
size: 20,
|
||||
type: "star_id",
|
||||
values: ["111", "222"]
|
||||
}),
|
||||
headers: {
|
||||
Authorization: "Bearer test-token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: "POST"
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import {
|
||||
isBackendMetricsResponseMessage,
|
||||
isBackendMetricsSearchRequestMessage
|
||||
} from "../src/shared/backend-metrics-messages";
|
||||
|
||||
describe("backend-metrics-messages", () => {
|
||||
test("accepts a backend metrics search request", () => {
|
||||
expect(
|
||||
isBackendMetricsSearchRequestMessage({
|
||||
type: "backend-metrics:search",
|
||||
value: {
|
||||
starIds: ["111", "222"]
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("accepts a successful backend metrics response", () => {
|
||||
expect(
|
||||
isBackendMetricsResponseMessage({
|
||||
ok: true,
|
||||
type: "backend-metrics:result",
|
||||
value: {
|
||||
rows: [
|
||||
{
|
||||
a3IncreaseCount: "10.00",
|
||||
afterViewSearchCount: "20.00",
|
||||
afterViewSearchRate: "0.20%",
|
||||
cpSearch: "1.10",
|
||||
cpa3: "2.20",
|
||||
newA3Rate: "1.50%",
|
||||
starId: "111"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createLogtoAuthClient } from "../src/background/auth/client";
|
||||
|
||||
vi.mock("@logto/chrome-extension", () => {
|
||||
const signIn = vi.fn(async () => undefined);
|
||||
const signOut = vi.fn(async () => undefined);
|
||||
const MockLogtoClient = vi.fn(function MockLogtoClient() {
|
||||
return {
|
||||
getAccessToken: vi.fn(async () => "token"),
|
||||
getIdTokenClaims: vi.fn(async () => null),
|
||||
isAuthenticated: vi.fn(async () => false),
|
||||
signIn,
|
||||
signOut
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
default: MockLogtoClient
|
||||
};
|
||||
});
|
||||
|
||||
describe("background-auth-client", () => {
|
||||
test("uses chrome identity redirect URLs for sign in and sign out", async () => {
|
||||
const getRedirectURL = vi.fn((path?: string) =>
|
||||
path ? `https://extension.chromiumapp.org${path}` : "https://extension.chromiumapp.org/"
|
||||
);
|
||||
|
||||
(
|
||||
globalThis as typeof globalThis & {
|
||||
chrome?: {
|
||||
identity?: {
|
||||
getRedirectURL?: (path?: string) => string;
|
||||
};
|
||||
};
|
||||
}
|
||||
).chrome = {
|
||||
identity: {
|
||||
getRedirectURL
|
||||
}
|
||||
};
|
||||
|
||||
const authClient = createLogtoAuthClient();
|
||||
await authClient.signIn();
|
||||
await authClient.signOut();
|
||||
|
||||
expect(getRedirectURL).toHaveBeenNthCalledWith(1, "/callback");
|
||||
expect(getRedirectURL).toHaveBeenNthCalledWith(2);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { createAuthController } from "../src/background/auth/controller";
|
||||
|
||||
describe("background-auth-controller", () => {
|
||||
test("returns unauthenticated state when the client is logged out", async () => {
|
||||
const controller = createAuthController({
|
||||
authClient: {
|
||||
getAccessToken: vi.fn(),
|
||||
getIdTokenClaims: vi.fn(),
|
||||
isAuthenticated: vi.fn(async () => false),
|
||||
signIn: vi.fn(),
|
||||
signOut: vi.fn()
|
||||
}
|
||||
});
|
||||
|
||||
await expect(controller.getAuthState()).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
isAuthenticated: false
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("delegates sign in to the auth client", async () => {
|
||||
const signIn = vi.fn(async () => undefined);
|
||||
const controller = createAuthController({
|
||||
authClient: {
|
||||
getAccessToken: vi.fn(),
|
||||
getIdTokenClaims: vi.fn(),
|
||||
isAuthenticated: vi.fn(async () => false),
|
||||
signIn,
|
||||
signOut: vi.fn()
|
||||
}
|
||||
});
|
||||
|
||||
await controller.signIn();
|
||||
|
||||
expect(signIn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -127,4 +127,144 @@ describe("background-index", () => {
|
||||
value: { accessToken: "test-access-token" }
|
||||
});
|
||||
});
|
||||
|
||||
test("submits batches through the background message handler", async () => {
|
||||
const listeners: Array<
|
||||
(message: unknown, sender: unknown, sendResponse: (response: unknown) => void) => boolean | void
|
||||
> = [];
|
||||
const sendResponse = vi.fn();
|
||||
const submitBatch = vi.fn(async () => ({
|
||||
acceptedCount: 1,
|
||||
batchId: "批次A-2026-04-22T12:30:00.000Z",
|
||||
ok: true
|
||||
}));
|
||||
|
||||
registerBackgroundMessageHandler(
|
||||
{
|
||||
runtime: {
|
||||
onMessage: {
|
||||
addListener(listener) {
|
||||
listeners.push(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
authController: {
|
||||
getAccessToken: vi.fn(async () => "test-access-token"),
|
||||
getAuthState: vi.fn(),
|
||||
signIn: vi.fn(),
|
||||
signOut: vi.fn()
|
||||
},
|
||||
submitBatch
|
||||
}
|
||||
);
|
||||
|
||||
const result = listeners[0](
|
||||
{
|
||||
payload: {
|
||||
authors: [{ authorId: "111", authorName: "达人A" }],
|
||||
batchId: "批次A-2026-04-22T12:30:00.000Z",
|
||||
batchName: "批次A",
|
||||
createdAt: "2026-04-22T12:30:00.000Z",
|
||||
creatorName: "王少卿",
|
||||
logtoUserId: "p7pdhhtde8kj",
|
||||
resource: "https://talent-search.intelligrow.cn"
|
||||
},
|
||||
type: "batch:submit"
|
||||
},
|
||||
{},
|
||||
sendResponse
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(submitBatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
batchId: "批次A-2026-04-22T12:30:00.000Z"
|
||||
})
|
||||
);
|
||||
expect(sendResponse).toHaveBeenCalledWith({
|
||||
ok: true,
|
||||
type: "batch:ack",
|
||||
value: {
|
||||
acceptedCount: 1,
|
||||
batchId: "批次A-2026-04-22T12:30:00.000Z",
|
||||
ok: true
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("searches backend metrics through the background message handler", async () => {
|
||||
const listeners: Array<
|
||||
(message: unknown, sender: unknown, sendResponse: (response: unknown) => void) => boolean | void
|
||||
> = [];
|
||||
const sendResponse = vi.fn();
|
||||
const searchBackendMetrics = vi.fn(async () => [
|
||||
{
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%",
|
||||
starId: "111"
|
||||
}
|
||||
]);
|
||||
|
||||
registerBackgroundMessageHandler(
|
||||
{
|
||||
runtime: {
|
||||
onMessage: {
|
||||
addListener(listener) {
|
||||
listeners.push(listener);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
authController: {
|
||||
getAccessToken: vi.fn(async () => "test-access-token"),
|
||||
getAuthState: vi.fn(),
|
||||
signIn: vi.fn(),
|
||||
signOut: vi.fn()
|
||||
},
|
||||
searchBackendMetrics
|
||||
}
|
||||
);
|
||||
|
||||
const result = listeners[0](
|
||||
{
|
||||
type: "backend-metrics:search",
|
||||
value: {
|
||||
starIds: ["111", "222"]
|
||||
}
|
||||
},
|
||||
{},
|
||||
sendResponse
|
||||
);
|
||||
|
||||
expect(result).toBe(true);
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(searchBackendMetrics).toHaveBeenCalledWith(["111", "222"]);
|
||||
expect(sendResponse).toHaveBeenCalledWith({
|
||||
ok: true,
|
||||
type: "backend-metrics:result",
|
||||
value: {
|
||||
rows: [
|
||||
{
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%",
|
||||
starId: "111"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+11
-2
@@ -11,10 +11,19 @@ describe("manifest", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("declares the downloads permission and background worker for csv export", () => {
|
||||
test("declares the downloads and auth permissions plus background worker", () => {
|
||||
expect(manifest.permissions).toEqual(
|
||||
expect.arrayContaining(["downloads"])
|
||||
expect.arrayContaining(["downloads", "identity", "storage"])
|
||||
);
|
||||
expect(manifest.host_permissions).toEqual(
|
||||
expect.arrayContaining([
|
||||
"http://*/*",
|
||||
"https://login-api.intelligrow.cn/*",
|
||||
"http://127.0.0.1:4319/*",
|
||||
"https://*/*"
|
||||
])
|
||||
);
|
||||
expect(manifest.background?.service_worker).toBe("background/index.js");
|
||||
expect(manifest.action?.default_popup).toBe("popup/index.html");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// @vitest-environment jsdom
|
||||
// @vitest-environment-options {"url":"https://xingtu.cn/ad/creator/market"}
|
||||
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
|
||||
import { bootContentScript } from "../src/content/index";
|
||||
|
||||
describe("market-auth-gating", () => {
|
||||
test("shows a login gate instead of booting the market controller when unauthenticated", async () => {
|
||||
document.body.innerHTML = "<div></div>";
|
||||
|
||||
const createMarketController = vi.fn();
|
||||
await bootContentScript({
|
||||
createMarketController,
|
||||
document,
|
||||
sendAuthMessage: vi.fn(async () => ({
|
||||
ok: true,
|
||||
type: "auth:state",
|
||||
value: { isAuthenticated: false }
|
||||
})),
|
||||
window
|
||||
});
|
||||
|
||||
expect(createMarketController).not.toHaveBeenCalled();
|
||||
expect(document.body.textContent).toContain("请先登录插件");
|
||||
});
|
||||
});
|
||||
@@ -241,6 +241,48 @@ describe("market-content-entry", () => {
|
||||
).toBe("0.03% - 0.2%");
|
||||
});
|
||||
|
||||
test("batch loads backend metrics for the visible page and renders the metrics panel", async () => {
|
||||
document.body.innerHTML = buildMarketFixture();
|
||||
const searchBackendMetrics = vi.fn(async (starIds: string[]) =>
|
||||
starIds
|
||||
.filter((starId) => starId === "a")
|
||||
.map((starId) => ({
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%",
|
||||
starId
|
||||
}))
|
||||
);
|
||||
|
||||
const { createMarketController } = await import("../src/content/market/index");
|
||||
const controller = trackController(createMarketController({
|
||||
document,
|
||||
loadAuthorMetrics: async () => ({
|
||||
success: false,
|
||||
reason: "request-failed"
|
||||
}),
|
||||
searchBackendMetrics,
|
||||
window
|
||||
}));
|
||||
|
||||
await controller.ready;
|
||||
|
||||
expect(searchBackendMetrics).toHaveBeenCalledTimes(1);
|
||||
expect(searchBackendMetrics).toHaveBeenCalledWith(["a", "b"]);
|
||||
expect(
|
||||
document.querySelector('[data-market-row-cell="backendMetrics"]')?.textContent
|
||||
).toContain("看后搜率");
|
||||
expect(
|
||||
document.querySelector('[data-market-row-cell="backendMetrics"]')?.textContent
|
||||
).toContain("0.36%");
|
||||
expect(
|
||||
document.querySelectorAll('[data-market-row-cell="backendMetrics"]')[1]?.textContent
|
||||
).toBe("暂无数据");
|
||||
});
|
||||
|
||||
test("boots the controller only after auth succeeds", async () => {
|
||||
const createMarketController = vi.fn(() => ({
|
||||
ready: Promise.resolve()
|
||||
@@ -302,12 +344,14 @@ describe("market-content-entry", () => {
|
||||
"¥450,000",
|
||||
"0.02% - 0.1%",
|
||||
"0.03% - 0.2%",
|
||||
"",
|
||||
"下单"
|
||||
]);
|
||||
expect(readDivRightRowTexts(1)).toEqual([
|
||||
"¥20,000",
|
||||
"0.5% - 1%",
|
||||
"0.01% - 0.1%",
|
||||
"",
|
||||
"下单"
|
||||
]);
|
||||
});
|
||||
@@ -368,12 +412,14 @@ describe("market-content-entry", () => {
|
||||
"¥450,000",
|
||||
"0.02%",
|
||||
"0.03% - 0.2%",
|
||||
"",
|
||||
"下单"
|
||||
]);
|
||||
expect(readDivRightRowTexts(1)).toEqual([
|
||||
"¥20,000",
|
||||
"0.5% - 1%",
|
||||
"0.01% - 0.1%",
|
||||
"",
|
||||
"下单"
|
||||
]);
|
||||
});
|
||||
@@ -429,12 +475,14 @@ describe("market-content-entry", () => {
|
||||
"¥450,000",
|
||||
"0.02%",
|
||||
"0.03% - 0.2%",
|
||||
"",
|
||||
"下单"
|
||||
]);
|
||||
expect(readDivRightRowTexts(1)).toEqual([
|
||||
"¥20,000",
|
||||
"0.5% - 1%",
|
||||
"0.01% - 0.1%",
|
||||
"",
|
||||
"下单"
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -47,10 +47,13 @@ describe("market-dom-sync", () => {
|
||||
'[data-market-header-cell="personalVideoAfterSearchRate"]'
|
||||
)
|
||||
).not.toBeNull();
|
||||
expect(document.querySelectorAll("[data-market-row-cell]").length).toBe(4);
|
||||
expect(
|
||||
document.querySelector('[data-market-header-cell="backendMetrics"]')
|
||||
).not.toBeNull();
|
||||
expect(document.querySelectorAll("[data-market-row-cell]").length).toBe(6);
|
||||
});
|
||||
|
||||
test("renders loading, success, and failed states", () => {
|
||||
test("renders loading, success, missing, and failed states", () => {
|
||||
const table = syncMarketTable(document);
|
||||
if (!table) {
|
||||
throw new Error("Expected market table");
|
||||
@@ -67,6 +70,15 @@ describe("market-dom-sync", () => {
|
||||
renderMarketRowState(betaRow, {
|
||||
authorId: "b",
|
||||
authorName: "Beta",
|
||||
backendMetrics: {
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%"
|
||||
},
|
||||
backendMetricsStatus: "success",
|
||||
status: "success",
|
||||
rates: {
|
||||
singleVideoAfterSearchRate: "0.5%-1%",
|
||||
@@ -75,16 +87,34 @@ describe("market-dom-sync", () => {
|
||||
});
|
||||
|
||||
expect(alphaRow.singleCell.textContent).toBe("加载中...");
|
||||
expect(alphaRow.backendMetricsCell.textContent).toBe("加载中...");
|
||||
expect(betaRow.singleCell.textContent).toBe("0.5% - 1%");
|
||||
expect(betaRow.personalCell.textContent).toBe("0.02% - 0.1%");
|
||||
expect(betaRow.backendMetricsCell.textContent).toContain("看后搜率");
|
||||
expect(betaRow.backendMetricsCell.textContent).toContain("0.36%");
|
||||
expect(betaRow.backendMetricsCell.textContent).toContain("CPA3");
|
||||
|
||||
renderMarketRowState(betaRow, {
|
||||
authorId: "b",
|
||||
authorName: "Beta",
|
||||
backendMetricsStatus: "missing",
|
||||
status: "success",
|
||||
rates: {
|
||||
singleVideoAfterSearchRate: "0.5%-1%",
|
||||
personalVideoAfterSearchRate: "0.02 - 0.1%"
|
||||
}
|
||||
});
|
||||
expect(betaRow.backendMetricsCell.textContent).toBe("暂无数据");
|
||||
|
||||
renderMarketRowState(betaRow, {
|
||||
authorId: "b",
|
||||
authorName: "Beta",
|
||||
backendMetricsStatus: "failed",
|
||||
status: "failed"
|
||||
});
|
||||
expect(betaRow.singleCell.textContent).toBe("加载失败");
|
||||
expect(betaRow.personalCell.textContent).toBe("加载失败");
|
||||
expect(betaRow.backendMetricsCell.textContent).toBe("加载失败");
|
||||
});
|
||||
|
||||
test("hides rows outside the visible author ids", () => {
|
||||
@@ -126,13 +156,37 @@ describe("market-dom-sync", () => {
|
||||
"21-60s报价",
|
||||
"单视频看后搜率",
|
||||
"个人视频看后搜率",
|
||||
"秒探指标",
|
||||
"操作"
|
||||
]);
|
||||
expect(
|
||||
Number.parseFloat(
|
||||
(
|
||||
document.querySelector('[data-testid="right-header"]') as HTMLElement
|
||||
).style.width
|
||||
)
|
||||
).toBeGreaterThan(350);
|
||||
expect(
|
||||
Number.parseFloat(
|
||||
(
|
||||
document.querySelector('[data-testid="right-section"]') as HTMLElement
|
||||
).style.width
|
||||
)
|
||||
).toBeGreaterThan(350);
|
||||
expect(table.rows.map((row) => row.authorId)).toEqual(["111", "222"]);
|
||||
|
||||
renderMarketRowState(table.rows[0], {
|
||||
authorId: "111",
|
||||
authorName: "达人 A",
|
||||
backendMetrics: {
|
||||
a3IncreaseCount: "78,366.22",
|
||||
afterViewSearchCount: "9,689.96",
|
||||
afterViewSearchRate: "0.36%",
|
||||
cpSearch: "14.46",
|
||||
cpa3: "1.79",
|
||||
newA3Rate: "3.44%"
|
||||
},
|
||||
backendMetricsStatus: "success",
|
||||
status: "success",
|
||||
rates: {
|
||||
singleVideoAfterSearchRate: "0.5%-1%",
|
||||
@@ -144,6 +198,7 @@ describe("market-dom-sync", () => {
|
||||
"¥450,000",
|
||||
"0.5% - 1%",
|
||||
"0.02% - 0.1%",
|
||||
"看后搜率0.36%看后搜数9,689.96新增A3数78,366.22新增A3率3.44%CPA31.79cp_search14.46",
|
||||
"下单"
|
||||
]);
|
||||
|
||||
@@ -156,7 +211,7 @@ describe("market-dom-sync", () => {
|
||||
applyRowOrder(table, ["222", "111"]);
|
||||
|
||||
expect(readAuthorNames()).toEqual(["达人 B", "达人 A"]);
|
||||
expect(readRightRowTexts(0)).toEqual(["¥20,000", "", "", "下单"]);
|
||||
expect(readRightRowTexts(0)).toEqual(["¥20,000", "", "", "", "下单"]);
|
||||
expect(table.rows[0].exportFields).toMatchObject({
|
||||
"21-60s报价": "¥450,000",
|
||||
"代表视频": "代表视频A",
|
||||
|
||||
Reference in New Issue
Block a user