feat: add extension update check

This commit is contained in:
2026-05-19 18:50:03 +08:00
parent 703a095c08
commit 02d9063a11
22 changed files with 919 additions and 18 deletions
+44
View File
@@ -48,6 +48,50 @@ describe("background-index", () => {
expect(sendResponse).toHaveBeenCalledWith({ ok: true });
});
test("downloads extension update assets", async () => {
const listeners: Array<
(message: unknown, sender: unknown, sendResponse: (response: unknown) => void) => boolean | void
> = [];
const download = vi.fn(async () => undefined);
const sendResponse = vi.fn();
registerBackgroundMessageHandler({
downloads: {
download
},
runtime: {
onMessage: {
addListener(listener) {
listeners.push(listener);
}
}
}
});
const result = listeners[0](
{
filename: "star-chart-search-enhancer-internal.zip",
type: "update:download",
url: "https://cos.example.com/star-chart-search-enhancer/releases/0.2.0421.3/star-chart-search-enhancer-internal.zip"
},
{},
sendResponse
);
expect(result).toBe(true);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(download).toHaveBeenCalledWith({
filename: "star-chart-search-enhancer-internal.zip",
saveAs: true,
url: "https://cos.example.com/star-chart-search-enhancer/releases/0.2.0421.3/star-chart-search-enhancer-internal.zip"
});
expect(sendResponse).toHaveBeenCalledWith({
ok: true,
type: "update:download-ack"
});
});
test("responds to auth:get-state with auth status", async () => {
const listeners: Array<
(message: unknown, sender: unknown, sendResponse: (response: unknown) => void) => boolean | void
+3 -2
View File
@@ -43,10 +43,11 @@ describe("manifest", () => {
"https://*.xingtu.cn/ad/creator/market*",
"https://login-api.intelligrow.cn/*",
"https://talent-search.intelligrow.cn/*",
"http://192.168.31.21:8083/*"
"http://192.168.31.21:8083/*",
"https://*/*"
]);
expect(releaseManifest.host_permissions).not.toEqual(
expect.arrayContaining(["http://*/*", "https://*/*", "http://127.0.0.1:4319/*"])
expect.arrayContaining(["http://*/*", "http://127.0.0.1:4319/*"])
);
});
+95
View File
@@ -51,6 +51,101 @@ describe("popup-entry", () => {
expect(dom.window.document.body.textContent).toContain("token");
});
test("shows available extension updates in the popup", async () => {
const fetchUpdateManifest = vi.fn(async () => ({
guideUrl: "https://cos.example.com/guide.pdf",
latestVersion: "0.2.0421.3",
minSupportedVersion: "0.2.0421.2",
publishedAt: "2026-05-19",
releaseNotes: ["支持检查更新"],
zipUrl: "https://cos.example.com/plugin.zip"
}));
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
currentVersion: "0.2.0421.2",
document: dom.window.document,
fetchUpdateManifest,
sendMessage: vi.fn(async () => ({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
userInfo: { name: "Dev" }
}
}))
});
await Promise.resolve();
expect(fetchUpdateManifest).toHaveBeenCalledTimes(1);
expect(dom.window.document.body.textContent).toContain("当前版本:0.2.0421.2");
expect(dom.window.document.body.textContent).toContain("发现新版本:0.2.0421.3");
expect(dom.window.document.body.textContent).toContain("支持检查更新");
expect(
dom.window.document.querySelector('[data-popup-download-update="button"]')
).not.toBeNull();
expect(
dom.window.document.querySelector('[data-popup-download-guide="button"]')
).not.toBeNull();
});
test("downloads update assets from popup buttons", async () => {
const sendMessage = vi
.fn()
.mockResolvedValueOnce({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
userInfo: { name: "Dev" }
}
})
.mockResolvedValue({ ok: true, type: "update:download-ack" });
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
currentVersion: "0.2.0421.2",
document: dom.window.document,
fetchUpdateManifest: vi.fn(async () => ({
guideUrl: "https://cos.example.com/guide.pdf",
latestVersion: "0.2.0421.3",
minSupportedVersion: "0.2.0421.2",
publishedAt: "2026-05-19",
releaseNotes: [],
zipUrl: "https://cos.example.com/plugin.zip"
})),
sendMessage
});
await Promise.resolve();
(
dom.window.document.querySelector(
'[data-popup-download-update="button"]'
) as HTMLButtonElement | null
)?.click();
(
dom.window.document.querySelector(
'[data-popup-download-guide="button"]'
) as HTMLButtonElement | null
)?.click();
await Promise.resolve();
await Promise.resolve();
expect(sendMessage).toHaveBeenCalledWith({
filename: "star-chart-search-enhancer-internal.zip",
type: "update:download",
url: "https://cos.example.com/plugin.zip"
});
expect(sendMessage).toHaveBeenCalledWith({
filename: "星图增强插件-超简单安装使用指南.pdf",
type: "update:download",
url: "https://cos.example.com/guide.pdf"
});
});
test("renders a protected api test button in the dev panel", async () => {
dom.window.document.body.innerHTML = "<main id='app'></main>";
+49
View File
@@ -0,0 +1,49 @@
import { describe, expect, test } from "vitest";
import {
compareExtensionVersions,
parseUpdateManifest
} from "../src/shared/update-check";
describe("update-check", () => {
test("compares dotted extension versions numerically", () => {
expect(compareExtensionVersions("0.2.0421.3", "0.2.0421.2")).toBeGreaterThan(0);
expect(compareExtensionVersions("0.2.10.0", "0.2.9.9")).toBeGreaterThan(0);
expect(compareExtensionVersions("0.2.0421.2", "0.2.0421.2")).toBe(0);
expect(compareExtensionVersions("0.2.0421.1", "0.2.0421.2")).toBeLessThan(0);
});
test("parses a valid update manifest", () => {
expect(
parseUpdateManifest({
guideUrl: "https://cos.example.com/guide.pdf",
latestVersion: "0.2.0421.3",
minSupportedVersion: "0.2.0421.2",
publishedAt: "2026-05-19",
releaseNotes: ["支持检查更新"],
zipUrl: "https://cos.example.com/plugin.zip"
})
).toEqual({
guideUrl: "https://cos.example.com/guide.pdf",
latestVersion: "0.2.0421.3",
minSupportedVersion: "0.2.0421.2",
publishedAt: "2026-05-19",
releaseNotes: ["支持检查更新"],
zipUrl: "https://cos.example.com/plugin.zip"
});
});
test("rejects invalid update manifests", () => {
expect(parseUpdateManifest({ latestVersion: "0.2.0421.3" })).toBeNull();
expect(
parseUpdateManifest({
guideUrl: "javascript:alert(1)",
latestVersion: "0.2.0421.3",
minSupportedVersion: "0.2.0421.2",
publishedAt: "2026-05-19",
releaseNotes: [],
zipUrl: "https://cos.example.com/plugin.zip"
})
).toBeNull();
});
});