diff --git a/ts/web/nextjs/polaris/public/icons/ali-cloud.svg b/ts/web/nextjs/polaris/public/icons/ali-cloud.svg
new file mode 100644
index 0000000..63020cd
--- /dev/null
+++ b/ts/web/nextjs/polaris/public/icons/ali-cloud.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ts/web/nextjs/polaris/src/app/categories/[categoryId]/page.test.tsx b/ts/web/nextjs/polaris/src/app/categories/[categoryId]/page.test.tsx
index c9c435c..f87a74f 100644
--- a/ts/web/nextjs/polaris/src/app/categories/[categoryId]/page.test.tsx
+++ b/ts/web/nextjs/polaris/src/app/categories/[categoryId]/page.test.tsx
@@ -6,7 +6,7 @@ import { getLinksByCategory } from "@/services/links"
import CategoryPage, { dynamicParams, generateStaticParams } from "./page"
describe("CategoryPage", () => {
- it("为全部 7 个分类生成静态参数并关闭运行时动态参数", () => {
+ it("为全部 8 个分类生成静态参数并关闭运行时动态参数", () => {
expect(generateStaticParams()).toEqual([
{ categoryId: "ai" },
{ categoryId: "dev" },
@@ -14,11 +14,35 @@ describe("CategoryPage", () => {
{ categoryId: "learning" },
{ categoryId: "efficiency" },
{ categoryId: "media" },
+ { categoryId: "game-resources" },
{ categoryId: "life" },
])
expect(dynamicParams).toBe(false)
})
+ it("归类边界:游戏资料页收录宝可梦维基,娱乐媒体页不再包含", async () => {
+ const gamePage = await CategoryPage({
+ params: Promise.resolve({ categoryId: "game-resources" }),
+ })
+ render(gamePage)
+
+ expect(
+ screen.getByRole("heading", { level: 1, name: "游戏资料" }),
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole("link", { name: /神奇宝贝百科/ }),
+ ).toBeInTheDocument()
+ expect(
+ screen.getByRole("link", { name: /星露谷物语中文维基/ }),
+ ).toBeInTheDocument()
+ expect(
+ screen.queryByRole("link", { name: /萌娘百科/ }),
+ ).not.toBeInTheDocument()
+ expect(getLinksByCategory("media").map(({ id }) => id)).not.toContain(
+ "wiki-52poke",
+ )
+ })
+
it("通过 service 组合开发工具分类页", async () => {
const links = getLinksByCategory("dev")
const page = await CategoryPage({
diff --git a/ts/web/nextjs/polaris/src/data/categories.json b/ts/web/nextjs/polaris/src/data/categories.json
index 317d95e..dad0b6f 100644
--- a/ts/web/nextjs/polaris/src/data/categories.json
+++ b/ts/web/nextjs/polaris/src/data/categories.json
@@ -41,6 +41,13 @@
"icon": "circle-play",
"sort": 60
},
+ {
+ "id": "game-resources",
+ "title": "游戏资料",
+ "description": "游戏攻略维基、资料速查与实用规划工具。",
+ "icon": "book-open",
+ "sort": 65
+ },
{
"id": "life",
"title": "生活服务",
diff --git a/ts/web/nextjs/polaris/src/data/data.test.ts b/ts/web/nextjs/polaris/src/data/data.test.ts
index 7550243..e80cd6a 100644
--- a/ts/web/nextjs/polaris/src/data/data.test.ts
+++ b/ts/web/nextjs/polaris/src/data/data.test.ts
@@ -9,9 +9,12 @@ const hasUniqueValues = (values: string[]) =>
new Set(values).size === values.length
describe("Polaris 静态数据", () => {
- it("包含 7 个唯一且结构完整的分类", () => {
- expect(categories).toHaveLength(7)
+ it("包含 8 个唯一且结构完整的分类", () => {
+ expect(categories).toHaveLength(8)
expect(hasUniqueValues(categories.map(({ id }) => id))).toBe(true)
+ expect(hasUniqueValues(categories.map(({ sort }) => String(sort)))).toBe(
+ true,
+ )
for (const category of categories) {
expect(category).toEqual({
@@ -27,9 +30,9 @@ describe("Polaris 静态数据", () => {
}
})
- it("包含 45 条合法链接且分类引用均存在", () => {
+ it("包含 51 条合法链接且分类引用均存在", () => {
const categoryIds = new Set(categories.map(({ id }) => id))
- expect(links).toHaveLength(45)
+ expect(links).toHaveLength(51)
expect(hasUniqueValues(links.map(({ id }) => id))).toBe(true)
for (const link of links) {
@@ -115,4 +118,134 @@ describe("Polaris 静态数据", () => {
}
})
})
+
+ describe("归类边界回归", () => {
+ const linkIdsByCategory = (categoryId: string) =>
+ links
+ .filter((link) => link.categoryId === categoryId)
+ .map(({ id }) => id)
+ .sort()
+
+ it("游戏维基与攻略工具归入 game-resources", () => {
+ expect(linkIdsByCategory("game-resources")).toEqual(
+ expect.arrayContaining([
+ "wiki-52poke",
+ "wiki-blhx",
+ "wiki-fgo",
+ "wiki-stardew",
+ "wiki-terraria",
+ ]),
+ )
+ })
+
+ it("娱乐媒体保留影音社区与 ACG 百科,不混入游戏资料", () => {
+ const mediaIds = linkIdsByCategory("media")
+
+ expect(mediaIds).toEqual(
+ expect.arrayContaining([
+ "youtube",
+ "bilibili",
+ "spotify",
+ "nexusmods",
+ "pokemon-showdown",
+ "expreview",
+ "chairo",
+ "wiki-moegirl",
+ ]),
+ )
+ for (const gameId of [
+ "wiki-52poke",
+ "wiki-blhx",
+ "wiki-fgo",
+ "wiki-stardew",
+ "wiki-terraria",
+ ]) {
+ expect(mediaIds).not.toContain(gameId)
+ }
+ })
+
+ it("Nexus Mods 与 Pokemon Showdown 等实测工具仍留在娱乐媒体", () => {
+ const mediaIds = linkIdsByCategory("media")
+
+ expect(mediaIds).toContain("nexusmods")
+ expect(mediaIds).toContain("pokemon-showdown")
+ expect(linkIdsByCategory("game-resources")).not.toContain("nexusmods")
+ expect(linkIdsByCategory("game-resources")).not.toContain(
+ "pokemon-showdown",
+ )
+ })
+ })
+
+ describe("新增九条链接回归", () => {
+ const expectedNewLinks = [
+ {
+ id: "kzo",
+ url: "https://kzo.moe",
+ categoryId: "media",
+ },
+ {
+ id: "manhuagui",
+ url: "https://www.manhuagui.com",
+ categoryId: "media",
+ },
+ {
+ id: "linovelib",
+ url: "https://www.linovelib.com",
+ categoryId: "media",
+ },
+ {
+ id: "steam",
+ url: "https://store.steampowered.com",
+ categoryId: "media",
+ },
+ {
+ id: "tieba",
+ url: "https://tieba.baidu.com",
+ categoryId: "media",
+ },
+ {
+ id: "manga-bilibili",
+ url: "https://manga.bilibili.com",
+ categoryId: "media",
+ },
+ {
+ id: "zhihu",
+ url: "https://www.zhihu.com",
+ categoryId: "media",
+ },
+ {
+ id: "pixiv",
+ url: "https://www.pixiv.net",
+ categoryId: "design",
+ },
+ {
+ id: "aliyun-unified-console",
+ url: "https://home.console.aliyun.com",
+ categoryId: "efficiency",
+ },
+ ] as const
+
+ it("九条新链接全部存在且总数为 51", () => {
+ expect(links).toHaveLength(51)
+ expect(
+ expectedNewLinks.every(({ id }) =>
+ links.some((link) => link.id === id),
+ ),
+ ).toBe(true)
+ })
+
+ it.each(expectedNewLinks)(
+ "$id 归入 $categoryId 且 URL 正确",
+ (expected) => {
+ const link = links.find(({ id }) => id === expected.id)
+
+ expect(link).toBeDefined()
+ expect(link).toMatchObject({
+ url: expected.url,
+ categoryId: expected.categoryId,
+ pinned: false,
+ })
+ },
+ )
+ })
})
diff --git a/ts/web/nextjs/polaris/src/data/links.json b/ts/web/nextjs/polaris/src/data/links.json
index 1c0885d..7af3375 100644
--- a/ts/web/nextjs/polaris/src/data/links.json
+++ b/ts/web/nextjs/polaris/src/data/links.json
@@ -266,47 +266,27 @@
"title": "神奇宝贝百科",
"description": "宝可梦中文资料维基",
"url": "https://wiki.52poke.com/wiki/%E4%B8%BB%E9%A1%B5",
- "categoryId": "media",
+ "categoryId": "game-resources",
"pinned": false,
"keywords": ["宝可梦", "口袋妖怪", "攻略", "资料"],
"sort": 80
},
- {
- "id": "wiki-52poke-typechart",
- "title": "宝可梦属性相克表",
- "description": "宝可梦全属性克制关系速查",
- "url": "https://wiki.52poke.com/wiki/%E5%B1%9E%E6%80%A7%E7%9B%B8%E5%85%8B%E8%A1%A8",
- "categoryId": "media",
- "pinned": false,
- "keywords": ["宝可梦", "属性", "相克", "速查"],
- "sort": 90
- },
{
"id": "wiki-blhx",
"title": "碧蓝航线WIKI",
"description": "碧蓝航线官方中文百科",
"url": "https://wiki.biligame.com/blhx/%E9%A6%96%E9%A1%B5",
- "categoryId": "media",
+ "categoryId": "game-resources",
"pinned": false,
"keywords": ["碧蓝航线", "手游", "攻略", "wiki"],
"sort": 100
},
- {
- "id": "wiki-blhx-boss",
- "title": "碧蓝航线月度BOSS解析",
- "description": "大型作战月度 BOSS 攻略解析",
- "url": "https://wiki.biligame.com/blhx/%E5%A4%A7%E5%9E%8B%E4%BD%9C%E6%88%98%E6%9C%88%E5%BA%A6BOSS%E8%A7%A3%E6%9E%90",
- "categoryId": "media",
- "pinned": false,
- "keywords": ["碧蓝航线", "boss", "攻略", "大型作战"],
- "sort": 110
- },
{
"id": "wiki-fgo",
"title": "Mooncell",
"description": "FGO 中文维基与攻略资料站",
"url": "https://fgo.wiki/w/%E9%A6%96%E9%A1%B5",
- "categoryId": "media",
+ "categoryId": "game-resources",
"pinned": false,
"keywords": ["fgo", "mooncell", "从者", "攻略"],
"sort": 120
@@ -316,7 +296,7 @@
"title": "星露谷物语中文维基",
"description": "星露谷物语官方中文资料维基",
"url": "https://zh.stardewvalleywiki.com",
- "categoryId": "media",
+ "categoryId": "game-resources",
"pinned": false,
"keywords": ["星露谷物语", "攻略", "作物", "wiki"],
"sort": 130
@@ -326,7 +306,7 @@
"title": "泰拉瑞亚中文维基",
"description": "泰拉瑞亚官方中文百科",
"url": "https://terraria.wiki.gg/zh",
- "categoryId": "media",
+ "categoryId": "game-resources",
"pinned": false,
"keywords": ["泰拉瑞亚", "攻略", "物品", "wiki"],
"sort": 140
@@ -341,16 +321,6 @@
"keywords": ["动漫", "acg", "百科", "二次元"],
"sort": 150
},
- {
- "id": "farmgamehub",
- "title": "星露谷洒水器规划器",
- "description": "星露谷物语洒水器布局可视化工具",
- "url": "https://www.farmgamehub.com/zh/tools/stardew-sprinklers",
- "categoryId": "media",
- "pinned": false,
- "keywords": ["星露谷物语", "洒水器", "布局", "工具"],
- "sort": 160
- },
{
"id": "iodraw",
"title": "iodraw",
@@ -406,7 +376,8 @@
"id": "aliyun-console",
"title": "阿里云轻量控制台",
"description": "阿里云轻量应用服务器控制台",
- "url": "https://swas.console.aliyun.com",
+ "url": "https://console.aliyun.com",
+ "icon": "local:ali-cloud",
"categoryId": "efficiency",
"pinned": false,
"keywords": ["阿里云", "控制台", "轻量服务器", "云服务"],
@@ -472,5 +443,96 @@
"pinned": false,
"keywords": ["地图", "导航", "出行", "路线"],
"sort": 10
+ },
+ {
+ "id": "kzo",
+ "title": "Kmoe 漫画",
+ "description": "Kindle/EPUB 漫画下载与推送",
+ "url": "https://kzo.moe",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["漫画", "kindle", "epub", "推送"],
+ "sort": 160
+ },
+ {
+ "id": "manhuagui",
+ "title": "漫画柜",
+ "description": "在线漫画阅读站点",
+ "url": "https://www.manhuagui.com",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["漫画", "在线阅读", "动漫"],
+ "sort": 170
+ },
+ {
+ "id": "linovelib",
+ "title": "轻小说文库",
+ "description": "在线轻小说阅读站点",
+ "url": "https://www.linovelib.com",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["轻小说", "在线阅读", "文库"],
+ "sort": 180
+ },
+ {
+ "id": "steam",
+ "title": "Steam",
+ "description": "PC 游戏数字发行商店",
+ "url": "https://store.steampowered.com",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["游戏", "商店", "平台"],
+ "sort": 190
+ },
+ {
+ "id": "tieba",
+ "title": "百度贴吧",
+ "description": "百度旗下主题兴趣社区",
+ "url": "https://tieba.baidu.com",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["贴吧", "论坛", "社区", "百度"],
+ "sort": 200
+ },
+ {
+ "id": "manga-bilibili",
+ "title": "哔哩哔哩漫画",
+ "description": "哔哩哔哩正版漫画平台",
+ "url": "https://manga.bilibili.com",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["漫画", "哔哩哔哩", "正版", "阅读"],
+ "sort": 210
+ },
+ {
+ "id": "zhihu",
+ "title": "知乎",
+ "description": "中文问答社区",
+ "url": "https://www.zhihu.com",
+ "categoryId": "media",
+ "pinned": false,
+ "keywords": ["问答", "社区", "知识"],
+ "sort": 220
+ },
+ {
+ "id": "pixiv",
+ "title": "pixiv",
+ "description": "插画作品分享社区",
+ "url": "https://www.pixiv.net",
+ "categoryId": "design",
+ "pinned": false,
+ "keywords": ["插画", "绘画", "灵感", "二次元"],
+ "sort": 40
+ },
+ {
+ "id": "aliyun-unified-console",
+ "title": "阿里云统一控制台",
+ "description": "阿里云产品统一管理控制台",
+ "url": "https://home.console.aliyun.com",
+ "icon": "local:ali-cloud",
+ "categoryId": "efficiency",
+ "pinned": false,
+ "keywords": ["阿里云", "控制台", "云服务", "管理"],
+ "sort": 100
}
]
diff --git a/ts/web/nextjs/polaris/src/features/category/components/category-content.test.tsx b/ts/web/nextjs/polaris/src/features/category/components/category-content.test.tsx
index 3b8a798..d2868e7 100644
--- a/ts/web/nextjs/polaris/src/features/category/components/category-content.test.tsx
+++ b/ts/web/nextjs/polaris/src/features/category/components/category-content.test.tsx
@@ -72,6 +72,7 @@ describe("CategoryContent", () => {
"learning",
"efficiency",
"media",
+ "game-resources",
"life",
] as const)("%s 分类展示服务提供的数量", (categoryId) => {
const category = getCategoryById(categoryId)
@@ -82,4 +83,24 @@ describe("CategoryContent", () => {
expect(screen.getByText(`共 ${links.length} 个`)).toBeInTheDocument()
})
+
+ it("归类边界:游戏资料面板只渲染 5 个游戏维基卡片", () => {
+ const category = getCategoryById("game-resources")
+ if (!category) throw new Error("测试数据缺少 game-resources 分类")
+ const links = getLinksByCategory(category.id)
+
+ render(