/** * Unit tests for keyboardLayout.ts * Run: node frontend/src/lib/util/keyboardLayout.test.js * * Tests exact Unicode character mappings for RU/EN QWERTY keyboard layout swap. * Critical: Latin 'c' ≠ Cyrillic 'с' (different Unicode codepoints). */ // Inline the module logic to avoid Svelte/TS build dependencies const RU_TO_EN = { й:'q', ц:'w', у:'e', к:'r', е:'t', н:'y', г:'u', ш:'i', щ:'o', з:'p', х:'[', ъ:']', ф:'a', ы:'s', в:'d', а:'f', п:'g', р:'h', о:'j', л:'k', д:'l', ж:';', э:"'", я:'z', ч:'x', с:'c', м:'v', и:'b', т:'n', ь:'m', ю:',', ё:'`', Й:'Q', Ц:'W', У:'E', К:'R', Е:'T', Н:'Y', Г:'U', Ш:'I', Щ:'O', З:'P', Х:'{', Ъ:'}', Ф:'A', Ы:'S', В:'D', А:'F', П:'G', Р:'H', О:'J', Л:'K', Д:'L', Ж:':', Э:'"', Я:'Z', Ч:'X', С:'C', М:'V', И:'B', Т:'N', Ь:'M', Ю:'<', Ё:'~', }; const EN_TO_RU = {}; for (const [ru, en] of Object.entries(RU_TO_EN)) EN_TO_RU[en] = ru; function swap(s, map) { let o=''; for(const c of s) o+=map[c]??c; return o; } function ruToEn(s) { return swap(s, RU_TO_EN); } function enToRu(s) { return swap(s, EN_TO_RU); } function expandKeyboardVariants(query) { const v = new Set([query]); const en = ruToEn(query); if(en!==query) v.add(en); const ru = enToRu(query); if(ru!==query) v.add(ru); for(const x of [...v]) v.add(x.toLowerCase()); return [...v]; } // ─── Test runner ───────────────────────────────────────────── let passed = 0, failed = 0; function assert(label, actual, expected) { if (actual === expected) { passed++; } else { failed++; console.error(`FAIL: ${label}`); console.error(` actual: ${JSON.stringify(actual)}`); console.error(` expected: ${JSON.stringify(expected)}`); } } function assertArray(label, actual, expected) { const a = JSON.stringify(actual.sort()); const e = JSON.stringify(expected.sort()); if (a === e) { passed++; } else { failed++; console.error(`FAIL: ${label}`); console.error(` actual: ${a}`); console.error(` expected: ${e}`); } } // ─── EN → RU: user typed Latin chars on Russian layout ────── console.log('=== EN → RU (latin on RU layout) ==='); assert('dthcnfr → верстак', enToRu('dthcnfr'), 'верстак'); assert('ghbdtn → привет', enToRu('ghbdtn'), 'привет'); assert('ntcn → тест', enToRu('ntcn'), 'тест'); // Verify each character mapping individually assert('d → в', enToRu('d'), 'в'); assert('t → е', enToRu('t'), 'е'); assert('h → р', enToRu('h'), 'р'); assert('c → с', enToRu('c'), 'с'); // Latin c → Cyrillic с (NOT ч!) assert('n → т', enToRu('n'), 'т'); assert('f → а', enToRu('f'), 'а'); assert('r → к', enToRu('r'), 'к'); // ─── RU → EN: user typed Cyrillic chars on English layout ─── console.log('=== RU → EN (cyrillic on EN layout) ==='); assert('руддщ → hello', ruToEn('руддщ'), 'hello'); assert('цщкдв → world', ruToEn('цщкдв'), 'world'); assert('ышеш → siti', ruToEn('ышеш'), 'siti'); // Verify each character mapping individually assert('р → h', ruToEn('р'), 'h'); assert('у → e', ruToEn('у'), 'e'); assert('д → l', ruToEn('д'), 'l'); assert('щ → o', ruToEn('щ'), 'o'); assert('ц → w', ruToEn('ц'), 'w'); assert('к → r', ruToEn('к'), 'r'); assert('в → d', ruToEn('в'), 'd'); // ─── Critical: Latin c vs Cyrillic с ──────────────────────── console.log('=== Unicode safety: Latin c ≠ Cyrillic с ==='); const latinC = 'c'; const cyrillicC = 'с'; // U+0441 assert('Latin c !== Cyrillic с', latinC === cyrillicC, false); assert('Latin c charCode', latinC.charCodeAt(0), 99); assert('Cyrillic с charCode', cyrillicC.charCodeAt(0), 1089); // 0x0441 assert('enToRu(c) → с (Cyrillic)', enToRu('c'), 'с'); assert('enToRu(c) is Cyrillic с', enToRu('c').charCodeAt(0), 1089); assert('ruToEn(с) → c (Latin)', ruToEn('с'), 'c'); assert('ruToEn(с) is Latin c', ruToEn('с').charCodeAt(0), 99); // ─── expandKeyboardVariants ───────────────────────────────── console.log('=== expandKeyboardVariants ==='); assertArray('dthcnfr variants', expandKeyboardVariants('dthcnfr'), ['dthcnfr', 'верстак']); assertArray('руддщ variants', expandKeyboardVariants('руддщ'), ['руддщ', 'hello']); assertArray('верстак variants', expandKeyboardVariants('верстак'), ['верстак', 'dthcnfr']); assertArray('hello variants', expandKeyboardVariants('hello'), ['hello', 'руддщ']); // Screen contains chars that map to RU: S→Ы, c→с, r→к, e→у, n→т assertArray('Screen variants (EN+RU swap)', expandKeyboardVariants('Screen'), ['Screen', 'screen', 'Ыскуут', 'ыскуут']); // ─── Edge cases ───────────────────────────────────────────── console.log('=== Edge cases ==='); assert('empty string', enToRu(''), ''); assert('single char d', enToRu('d'), 'в'); assert('already correct RU', ruToEn('верстак'), 'dthcnfr'); assert('already correct EN', enToRu('hello'), 'руддщ'); assert('mixed case EN: DthCnFr → ВерСтАк', enToRu('DthCnFr'), 'ВерСтАк'); assert('numbers pass through', enToRu('123'), '123'); assert('spaces pass through', enToRu('dth cnfr'), 'вер стак'); // ─── Summary ───────────────────────────────────────────────── console.log(); console.log(`Results: ${passed} passed, ${failed} failed`); if (failed > 0) { process.exit(1); } else { console.log('ALL TESTS PASSED'); }