diff --git a/src/popup/index.html b/src/popup/index.html index 6e17f31..653f54a 100644 --- a/src/popup/index.html +++ b/src/popup/index.html @@ -4,6 +4,183 @@ Star Chart Search Enhancer +
diff --git a/src/popup/index.ts b/src/popup/index.ts index 5a870d6..3124ae9 100644 --- a/src/popup/index.ts +++ b/src/popup/index.ts @@ -104,7 +104,7 @@ async function renderCurrentAuthState( } renderLoggedIn(root, response.value); - void runUpdateCheck(root, sendMessage, updateOptions); + await runUpdateCheck(root, sendMessage, updateOptions); root .querySelector('[data-popup-sign-out="button"]') ?.addEventListener("click", () => { @@ -195,9 +195,10 @@ async function runUpdateCheck( status: "available" }); bindUpdateDownloadButtons(root, sendMessage, manifest); - } catch { + } catch (error) { renderUpdateStatus(root, { currentVersion: options.currentVersion, + message: error instanceof Error ? error.message : String(error), status: "error" }); } diff --git a/src/popup/view.ts b/src/popup/view.ts index e5fce55..aaec8f7 100644 --- a/src/popup/view.ts +++ b/src/popup/view.ts @@ -3,11 +3,18 @@ import type { UpdateManifest } from "../shared/update-check"; export function renderLoggedOut(root: HTMLElement, error?: string | null): void { root.innerHTML = ` -
-

Star Chart Search Enhancer

-

登录后才能使用星图增强功能

- ${error ? `

${error}

` : ""} - + `; } @@ -19,16 +26,24 @@ export function renderLoggedIn( const userInfo = authState.userInfo; root.innerHTML = ` -
-

Star Chart Search Enhancer

-

已登录

-

${userInfo?.name ?? userInfo?.username ?? "未知用户"}

-

${userInfo?.email ?? ""}

-
-

版本更新

-

正在检查更新...

+ `; } @@ -38,50 +53,54 @@ export function renderUpdateStatus( options: { currentVersion: string; manifest?: UpdateManifest; + message?: string | null; status: "checking" | "error" | "latest" | "available"; } ): void { - const container = root.querySelector('[data-popup-update="root"]'); + const container = root.querySelector('[data-popup-update-root="root"]'); if (!container) { return; } if (options.status === "checking") { container.innerHTML = ` -

版本更新

-

当前版本:${options.currentVersion}

-

正在检查更新...

+ + + `; return; } if (options.status === "error") { container.innerHTML = ` -

版本更新

-

当前版本:${options.currentVersion}

-

暂时无法检查更新

-

如果需要新版,请联系维护同事获取更新包。

+ + + + ${options.message ? `` : ""} + `; return; } if (options.status === "latest" || !options.manifest) { container.innerHTML = ` -

版本更新

-

当前版本:${options.currentVersion}

-

当前已是最新版本

+ + + `; return; } container.innerHTML = ` -

版本更新

-

当前版本:${options.currentVersion}

-

发现新版本:${options.manifest.latestVersion}

+ + + ${renderReleaseNotes(options.manifest.releaseNotes)} - - -

下载后请解压新版 zip,并在 chrome://extensions 里重新加载插件。

+ + `; } @@ -103,7 +122,7 @@ function renderReleaseNotes(releaseNotes: string[]): string { } return ` -
    + `; @@ -122,15 +141,16 @@ export function renderDevPanel( authState: AuthStateValue ): void { const panel = root.ownerDocument.createElement("section"); + panel.className = "popup-card popup-card--dev"; panel.dataset.popupDevPanel = "root"; panel.innerHTML = ` -

    dev auth panel

    -

    resource: ${authState.resource ?? ""}

    -

    scopes: ${(authState.scopes ?? []).join(", ")}

    -

    token: ${authState.tokenAvailable ? "available" : "missing"}

    -

    expires: ${authState.accessTokenExpiresAt ?? "unknown"}

    -

    error: ${authState.lastError ?? ""}

    - + + + + + + +
    
       `;
       root.appendChild(panel);
    diff --git a/tests/popup-entry.test.ts b/tests/popup-entry.test.ts
    index 1bc2ef4..0965c1d 100644
    --- a/tests/popup-entry.test.ts
    +++ b/tests/popup-entry.test.ts
    @@ -3,6 +3,10 @@ import { beforeEach, describe, expect, test, vi } from "vitest";
     
     import { bootPopup } from "../src/popup/index";
     
    +function flushTasks(): Promise {
    +  return new Promise((resolve) => setTimeout(resolve, 0));
    +}
    +
     describe("popup-entry", () => {
       let dom: JSDOM;
     
    @@ -22,6 +26,12 @@ describe("popup-entry", () => {
           }))
         });
     
    +    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(
           "登录"
         );
    @@ -47,6 +57,12 @@ describe("popup-entry", () => {
           }))
         });
     
    +    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");
       });
    @@ -76,12 +92,16 @@ describe("popup-entry", () => {
             }
           }))
         });
    -    await Promise.resolve();
    +    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();
    @@ -118,7 +138,8 @@ describe("popup-entry", () => {
           })),
           sendMessage
         });
    -    await Promise.resolve();
    +    await flushTasks();
    +    await flushTasks();
     
         (
           dom.window.document.querySelector(
    @@ -245,6 +266,63 @@ describe("popup-entry", () => {
         expect(sendMessage).toHaveBeenCalledWith({ type: "auth:sign-out" });
       });
     
    +  test("shows the latest state without update actions", async () => {
    +    dom.window.document.body.innerHTML = "
    "; + + 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 = "
    "; + + 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()