mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-11-12 07:21:01 +08:00
refactor(download-count): 下载量改为读取 app.json/applist.json 的 DownloadTimes
下载量不再逐个应用请求 download-times.txt(数千个应用 = 数千次额外请求), 改为直接读取服务器注入到目录/详情 JSON 的 DownloadTimes 字段。 - AppJson 新增 DownloadTimes,normalizeAppJson 映射为 app.downloadCount - useRanking 移除逐应用请求、并发批处理与 localStorage 缓存, 直接按已加载 apps 的 downloadCount 排序发布(零额外请求) - 详情页优先用目录数据的 downloadCount;缺失时(如已安装页兜底 App) 回退读取 app.json 的 DownloadTimes,并加请求序号防止旧响应覆盖 - modules/ranking 新增 parseDownloadTimes,统一处理数字/字符串/非法值 - 新增回归测试 ranking-download-count.test.ts - 更新《下载量统计接口规范》与 ranking 计划文档 验证:vue-tsc 0 error;新增 6 项测试通过;全套单测失败项与改动前 完全一致(35 项均为既有失败,无新增);eslint 改动文件无新增问题。 Signed-off-by: shenmo <jifengshenmo@outlook.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { normalizeAppJson } from "@/composables/useCatalog";
|
||||
import { loadRanking } from "@/composables/useRanking";
|
||||
import {
|
||||
apps,
|
||||
apmRanking,
|
||||
rankingLoading,
|
||||
sparkRanking,
|
||||
} from "@/composables/useAppState";
|
||||
import { parseDownloadTimes } from "@/modules/ranking";
|
||||
import type { App, AppJson } from "@/global/typedefinition";
|
||||
|
||||
/**
|
||||
* 下载量来源回归测试:
|
||||
* 下载量取自 app.json / applist.json 的 DownloadTimes 字段,
|
||||
* 不得再逐个应用请求 download-times.txt。
|
||||
*/
|
||||
|
||||
const baseJson = (overrides: Partial<AppJson>): AppJson => ({
|
||||
Name: "App",
|
||||
Version: "1.0.0",
|
||||
Filename: "app_1.0.0_amd64.deb",
|
||||
Torrent_address: "",
|
||||
Pkgname: "app",
|
||||
Author: "",
|
||||
Contributor: "",
|
||||
Website: "",
|
||||
Update: "2026-01-01 00:00:00",
|
||||
Size: "1M",
|
||||
More: "",
|
||||
Tags: "",
|
||||
img_urls: "[]",
|
||||
icons: "",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("parseDownloadTimes", () => {
|
||||
it("接受数字与数字字符串", () => {
|
||||
expect(parseDownloadTimes(52210)).toBe(52210);
|
||||
expect(parseDownloadTimes("4806")).toBe(4806);
|
||||
expect(parseDownloadTimes(" 42 ")).toBe(42);
|
||||
});
|
||||
|
||||
it("截断小数并拒绝非法值", () => {
|
||||
expect(parseDownloadTimes(12.7)).toBe(12);
|
||||
expect(parseDownloadTimes(undefined)).toBeUndefined();
|
||||
expect(parseDownloadTimes(null)).toBeUndefined();
|
||||
expect(parseDownloadTimes("")).toBeUndefined();
|
||||
expect(parseDownloadTimes("abc")).toBeUndefined();
|
||||
expect(parseDownloadTimes(-1)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeAppJson", () => {
|
||||
it("把 applist.json 的 DownloadTimes 映射为 downloadCount", () => {
|
||||
const app = normalizeAppJson(
|
||||
baseJson({ DownloadTimes: 9001 }),
|
||||
"office",
|
||||
"spark",
|
||||
);
|
||||
expect(app.downloadCount).toBe(9001);
|
||||
});
|
||||
|
||||
it("字段缺失时 downloadCount 为 undefined(不代表 0 次下载)", () => {
|
||||
const app = normalizeAppJson(baseJson({}), "office", "apm");
|
||||
expect(app.downloadCount).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("loadRanking", () => {
|
||||
const makeApp = (
|
||||
pkgname: string,
|
||||
origin: "spark" | "apm",
|
||||
downloadCount?: number,
|
||||
): App => ({
|
||||
name: pkgname,
|
||||
pkgname,
|
||||
version: "1.0.0",
|
||||
filename: `${pkgname}.deb`,
|
||||
torrent_address: "",
|
||||
author: "",
|
||||
contributor: "",
|
||||
website: "",
|
||||
update: "",
|
||||
size: "",
|
||||
more: "",
|
||||
tags: "",
|
||||
img_urls: [],
|
||||
icons: "",
|
||||
category: "tools",
|
||||
origin,
|
||||
currentStatus: "not-installed",
|
||||
downloadCount,
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
apps.value = [];
|
||||
apmRanking.value = [];
|
||||
sparkRanking.value = [];
|
||||
rankingLoading.value = false;
|
||||
});
|
||||
|
||||
it("按 downloadCount 排序分来源发布,且不发网络请求", () => {
|
||||
const fetchSpy = vi.spyOn(globalThis, "fetch");
|
||||
apps.value = [
|
||||
makeApp("apm-low", "apm", 10),
|
||||
makeApp("spark-low", "spark", 5),
|
||||
makeApp("apm-high", "apm", 900),
|
||||
makeApp("spark-high", "spark", 500),
|
||||
makeApp("no-count", "apm"),
|
||||
];
|
||||
|
||||
loadRanking();
|
||||
|
||||
expect(apmRanking.value.map((a) => a.pkgname)).toEqual([
|
||||
"apm-high",
|
||||
"apm-low",
|
||||
"no-count",
|
||||
]);
|
||||
expect(sparkRanking.value.map((a) => a.pkgname)).toEqual([
|
||||
"spark-high",
|
||||
"spark-low",
|
||||
]);
|
||||
expect(rankingLoading.value).toBe(false);
|
||||
expect(fetchSpy).not.toHaveBeenCalled();
|
||||
fetchSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("应用列表为空时直接返回,不发布空榜", () => {
|
||||
loadRanking();
|
||||
expect(apmRanking.value).toEqual([]);
|
||||
expect(sparkRanking.value).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user