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"); }); });