feat: add preferred STT languages and fix model settings layout

This commit is contained in:
2026-10-26 02:14:27 +03:00
parent 70977bad84
commit ce28d95bcf
13 changed files with 195 additions and 50 deletions
+63 -2
View File
@@ -4,7 +4,7 @@ import assert from "node:assert/strict";
import { afterEach, it } from "node:test";
import { createRoot, type Root } from "react-dom/client";
import { parseHTML } from "linkedom";
import { I18nProvider, LanguageSelect, useI18n } from "../components/i18n.tsx";
import { I18nProvider, LanguageSelect, SttPreferredLanguages, useI18n } from "../components/i18n.tsx";
import { MENU_CLOSE_EVENT, MENU_OPEN_EVENT } from "../components/menu-shell.tsx";
import { dictationUrl, translate, type GeneralSettings, type SttLanguage } from "../src/core/index.ts";
@@ -67,7 +67,7 @@ async function mount(initialGeneral: GeneralSettings = { language: "ru", sttLang
function Controls() {
recording = useI18n().setDictating;
resolvedSttLanguage = useI18n().sttLanguage;
return <><LanguageSelect field="language" /><LanguageSelect field="sttLanguage" /><LanguageSelect field="sttLanguage" compact /></>;
return <><LanguageSelect field="language" /><LanguageSelect field="sttLanguage" /><LanguageSelect field="sttLanguage" compact /><SttPreferredLanguages /></>;
}
await act(async () => { root!.render(<I18nProvider initialGeneral={general}><Controls /></I18nProvider>); });
}
@@ -201,3 +201,64 @@ it("serializes rapid picks in independent selectors without losing either prefer
assert.deepEqual(general, { language: "en", sttLanguage: "ru", autoUpdate: false });
assert.equal(writes.length, 2);
});
function preferred(language: "en" | "ru") { return element(`[data-testid="stt-preferred-${language}"]`) as HTMLButtonElement; }
it("saves RU + EN hints, restores them after reload, and retains the choices in single-language mode", async () => {
await mount();
assert(preferred("en").disabled); assert(preferred("ru").disabled);
assert.equal(writes.length, 0);
await click(trigger("sttLanguage")); await pick("Несколько языков");
assert(!preferred("en").disabled);
await click(preferred("en")); await click(preferred("ru"));
assert.deepEqual(general, { language: "ru", sttLanguage: "multi", sttPreferredLanguages: ["en", "ru"], autoUpdate: false });
assert.deepEqual(new URL(dictationUrl(resolvedSttLanguage, general.sttPreferredLanguages)).searchParams.getAll("language_hint"), ["en", "ru"]);
const saved = general;
await act(async () => root!.unmount()); root = null; host.remove();
await mount(saved);
assert.equal(preferred("en").getAttribute("aria-checked"), "true");
assert.equal(preferred("ru").getAttribute("aria-checked"), "true");
await click(trigger("sttLanguage", true)); await pick("English");
assert(preferred("en").disabled); assert(preferred("ru").disabled);
assert.deepEqual(general.sttPreferredLanguages, ["en", "ru"]);
assert.deepEqual(new URL(dictationUrl(resolvedSttLanguage, general.sttPreferredLanguages)).searchParams.getAll("language_hint"), []);
await click(trigger("sttLanguage")); await pick("Несколько языков");
assert.equal(preferred("en").getAttribute("aria-checked"), "true");
await click(preferred("en")); await click(preferred("ru"));
assert.deepEqual(general.sttPreferredLanguages, []);
});
it("locks preference edits while saving and recording, and reports a failed save without changing the choice", async () => {
await mount({ language: "ru", sttLanguage: "multi", autoUpdate: false });
failSave = true;
await click(preferred("en"));
assert.equal(preferred("en").getAttribute("aria-checked"), "false");
assert.equal(general.sttPreferredLanguages, undefined);
assert.equal(element('[role="alert"]').textContent, "Не удалось сохранить предпочитаемые языки");
failSave = false;
let release!: () => void;
const pending = new Promise<void>((resolve) => { release = resolve; }); holdSave = () => pending;
await click(preferred("en"));
assert(preferred("en").disabled); assert(preferred("ru").disabled);
await click(preferred("ru"));
assert.equal(writes.length, 2);
await act(async () => release()); holdSave = undefined;
assert.equal(element('[data-testid="stt-preferred-en"]').getAttribute("aria-checked"), "true");
assert.equal(document.querySelector('[role="alert"]'), null);
await act(async () => recording(true));
assert(preferred("en").disabled); assert(preferred("ru").disabled);
await click(preferred("en"));
assert.deepEqual(general.sttPreferredLanguages, ["en"]);
await act(async () => recording(false));
assert(!preferred("en").disabled);
});
it("serializes hint and interface-language saves without losing either preference", async () => {
await mount({ language: "ru", sttLanguage: "multi", autoUpdate: false });
let release!: () => void;
const pending = new Promise<void>((resolve) => { release = resolve; }); holdSave = () => pending;
await click(preferred("ru"));
await click(trigger("language")); await pick("English");
await act(async () => release());
assert.deepEqual(general, { language: "en", sttLanguage: "multi", sttPreferredLanguages: ["ru"], autoUpdate: false });
});