wxs 6cc703ada2 feat: monorepo 重构 + 新增 5 个平台适配器
项目从单体结构重构为 pnpm monorepo (shared/backend/frontend),
新增 YouTube、Instagram、Twitter/X、哔哩哔哩、微博 5 个平台适配器,
包含完整的单元测试和 E2E 测试覆盖。

- 完成 T-031~T-044: 5 个适配器实现、注册、配置和测试
- 重构前后端分离: Hono 后端 + Next.js 前端
- 151 个单元测试 + 21 个 Mock E2E + 25 个真实 E2E
- 适配器基于真实 TikHub API 响应结构实现

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:43:25 +08:00

135 lines
4.7 KiB
TypeScript

import { test, expect, type Page } from "@playwright/test";
import { mockContentList, mockDetailItem } from "./fixtures";
async function mockTrendingAPI(page: Page, items = mockContentList) {
await page.route(/\/api\/tikhub\//, async (route) => {
const url = route.request().url();
if (url.includes("/detail")) {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: mockDetailItem }),
});
return;
}
const match = url.match(/\/api\/tikhub\/(\w+)/);
const platform = match?.[1];
const filtered = items.filter((i) => i.platform === platform);
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ data: filtered }),
});
});
}
test.describe("首页浏览流程", () => {
test("首页加载并显示内容卡片", async ({ page }) => {
await mockTrendingAPI(page);
await page.goto("/");
await expect(page.getByTestId("content-grid")).toBeVisible();
const cards = page.getByTestId("content-card");
await expect(cards).toHaveCount(9);
});
test("平台切换", async ({ page }) => {
await mockTrendingAPI(page);
await page.goto("/");
await expect(page.getByTestId("content-grid")).toBeVisible();
// Switch to douyin — 2 items (dy-001, dy-004)
await page.getByTestId("platform-tab-douyin").click();
await expect(page.getByTestId("content-card")).toHaveCount(2);
// Switch to tiktok — 1 item (tt-002)
await page.getByTestId("platform-tab-tiktok").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch to xiaohongshu — 1 item (xhs-003)
await page.getByTestId("platform-tab-xiaohongshu").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch to youtube — 1 item (yt-005)
await page.getByTestId("platform-tab-youtube").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch to instagram — 1 item (ig-006)
await page.getByTestId("platform-tab-instagram").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch to twitter — 1 item (tw-007)
await page.getByTestId("platform-tab-twitter").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch to bilibili — 1 item (bl-008)
await page.getByTestId("platform-tab-bilibili").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch to weibo — 1 item (wb-009)
await page.getByTestId("platform-tab-weibo").click();
await expect(page.getByTestId("content-card")).toHaveCount(1);
// Switch back to all — 9 items
await page.getByTestId("platform-tab-all").click();
await expect(page.getByTestId("content-card")).toHaveCount(9);
});
test("排序功能", async ({ page }) => {
await mockTrendingAPI(page);
await page.goto("/");
await expect(page.getByTestId("content-grid")).toBeVisible();
// Default: play_count desc — YouTube (10M) first
await expect(page.getByTestId("content-card").first()).toContainText(
"YouTube Top Music"
);
// Switch sort field to like_count
await page.getByTestId("sort-select").selectOption("like_count");
// like_count desc: yt-005(500K) first
await expect(page.getByTestId("content-card").first()).toContainText(
"YouTube Top Music"
);
// Toggle to ascending
await page.getByTestId("sort-order").click();
// like_count asc: dy-004(35K) first
await expect(page.getByTestId("content-card").first()).toContainText(
"生活小妙招"
);
});
test("卡片导航到详情页", async ({ page }) => {
await mockTrendingAPI(page);
await page.goto("/");
await expect(page.getByTestId("content-grid")).toBeVisible();
// Default sort: play_count desc → first card is YouTube (yt-005, 10M)
await page.getByTestId("content-card").first().click();
await expect(page).toHaveURL(/\/detail\/youtube\/yt-005/);
});
test("空状态", async ({ page }) => {
await mockTrendingAPI(page, []);
await page.goto("/");
await expect(page.getByTestId("empty-state")).toBeVisible();
await expect(page.getByText("暂无内容")).toBeVisible();
});
test("错误状态和重试", async ({ page }) => {
await page.route(/\/api\/tikhub\//, async (route) => {
await route.fulfill({
status: 500,
contentType: "application/json",
body: JSON.stringify({ error: "服务器错误" }),
});
});
await page.goto("/");
await expect(page.getByText("加载失败")).toBeVisible({ timeout: 30_000 });
await expect(page.getByTestId("error-retry")).toBeVisible();
});
});