158 lines
4.8 KiB
TypeScript
158 lines
4.8 KiB
TypeScript
/**
|
||
* 行动建议系统统一测试
|
||
* 验证 ActionSuggestion 和 NextAction 使用统一的 ActionItem 类型
|
||
*/
|
||
|
||
import { describe, it, expect } from "vitest";
|
||
import { generateActionSuggestions } from "@/lib/dashboard-health";
|
||
import { generateNextActions } from "@/lib/next-action";
|
||
import type { ActionItem, ActionPriority } from "@/types/suggestion";
|
||
import type { ActionSuggestion } from "@/types/dashboard-health";
|
||
import type { NextAction } from "@/types/next-action";
|
||
|
||
describe("ActionItem 统一类型", () => {
|
||
it("ActionItem 类型应包含所有必要字段", async () => {
|
||
const suggestionModule = await import("@/types/suggestion");
|
||
// 验证类型可以被导入(编译时检查)
|
||
expect(suggestionModule).toBeDefined();
|
||
});
|
||
|
||
it("ActionPriority 应为 'primary' | 'secondary' | 'optional'", async () => {
|
||
const suggestionModule = await import("@/types/suggestion");
|
||
// ActionPriority 是类型,运行时不可直接检查,但我们可以验证模块导出
|
||
expect(suggestionModule).toBeDefined();
|
||
});
|
||
});
|
||
|
||
describe("ActionSuggestion 兼容 ActionItem", () => {
|
||
it("generateActionSuggestions 返回的对象应兼容 ActionItem 结构", () => {
|
||
const stats = {
|
||
platformScores: [
|
||
{
|
||
platform: "kimi",
|
||
score: 30,
|
||
competitor_score: 60,
|
||
competitor_name: "竞品A",
|
||
},
|
||
],
|
||
overallScore: 30,
|
||
hasQueries: true,
|
||
};
|
||
|
||
const suggestions = generateActionSuggestions(stats);
|
||
|
||
expect(suggestions.length).toBeGreaterThan(0);
|
||
|
||
for (const suggestion of suggestions) {
|
||
// ActionItem 必需字段
|
||
expect(suggestion).toHaveProperty("id");
|
||
expect(suggestion).toHaveProperty("title");
|
||
expect(suggestion).toHaveProperty("description");
|
||
expect(suggestion).toHaveProperty("icon");
|
||
expect(suggestion).toHaveProperty("href");
|
||
|
||
// type 字段应兼容 ActionPriority
|
||
const validPriorities: ActionPriority[] = [
|
||
"primary",
|
||
"secondary",
|
||
"optional",
|
||
];
|
||
expect(validPriorities).toContain(suggestion.type);
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("NextAction 兼容 ActionItem", () => {
|
||
it("generateNextActions 返回的对象应兼容 ActionItem 结构", () => {
|
||
const context = {
|
||
hasData: true,
|
||
hasBrands: true,
|
||
brandCount: 1,
|
||
overallScore: 50,
|
||
scoreChange: -5,
|
||
competitorCount: 2,
|
||
hasQueryHistory: true,
|
||
currentPage: "dashboard" as const,
|
||
};
|
||
|
||
const actions = generateNextActions(context);
|
||
|
||
expect(actions.length).toBeGreaterThan(0);
|
||
|
||
for (const action of actions) {
|
||
// ActionItem 必需字段
|
||
expect(action).toHaveProperty("id");
|
||
expect(action).toHaveProperty("title");
|
||
expect(action).toHaveProperty("description");
|
||
expect(action).toHaveProperty("icon");
|
||
|
||
// NextAction 应有 href 字段(映射自 actionUrl)
|
||
expect(action).toHaveProperty("href");
|
||
|
||
// priority 字段应兼容 ActionPriority
|
||
const validPriorities: ActionPriority[] = [
|
||
"primary",
|
||
"secondary",
|
||
"optional",
|
||
];
|
||
expect(validPriorities).toContain(action.priority);
|
||
}
|
||
});
|
||
|
||
it("NextAction 的 href 应与 actionUrl 一致", () => {
|
||
const context = {
|
||
hasData: false,
|
||
hasBrands: false,
|
||
brandCount: 0,
|
||
overallScore: 0,
|
||
scoreChange: 0,
|
||
competitorCount: 0,
|
||
hasQueryHistory: false,
|
||
currentPage: "dashboard" as const,
|
||
};
|
||
|
||
const actions = generateNextActions(context);
|
||
|
||
for (const action of actions) {
|
||
// href 应该存在且非空
|
||
expect(action.href).toBeTruthy();
|
||
expect(typeof action.href).toBe("string");
|
||
}
|
||
});
|
||
});
|
||
|
||
describe("两个系统生成结果的一致性", () => {
|
||
it("ActionSuggestion 和 NextAction 都能生成有效的行动建议", () => {
|
||
// dashboard-health 的 generateActionSuggestions
|
||
const dashboardSuggestions = generateActionSuggestions({
|
||
platformScores: [
|
||
{ platform: "kimi", score: 30, competitor_score: 60 },
|
||
],
|
||
overallScore: 30,
|
||
hasQueries: true,
|
||
});
|
||
|
||
// next-action 的 generateNextActions
|
||
const nextActions = generateNextActions({
|
||
hasData: true,
|
||
hasBrands: true,
|
||
brandCount: 1,
|
||
overallScore: 30,
|
||
scoreChange: -5,
|
||
competitorCount: 1,
|
||
hasQueryHistory: true,
|
||
currentPage: "dashboard",
|
||
});
|
||
|
||
// 两个系统都应该能生成建议
|
||
expect(dashboardSuggestions.length).toBeGreaterThan(0);
|
||
expect(nextActions.length).toBeGreaterThan(0);
|
||
|
||
// 所有建议都应有唯一 id
|
||
const dashboardIds = dashboardSuggestions.map((s) => s.id);
|
||
const nextActionIds = nextActions.map((a) => a.id);
|
||
expect(new Set(dashboardIds).size).toBe(dashboardIds.length);
|
||
expect(new Set(nextActionIds).size).toBe(nextActionIds.length);
|
||
});
|
||
});
|