From fe37a47f980a54cace443555da14a182f68f6502 Mon Sep 17 00:00:00 2001 From: Navid Shad Date: Fri, 19 Jun 2026 19:07:34 +0300 Subject: [PATCH] feat(popup): show extension version in home footer #86exqazkq MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Surface the installed extension version in the popup so users and support can confirm which version they're on at a glance — previously the version was shown nowhere in the UI. - Add getExtensionVersion() helper in common/static/global.ts. It prefers the manifest's version_name (full semver incl. prerelease channel, e.g. 1.15.1-dev.N) and falls back to the numeric manifest version on stable builds, with a final fallback to the package.json VERSION constant for non-extension/test contexts (the Vitest chrome shim has no getManifest). - Render a subtle "v" line at the bottom of the Home footer, the default popup view shown to both logged-in and logged-out users. - Add tests/global-version.test.ts covering all three resolution branches. Co-Authored-By: Claude Opus 4.8 --- src/common/static/global.ts | 16 ++++++++++++++++ src/popup/views/HomeView.vue | 13 ++++++++++++- tests/global-version.test.ts | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 tests/global-version.test.ts diff --git a/src/common/static/global.ts b/src/common/static/global.ts index cf784a2..8afcdcf 100644 --- a/src/common/static/global.ts +++ b/src/common/static/global.ts @@ -2,6 +2,22 @@ import { authentication } from "@modular-rest/client"; export const VERSION = require("../../../package.json").version; +/** + * Installed-extension version for display. Prefers manifest `version_name` + * (carries the full semver incl. prerelease channel, e.g. "1.15.1-dev.1") and + * falls back to the numeric manifest `version` on stable builds. Outside an + * extension context (unit tests / non-chrome), falls back to the package.json + * VERSION constant — the Vitest chrome shim doesn't implement getManifest. + */ +export function getExtensionVersion(): string { + try { + const manifest = chrome.runtime.getManifest(); + return manifest.version_name || manifest.version; + } catch { + return VERSION; + } +} + export const SUBTURTLE_DASHBOARD_URL = process.env.SUBTURTLE_DASHBOARD_URL; export function getSubturtleDashboardUrlWithToken(redirectPath?: string) { diff --git a/src/popup/views/HomeView.vue b/src/popup/views/HomeView.vue index 0bc7d53..721ac3e 100644 --- a/src/popup/views/HomeView.vue +++ b/src/popup/views/HomeView.vue @@ -283,6 +283,13 @@ + + +
+ v{{ appVersion }} +
@@ -293,7 +300,10 @@ import { computed, onMounted, ref, watch } from "vue"; import { getAsset } from "../helper/assets"; import { isLogin, logout } from "../../plugins/modular-rest"; import { useRouter } from "vue-router"; -import { getSubturtleDashboardUrlWithToken } from "../../common/static/global"; +import { + getSubturtleDashboardUrlWithToken, + getExtensionVersion, +} from "../../common/static/global"; import { useSettingsStore } from "../../common/store/settings"; import TranslateCard from "../components/TranslateCard.vue"; @@ -304,6 +314,7 @@ defineOptions({ name: "HomeView" }); const router = useRouter(); const isLoading = ref(false); const showLogoutConfirm = ref(false); +const appVersion = getExtensionVersion(); const settings = useSettingsStore(); const currentHost = ref(""); diff --git a/tests/global-version.test.ts b/tests/global-version.test.ts new file mode 100644 index 0000000..c8914a3 --- /dev/null +++ b/tests/global-version.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { getExtensionVersion, VERSION } from "../src/common/static/global"; + +// getExtensionVersion drives the "vX.Y.Z" line shown in the popup Home footer. +// It must prefer the manifest's version_name (full semver incl. prerelease +// channel) over the numeric version, and degrade gracefully when there's no +// real extension runtime (the Vitest chrome shim has no getManifest). +describe("getExtensionVersion", () => { + const runtime = (globalThis as any).chrome.runtime; + + afterEach(() => { + // The shim is shared across files — don't leak a getManifest into it. + delete runtime.getManifest; + }); + + it("prefers version_name when present (prerelease build)", () => { + runtime.getManifest = () => ({ + version: "1.15.1.2", + version_name: "1.15.1-dev.2", + }); + expect(getExtensionVersion()).toBe("1.15.1-dev.2"); + }); + + it("falls back to numeric version on a stable build", () => { + runtime.getManifest = () => ({ version: "1.15.1" }); + expect(getExtensionVersion()).toBe("1.15.1"); + }); + + it("falls back to the package.json VERSION outside an extension context", () => { + // No getManifest on the shim → call throws → catch returns VERSION. + expect(runtime.getManifest).toBeUndefined(); + expect(getExtensionVersion()).toBe(VERSION); + }); +});