Files
ai-directory-platform/tests/ranking-score.test.ts
T
AI Directory Platform e46b4b52a3 feat: AI Directory Platform 底座升级 + 五套主题站点
- 数据模型: vote/review/submission/claim/use_case/audience/collection/event (+tool 冗余统计列)
- 社区: 投票(限流+原子toggle+API) / 评论(评分+优缺点+Helpful+聚合回写) / 提交(重复检测+审批发布) / 认领(Owner白名单编辑)
- Ranking Engine: trending/popular/most-voted/most-saved/highest-rated/new/editors-choice (纯函数可测)
- 发现体系: Use Case + Audience + Collection 三维发现 / /ai-tools-for / /best SEO 页
- Programmatic SEO: 页面生成器 / sitemap 270条 / AggregateRating/FAQPage/Breadcrumb/ItemList schema / alternatives + vs 对比页
- Analytics: event 事件流 + 外链跳转埋点 + /admin/analytics CTR 与搜索词分析
- Admin: submissions/claims/reviews/discovery/vote-audit/analytics 六个新后台 + 仪表盘扩展
- 主题系统: themes registry + 五套站点 (classic/product/editorial/niche/minimal) 构建期注入
- 认证: GitHub OAuth (env门控) + 登录回跳 redirect
- i18n: 新增 7 个命名空间中英文案 (546 键一致)
- 测试: bun test 32 用例 (ranking/slug/去重/SEO生成器) + Playwright e2e 脚本
- 种子: 14分类/16用例/9受众/40工具/6用户/投票/评论/6合集/7718事件
2026-09-08 17:38:06 +00:00

167 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, test, expect } from "bun:test";
import {
trendingScore,
bayesianRating,
timeDecayFactor,
popularScore,
parseToolSlugRoute,
normalizeDomain,
slugify,
type ToolSignals,
} from "../src/lib/ranking/score";
// ========== Trending 评分 ==========
describe("trendingScore", () => {
const baseSignals: ToolSignals = {
votesInWindow: 10,
favoritesInWindow: 5,
reviewsInWindow: 2,
viewsInWindow: 100,
ageHours: 24,
};
test("互动越多分数越高", () => {
const more: ToolSignals = { ...baseSignals, votesInWindow: 50 };
expect(trendingScore(more)).toBeGreaterThan(trendingScore(baseSignals));
});
test("无互动分数为 0(避免空榜填充)", () => {
const empty: ToolSignals = {
votesInWindow: 0,
favoritesInWindow: 0,
reviewsInWindow: 0,
viewsInWindow: 0,
ageHours: 100,
};
expect(trendingScore(empty)).toBe(0);
});
test("新工具衰减慢:相同互动下,新工具 > 老工具(防止老工具永久霸榜)", () => {
const fresh = trendingScore({ ...baseSignals, ageHours: 24 });
const old = trendingScore({ ...baseSignals, ageHours: 24 * 365 });
expect(fresh).toBeGreaterThan(old);
});
test("评论权重高于投票", () => {
const reviewHeavy = trendingScore({ ...baseSignals, reviewsInWindow: 3, votesInWindow: 0 });
const voteHeavy = trendingScore({ ...baseSignals, reviewsInWindow: 0, votesInWindow: 3 });
expect(reviewHeavy).toBeGreaterThan(voteHeavy);
});
});
// ========== 贝叶斯评分 ==========
describe("bayesianRating", () => {
test("评论极少时被先验拉低", () => {
const oneFiveStar = bayesianRating(5, 1, 4.0);
expect(oneFiveStar).toBeLessThan(5);
});
test("评论很多时接近观测均值", () => {
const manyReviews = bayesianRating(4.8, 200, 4.0);
expect(manyReviews).toBeGreaterThan(4.7);
expect(manyReviews).toBeLessThanOrEqual(4.8);
});
test("样本相同的两工具,均值高者胜", () => {
expect(bayesianRating(4.8, 10, 4.0)).toBeGreaterThan(bayesianRating(4.2, 10, 4.0));
});
test("无评论返回 0", () => {
expect(bayesianRating(5, 0, 4)).toBe(0);
});
});
// ========== 时间衰减 / Popular ==========
describe("timeDecayFactor", () => {
test("未衰减为 1,随时间单调递减", () => {
expect(timeDecayFactor(0)).toBe(1);
expect(timeDecayFactor(100)).toBeLessThan(1);
expect(timeDecayFactor(200)).toBeLessThan(timeDecayFactor(100));
});
test("半衰期处恰好为 0.5", () => {
expect(timeDecayFactor(168, 168)).toBeCloseTo(0.5);
});
});
describe("popularScore", () => {
test("全量互动加权求和", () => {
expect(popularScore(10, 10, 10, 1000)).toBe(10 * 2 + 10 * 1.5 + 10 * 3 + 1000 * 0.1);
});
});
// ========== 复合路由解析(Alternatives / Comparison SEO) ==========
describe("parseToolSlugRoute", () => {
test("普通工具", () => {
expect(parseToolSlugRoute("chatgpt")).toEqual({ kind: "tool", slug: "chatgpt" });
});
test("alternatives 页", () => {
expect(parseToolSlugRoute("chatgpt-alternatives")).toEqual({
kind: "alternatives",
base: "chatgpt",
});
});
test("vs 对比页", () => {
expect(parseToolSlugRoute("cursor-vs-windsurf")).toEqual({
kind: "comparison",
a: "cursor",
b: "windsurf",
});
});
test("名称中带连字符的对比页", () => {
expect(parseToolSlugRoute("github-copilot-vs-tabnine")).toEqual({
kind: "comparison",
a: "github-copilot",
b: "tabnine",
});
});
});
// ========== 域名归一化(重复工具检测) ==========
describe("normalizeDomain", () => {
test("https URL 提取主机", () => {
expect(normalizeDomain("https://chatgpt.com/")).toBe("chatgpt.com");
});
test("去 www", () => {
expect(normalizeDomain("www.anthropic.com")).toBe("anthropic.com");
});
test("带路径与查询参数", () => {
expect(normalizeDomain("https://openai.com/chatgpt?ref=x")).toBe("openai.com");
});
test("子域保留(重复检测按主机比较)", () => {
expect(normalizeDomain("http://chat.openai.com")).toBe("chat.openai.com");
});
test("非法输入返回 null", () => {
expect(normalizeDomain("")).toBeNull();
expect(normalizeDomain("not a url")).toBeNull();
});
});
// ========== slug 生成 ==========
describe("slugify", () => {
test("空格转连字符 + 小写", () => {
expect(slugify("Best AI Writing Tools")).toBe("best-ai-writing-tools");
});
test("去除特殊字符", () => {
expect(slugify("DALL·E 3!")).toBe("dalle-3");
});
test("合并连续连字符", () => {
expect(slugify("a -- b")).toBe("a-b");
});
});