diff --git a/extensions/lingoflash/README.md b/extensions/lingoflash/README.md index 945be3ce..5c950419 100644 --- a/extensions/lingoflash/README.md +++ b/extensions/lingoflash/README.md @@ -1,4 +1,4 @@ -# LingoFlash Browser Extension v1.6.2 +# LingoFlash Browser Extension v1.6.3 A Manifest V3 WebExtension for Chrome-compatible browsers and Safari. @@ -47,7 +47,7 @@ npm run extension:build Output: - `artifacts/browser-extension/lingoflash/` -- `artifacts/browser-extension/lingoflash-extension-v1.6.2.zip` +- `artifacts/browser-extension/lingoflash-extension-v1.6.3.zip` ## Chrome installation diff --git a/extensions/lingoflash/manifest.json b/extensions/lingoflash/manifest.json index 364dab73..38315489 100644 --- a/extensions/lingoflash/manifest.json +++ b/extensions/lingoflash/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "LingoFlash — Translate & Save", "short_name": "LingoFlash", - "version": "1.6.2", + "version": "1.6.3", "description": "Translate selected text inline, or create and save one LingoFlash flashcard with one AI request.", "permissions": [ "activeTab", diff --git a/extensions/lingoflash/popup.css b/extensions/lingoflash/popup.css index 83b32cd3..5198acee 100644 --- a/extensions/lingoflash/popup.css +++ b/extensions/lingoflash/popup.css @@ -8,8 +8,7 @@ * { box-sizing: border-box; } body { margin: 0; - width: 380px; - max-width: 100vw; + min-width: 380px; background: radial-gradient(circle at 15% 0%, #163852 0%, transparent 45%), radial-gradient(circle at 85% 100%, #0e2b3d 0%, transparent 45%), #07111f; } button, textarea, select { font: inherit; } diff --git a/extensions/lingoflash/popup.html b/extensions/lingoflash/popup.html index 2fcbac43..28334eec 100644 --- a/extensions/lingoflash/popup.html +++ b/extensions/lingoflash/popup.html @@ -11,7 +11,7 @@
-

LINGOFLASH v1.6.2

+

LINGOFLASH v1.6.3

Dịch ngay tại trang

diff --git a/extensions/lingoflash/tests/background.node.mjs b/extensions/lingoflash/tests/background.node.mjs index 59dac3bb..d4eac92f 100644 --- a/extensions/lingoflash/tests/background.node.mjs +++ b/extensions/lingoflash/tests/background.node.mjs @@ -109,7 +109,7 @@ const createWorkerContext = async ({ onStartup: events.startup, onMessage: events.messages, getManifest() { - return { version: '1.6.2' }; + return { version: '1.6.3' }; }, sendMessage: message => { calls.push({ type: 'runtime.sendMessage', message }); diff --git a/extensions/lingoflash/tests/manifest.node.mjs b/extensions/lingoflash/tests/manifest.node.mjs index b81ab89b..4dc1c29d 100644 --- a/extensions/lingoflash/tests/manifest.node.mjs +++ b/extensions/lingoflash/tests/manifest.node.mjs @@ -7,7 +7,7 @@ const manifest = JSON.parse( ); test('keeps Chrome manifest description within the published limit', () => { - assert.equal(manifest.version, '1.6.2'); + assert.equal(manifest.version, '1.6.3'); assert.equal(manifest.background?.service_worker, 'background.js'); assert.equal(manifest.options_ui?.page, 'options.html'); assert.deepEqual(manifest.host_permissions, [ diff --git a/extensions/lingoflash/tests/popup.node.mjs b/extensions/lingoflash/tests/popup.node.mjs index 7e0cdfd9..40f5d612 100644 --- a/extensions/lingoflash/tests/popup.node.mjs +++ b/extensions/lingoflash/tests/popup.node.mjs @@ -249,8 +249,9 @@ test('hides Chrome shortcut settings on browsers that cannot open them', async ( assert.equal(popup.elements.get('open-shortcuts').hidden, true); }); -test('popup styles reflow at narrow widths and keep helper text WCAG-readable', () => { - assert.doesNotMatch(popupCssSource, /min-width:\s*380px/); +test('popup keeps its designed width and reflows fallback layouts', () => { + assert.match(popupCssSource, /body\s*\{[^}]*min-width:\s*380px/); + assert.doesNotMatch(popupCssSource, /max-width:\s*100vw/); assert.match(popupCssSource, /max-width:\s*340px[\s\S]*\.config-grid[\s\S]*grid-template-columns:\s*1fr/); assert.match(popupCssSource, /max-width:\s*340px[\s\S]*\.actions[\s\S]*grid-template-columns:\s*1fr/); assert.match(popupCssSource, /textarea::placeholder\s*\{\s*color:\s*#94a3b8/); diff --git a/extensions/lingoflash/tests/release-check.node.mjs b/extensions/lingoflash/tests/release-check.node.mjs index 7b007730..e41f26a0 100644 --- a/extensions/lingoflash/tests/release-check.node.mjs +++ b/extensions/lingoflash/tests/release-check.node.mjs @@ -33,8 +33,8 @@ const runCheckWithMutation = async (file, mutate, expectedMessage = `${file} ver }; test('release guard rejects popup and README version drift', async () => { - await runCheckWithMutation('popup.html', source => source.replace('v1.6.2', 'v1.6.3')); - await runCheckWithMutation('README.md', source => source.replace('v1.6.2', 'v1.6.3')); + await runCheckWithMutation('popup.html', source => source.replace('v1.6.3', 'v1.6.4')); + await runCheckWithMutation('README.md', source => source.replace('v1.6.3', 'v1.6.4')); }); test('release guard rejects an extension that drops the v3 nonce contract', async () => { diff --git a/scripts/check-browser-extension.mjs b/scripts/check-browser-extension.mjs index e7dd57a0..3bd5592c 100644 --- a/scripts/check-browser-extension.mjs +++ b/scripts/check-browser-extension.mjs @@ -38,7 +38,7 @@ for (const permission of manifest.permissions ?? []) if (!allowedPermissions.has for (const permission of allowedPermissions) if (!(manifest.permissions ?? []).includes(permission)) fail(`missing permission: ${permission}`); const command = manifest.commands?.['translate-selection']; if (!command?.suggested_key?.default || !command.suggested_key.mac) fail('keyboard shortcut suggestions must cover default and macOS.'); -if (manifest.version !== '1.6.2') fail('manifest must publish the polished v1.6 extension as v1.6.2.'); +if (manifest.version !== '1.6.3') fail('manifest must publish the polished v1.6 extension as v1.6.3.'); if (JSON.stringify([...(manifest.optional_host_permissions ?? [])].sort()) !== JSON.stringify(optionalHostPermissions)) { fail('optional_host_permissions must contain only the explicit http(s) site opt-in patterns.'); } diff --git a/src/components/Flashcard.test.tsx b/src/components/Flashcard.test.tsx index ba1abfc2..812a203d 100644 --- a/src/components/Flashcard.test.tsx +++ b/src/components/Flashcard.test.tsx @@ -48,6 +48,30 @@ describe('Flashcard mobile controls', () => { expect(html).toContain('data-translation-reveal="true"'); }); + it('renders markdown emphasis in the memory hook', () => { + const html = renderToStaticMarkup( + , + ); + + expect(html).toContain('bờ'); + expect(html).not.toContain('**bờ**'); + }); + it('uses the same centered minimal action for returning to English', () => { const html = renderToStaticMarkup( -

- {card.mnemonic} -

+ ) : (
diff --git a/src/components/shell/AppFooter.test.tsx b/src/components/shell/AppFooter.test.tsx index 7374d2f0..c8700f7e 100644 --- a/src/components/shell/AppFooter.test.tsx +++ b/src/components/shell/AppFooter.test.tsx @@ -1,7 +1,11 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } from 'vitest'; import { AppFooter } from './AppFooter'; +const indexCssSource = readFileSync(fileURLToPath(new URL('../../index.css', import.meta.url)), 'utf8'); + describe('AppFooter', () => { it('renders online library status without announcing its decorative dot', () => { const html = renderToStaticMarkup( @@ -13,11 +17,17 @@ describe('AppFooter', () => { ); expect(html).toContain('LIBRARY: 12 WORDS'); + expect(html).toContain('app-footer-bar'); expect(html).toContain('STATUS:'); expect(html).toContain('Online'); expect(html).toContain('aria-hidden="true"'); }); + it('gives the library status strip a raised surface and a visible edge', () => { + expect(indexCssSource).toMatch(/\.app-footer-bar\s*\{[^}]*border-top:\s*1px\s+solid\s+var\(--sf-border\)/s); + expect(indexCssSource).toMatch(/\.app-footer-bar\s*\{[^}]*background:\s*var\(--sf-surface-raised\)/s); + }); + it('preserves the cache fallback labels', () => { const offlineHtml = renderToStaticMarkup( +