- updater-fallback.mjs (82 assertions): GitHub probe fallback to Gitea, timeout budgets, in-flight check never stuck in 'checking'; updater.ts gains a setUpdateTimeouts test seam, electron-updater aliased to a stub - ipc-guard.mjs (43): trusted-frame guard exercised through real registerIpc handlers with forged senderFrames; electron-stub now records registrations via globalThis so bundle and test share one instance - log-sanitizer.mjs (71): CSI/OSC/charset state machine, alt-screen fold, byte-split fuzz equal to whole-chunk output; fixes a wrong comment - sftp-timeout.mjs (39): per-op timeouts (metadata 30s, transfer chunk 60s, open 10s) evict half-dead channels with one retry, slow-but-progressing transfers untouched, late rejections never unhandled - reservedAccelerators.ts: single pure isReservedAccelerator shared by the settings recorder and applyGlobalShortcut (the two tables had drifted — main now also refuses Ctrl+=/-/0/PgUp/PgDn legacy values); 56 assertions - ssh-loopback.mjs loads the real ssh.ts via a bundle (50 assertions): TOFU pinning, fail-closed stores, auth gate, connect budget Offline suite grows 13 -> 17. Renderer test framework evaluated: not introducing vitest/jsdom; pure logic keeps being extracted and tested through the existing bundle harness.
120 lines
4.0 KiB
JavaScript
120 lines
4.0 KiB
JavaScript
/**
|
|
* Reserved-accelerator self-test (reserved-accelerators.mjs).
|
|
*
|
|
* src/shared/reservedAccelerators.ts is the single table two guards read:
|
|
* - the settings recorder refuses to SAVE such a chord (SettingsTabs.tsx);
|
|
* - `applyGlobalShortcut` refuses to REGISTER one (main/globalShortcuts.ts).
|
|
* They used to be two independent tables with two spellings of the same rule,
|
|
* and only the renderer's had a test. What is pinned here:
|
|
* - the in-app chords: Ctrl+=/-/0 (font size) and Ctrl+PgUp/PgDn (tab cycle),
|
|
* under ANY extra modifiers — the in-app handlers ignore Shift/Alt, so a
|
|
* global registration of Ctrl+Shift+= would shadow them;
|
|
* - Ctrl+L, the panic lock, in every spelling a user or an older build can
|
|
* produce ('ctrl+l', 'Ctrl+L', 'Control+L', 'CommandOrControl+L');
|
|
* - everything else stays registrable, so the guard cannot grow into a
|
|
* blanket refusal.
|
|
*
|
|
* Build: node tests/build-bundles.cjs
|
|
* Run: node tests/reserved-accelerators.mjs (must exit 0)
|
|
*/
|
|
import { createRequire } from 'node:module'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const { isReservedAccelerator, acceleratorParts } = require('./.reserved-accelerators.cjs')
|
|
|
|
let failed = 0
|
|
const ok = (cond, msg) => {
|
|
console.log(` ${cond ? 'ok' : 'FAIL'}: ${msg}`)
|
|
if (!cond) failed += 1
|
|
}
|
|
|
|
const reserved = [
|
|
// --- the panic lock, every spelling ---------------------------------------
|
|
'Ctrl+L',
|
|
'ctrl+l',
|
|
'CTRL+L',
|
|
'Control+L',
|
|
'control+l',
|
|
'CommandOrControl+L',
|
|
'CmdOrCtrl+L',
|
|
'CommandOrCtrl+L',
|
|
'Ctrl + L', // Electron tolerates surrounding whitespace
|
|
// --- font size (Ctrl = / - / 0) under extra modifiers ---------------------
|
|
'Control+=',
|
|
'Control+-',
|
|
'Control+0',
|
|
'Control+Shift+=',
|
|
'Control+Shift+-',
|
|
'Control+Shift+0',
|
|
'Control+Alt+=',
|
|
'Control+Alt+Shift+0',
|
|
'ctrl+0',
|
|
// --- tab cycling (Ctrl+PgUp/PgDn) ----------------------------------------
|
|
'Control+PageUp',
|
|
'Control+PageDown',
|
|
'Control+Shift+PageUp',
|
|
'Control+Alt+PageDown',
|
|
'ctrl+pageup'
|
|
]
|
|
|
|
const allowed = [
|
|
// Not the panic chord: extra/missing modifiers change the meaning on purpose.
|
|
'Alt+L',
|
|
'Shift+L',
|
|
'Super+L',
|
|
'Control+Shift+L',
|
|
'Control+Alt+L',
|
|
'Control+Meta+L',
|
|
// The font/tab keys WITHOUT Control belong to whatever is focused.
|
|
'=',
|
|
'-',
|
|
'0',
|
|
'Shift+=',
|
|
'Alt+=',
|
|
'PageUp',
|
|
'PageDown',
|
|
'Shift+PageUp',
|
|
// Other Control chords are free (they do not collide with an in-app binding).
|
|
'Control+R',
|
|
'Control+Shift+I',
|
|
'Control+1',
|
|
'Control+PageHome',
|
|
'Control+F5',
|
|
'Alt+Control+P',
|
|
// Standalone function keys.
|
|
'F5',
|
|
'F12',
|
|
'Control+F12'
|
|
]
|
|
|
|
console.log('reserved (must be refused by both guards)')
|
|
for (const a of reserved) ok(isReservedAccelerator(a), `reserved: ${JSON.stringify(a)}`)
|
|
|
|
console.log('allowed (must stay registrable)')
|
|
for (const a of allowed) ok(!isReservedAccelerator(a), `allowed: ${JSON.stringify(a)}`)
|
|
|
|
console.log('modifier aliases fold to one canonical form')
|
|
ok(acceleratorParts('Ctrl+L').join('+') === 'control+l', "'Ctrl' folds to 'control'")
|
|
ok(acceleratorParts('CommandOrControl+L').join('+') === 'control+l', "'CommandOrControl' folds to 'control' (Windows-only app)")
|
|
ok(acceleratorParts('CmdOrCtrl+L').join('+') === 'control+l', "'CmdOrCtrl' folds to 'control'")
|
|
ok(acceleratorParts('Ctrl+Shift+P').join('+') === 'control+shift+p', 'Shift is folded to lower case and kept')
|
|
ok(acceleratorParts('Super+L')[0] === 'super', 'Super is preserved (not an alias of Control)')
|
|
|
|
console.log('degenerate input does not throw and grants nothing')
|
|
for (const a of ['', ' ', '+', 'Control+', '+L', 'nonsense']) {
|
|
let threw = false
|
|
let verdict
|
|
try {
|
|
verdict = isReservedAccelerator(a)
|
|
} catch {
|
|
threw = true
|
|
}
|
|
ok(!threw && verdict === false, `junk input refused without throwing: ${JSON.stringify(a)}`)
|
|
}
|
|
|
|
if (failed > 0) {
|
|
console.error(`\n[reserved-accelerators] ${failed} check(s) FAILED`)
|
|
process.exit(1)
|
|
}
|
|
console.log(`\n[reserved-accelerators] ALL CHECKS PASSED (${reserved.length + allowed.length + 4 + 6} assertions)`)
|