star-chart-search-enhancer/tests/popup-entry.test.ts

362 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { JSDOM } from "jsdom";
import { beforeEach, describe, expect, test, vi } from "vitest";
import { bootPopup } from "../src/popup/index";
function flushTasks(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 0));
}
describe("popup-entry", () => {
let dom: JSDOM;
beforeEach(() => {
dom = new JSDOM("<!doctype html><html><body></body></html>");
});
test("renders a sign-in button when unauthenticated", async () => {
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
document: dom.window.document,
sendMessage: vi.fn(async () => ({
ok: true,
type: "auth:state",
value: { isAuthenticated: false }
}))
});
expect(
dom.window.document.querySelector('[data-popup-shell="root"]')
).not.toBeNull();
expect(
dom.window.document.querySelector('[data-popup-account="card"]')
).not.toBeNull();
expect(dom.window.document.querySelector("button")?.textContent).toContain(
"登录"
);
});
test("renders the dev auth panel when enabled", async () => {
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
config: { enableDevAuthPanel: true },
document: dom.window.document,
sendMessage: vi.fn(async () => ({
ok: true,
type: "auth:state",
value: {
accessTokenExpiresAt: 1700000000000,
isAuthenticated: true,
resource: "https://api.example.test",
scopes: ["openid", "profile"],
tokenAvailable: true,
userInfo: { email: "dev@example.com", name: "Dev" }
}
}))
});
expect(
dom.window.document.querySelector('[data-popup-header="root"]')
).not.toBeNull();
expect(
dom.window.document.querySelector('[data-popup-account="card"]')
).not.toBeNull();
expect(dom.window.document.body.textContent).toContain("resource");
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 flushTasks();
await flushTasks();
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-update="card"]')
).not.toBeNull();
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 flushTasks();
await flushTasks();
(
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>";
await bootPopup({
config: { enableDevAuthPanel: true },
document: dom.window.document,
sendMessage: vi.fn(async () => ({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
tokenAvailable: true
}
}))
});
expect(
dom.window.document.querySelector('[data-popup-test-protected-api="button"]')
).not.toBeNull();
});
test("clicking the dev button runs the protected api client and prints the result", async () => {
const fetchProtectedApi = vi.fn(async () => ({
message: "authorized",
ok: true,
source: "mock-protected-api"
}));
const sendMessage = vi.fn(async () => ({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
tokenAvailable: true
}
}));
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
config: { enableDevAuthPanel: true },
document: dom.window.document,
fetchProtectedApi,
sendMessage
});
(
dom.window.document.querySelector(
'[data-popup-test-protected-api="button"]'
) as HTMLButtonElement | null
)?.click();
await Promise.resolve();
expect(fetchProtectedApi).toHaveBeenCalledTimes(1);
expect(dom.window.document.body.textContent).toContain("authorized");
expect(dom.window.document.body.textContent).toContain("mock-protected-api");
});
test("clicking sign-out sends the auth:sign-out message", async () => {
const sendMessage = vi
.fn()
.mockResolvedValueOnce({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
userInfo: { email: "dev@example.com", name: "Dev" }
}
})
.mockResolvedValueOnce({
ok: true,
type: "auth:ack"
})
.mockResolvedValueOnce({
ok: true,
type: "auth:state",
value: {
isAuthenticated: false
}
});
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
document: dom.window.document,
sendMessage
});
(
dom.window.document.querySelector('[data-popup-sign-out="button"]') as
| HTMLButtonElement
| null
)?.click();
await Promise.resolve();
expect(sendMessage).toHaveBeenCalledWith({ type: "auth:sign-out" });
});
test("shows the latest state without update actions", async () => {
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
currentVersion: "0.0525.5",
document: dom.window.document,
fetchUpdateManifest: vi.fn(async () => ({
guideUrl: "https://cos.example.com/guide.pdf",
latestVersion: "0.0525.5",
minSupportedVersion: "0.0525.5",
publishedAt: "2026-05-19",
releaseNotes: ["支持检查更新"],
zipUrl: "https://cos.example.com/plugin.zip"
})),
sendMessage: vi.fn(async () => ({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
userInfo: { name: "Dev" }
}
}))
});
await flushTasks();
await flushTasks();
expect(dom.window.document.body.textContent).toContain("当前已是最新版本");
expect(
dom.window.document.querySelector('[data-popup-download-update="button"]')
).toBeNull();
});
test("shows a readable error state when the manifest fetch fails", async () => {
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
currentVersion: "0.0525.5",
document: dom.window.document,
fetchUpdateManifest: vi.fn(async () => {
throw new Error("network down");
}),
sendMessage: vi.fn(async () => ({
ok: true,
type: "auth:state",
value: {
isAuthenticated: true,
userInfo: { name: "Dev" }
}
}))
});
await flushTasks();
await flushTasks();
expect(dom.window.document.body.textContent).toContain("暂时无法检查更新");
expect(dom.window.document.body.textContent).toContain("network down");
});
test("shows the auth error when sign-in fails", async () => {
const sendMessage = vi
.fn()
.mockResolvedValueOnce({
ok: true,
type: "auth:state",
value: {
isAuthenticated: false
}
})
.mockResolvedValueOnce({
error: "redirect_uri_mismatch",
ok: false,
type: "auth:error"
});
dom.window.document.body.innerHTML = "<main id='app'></main>";
await bootPopup({
document: dom.window.document,
sendMessage
});
(
dom.window.document.querySelector('[data-popup-sign-in="button"]') as
| HTMLButtonElement
| null
)?.click();
await Promise.resolve();
expect(dom.window.document.body.textContent).toContain(
"redirect_uri_mismatch"
);
});
});