From af44ce2fa2df01eaabb33b390903c322ab5d0df6 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Sat, 11 Jul 2026 22:24:32 +0800 Subject: [PATCH] fix: preserve popup copy when catalogs fail --- package.json | 2 +- scripts/test-popup-catalog-fallback.js | 71 ++++++++++++++++++++++++++ shared/popup/popup.js | 5 +- 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 scripts/test-popup-catalog-fallback.js diff --git a/package.json b/package.json index 4cb5fee..52849bd 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Verstak browser capture extension for Chromium and Firefox", "scripts": { "build": "node scripts/build-extension.js", - "test": "node scripts/test-protocol.js && node scripts/test-i18n.js && node scripts/test-popup-settings.js && node scripts/test-background-i18n.js", + "test": "node scripts/test-protocol.js && node scripts/test-i18n.js && node scripts/test-popup-settings.js && node scripts/test-popup-catalog-fallback.js && node scripts/test-background-i18n.js", "sign:firefox": "./scripts/sign-firefox-xpi.sh", "release:firefox": "./scripts/release-firefox-xpi.sh" }, diff --git a/scripts/test-popup-catalog-fallback.js b/scripts/test-popup-catalog-fallback.js new file mode 100644 index 0000000..94fbe9b --- /dev/null +++ b/scripts/test-popup-catalog-fallback.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); +const vm = require('vm'); + +class Element { + constructor(text) { + this.value = ''; + this.textContent = text || ''; + this.className = ''; + this.files = []; + this.listeners = {}; + } + addEventListener(type, listener) { this.listeners[type] = listener; } +} + +const ids = [ + 'status', 'receiver-state', 'receiver-url', 'receiver-input', 'receiver-token-input', + 'language-select', 'file-input', 'pending-count', 'status-dot', 'subtitle', + 'receiver-label', 'pending-label', 'url-label', 'file-label', 'receiver-url-label', + 'receiver-token-label', 'language-label', 'capture-page', 'capture-file', 'retry', + 'save-settings', 'context-menu-hint', 'language-system-option', 'language-en-option', + 'language-ru-option', +]; +const elements = Object.fromEntries(ids.map((id) => [id, new Element()])); +elements['capture-page'].textContent = 'Send Page'; + +const browser = { + runtime: { + getURL(relativePath) { return `extension://${relativePath}`; }, + sendMessage() { + return Promise.resolve({ + settings: { receiverUrl: 'http://127.0.0.1/', receiverToken: '', language: 'system' }, + pendingCount: 0, + status: {}, + }); + }, + }, + i18n: { getUILanguage() { return 'en-US'; } }, +}; +const document = { + activeElement: null, + documentElement: { lang: '' }, + getElementById(id) { return elements[id]; }, +}; +const context = vm.createContext({ + browser, + document, + navigator: { language: 'en-US' }, + console: { warn() {}, error: console.error }, + Promise, + fetch() { return Promise.reject(new Error('catalog unavailable')); }, +}); +context.globalThis = context; + +const root = path.resolve(__dirname, '..'); +for (const file of ['shared/i18n.js', 'shared/popup/popup.js']) { + vm.runInContext(fs.readFileSync(path.join(root, file), 'utf8'), context, { filename: file }); +} + +(async () => { + for (let i = 0; i < 16; i += 1) await Promise.resolve(); + assert.strictEqual(elements['capture-page'].textContent, 'Send Page'); + assert.strictEqual(elements['receiver-state'].textContent, 'Unknown'); + assert.strictEqual(document.documentElement.lang, 'en'); + console.log('browser extension popup catalog fallback tests passed'); +})().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/shared/popup/popup.js b/shared/popup/popup.js index 36d2b21..819ac26 100644 --- a/shared/popup/popup.js +++ b/shared/popup/popup.js @@ -82,7 +82,10 @@ languageSelectEl.value = currentPreference; Object.keys(staticText).forEach(function (id) { var element = document.getElementById(id); - if (element) element.textContent = t(staticText[id]); + if (element) { + if (element.__verstakI18nFallback == null) element.__verstakI18nFallback = element.textContent; + element.textContent = t(staticText[id], null, element.__verstakI18nFallback); + } }); applyReceiverState(currentState); }