154 lines
9.5 KiB
Bash
Executable File
154 lines
9.5 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
|
|
|
run_node_test <<'JS'
|
|
const model = requireFromRoot('shell/plugins/bar/widgets/KeyboardLayoutModel.js')
|
|
|
|
const japanese = { methods: ['keyboard-us', 'mozc'], current: 'mozc', name: 'Mozc', language: 'ja' }
|
|
assertEqual(model.inputLabel(japanese, 'EN'), 'あ', 'Japanese input has a distinct active label')
|
|
assertEqual(model.inputLabel({ ...japanese, label: 'ア' }, 'EN'), 'ア', 'the live engine mode takes precedence over its default glyph')
|
|
assertEqual(model.inputLabel({ ...japanese, current: 'keyboard-us' }, 'EN'), 'EN', 'Latin input retains the keyboard label')
|
|
assertEqual(model.inputLabel({}, 'EN'), 'EN', 'a stopped input service retains the keyboard label')
|
|
assertEqual(model.inputLabel({ current: 'hangul' }, 'EN'), '한', 'Korean input has a distinct label')
|
|
assertEqual(model.inputLabel({ current: 'custom', language: 'vi' }, 'EN'), 'VI', 'other engines use their reported language')
|
|
assertEqual(model.inputTooltip(japanese, 'English (US)', false).includes('Space twice'), true, 'Japanese explains character choices')
|
|
assertEqual(model.inputTooltip(japanese, 'English (US)', false).includes('Ctrl + Shift + Space'), true, 'the input toggle shortcut is discoverable')
|
|
assertEqual(model.inputTooltip(japanese, 'English (US)', true).includes('Right-click'), true, 'combined input and layout switching stays discoverable')
|
|
assertEqual(model.inputTooltip({ methods: ['keyboard-fr'] }, 'French', false), 'French\nClick to set up an input language', 'one foreign mode offers input setup')
|
|
assertEqual(model.showIndicator('EN', false, false), false, 'English alone stays hidden')
|
|
assertEqual(model.showIndicator('ENG', false, false), false, 'English before the brief table loads stays hidden')
|
|
assertEqual(model.showIndicator('FR', false, false), true, 'a single non-English layout stays available')
|
|
assertEqual(model.showIndicator('EN', false, true), true, 'Latin with another input mode stays available')
|
|
assertEqual(model.showIndicator('EN', true, false), true, 'multiple keyboard layouts stay available')
|
|
|
|
// Trimmed from xkbcli list, keeping the format of every section it prints,
|
|
// including the options nested under an option group: those quote their
|
|
// description and print an empty brief, one indent deeper than a layout's.
|
|
const listing = [
|
|
'models:',
|
|
'- name: pc105',
|
|
' vendor: Generic',
|
|
' description: Generic 105-key PC',
|
|
'layouts:',
|
|
"- layout: 'us'",
|
|
" variant: ''",
|
|
" brief: 'en'",
|
|
' description: English (US)',
|
|
" iso639: ['eng']",
|
|
" iso3166: ['USA']",
|
|
"- layout: 'us'",
|
|
" variant: 'intl'",
|
|
" brief: 'en'",
|
|
' description: English (US, intl., with dead keys)',
|
|
"- layout: 'br'",
|
|
" variant: ''",
|
|
" brief: 'pt'",
|
|
' description: Portuguese (Brazil)',
|
|
"- layout: 'epo'",
|
|
" variant: ''",
|
|
" brief: 'eo'",
|
|
' description: Esperanto',
|
|
"- layout: 'latam'",
|
|
" variant: ''",
|
|
" brief: 'es'",
|
|
' description: Spanish (Latin American)',
|
|
"- layout: 'mm'",
|
|
" variant: 'zawgyi'",
|
|
" brief: 'my-zwg'",
|
|
' description: Burmese (Zawgyi)',
|
|
'option_groups:',
|
|
"- name: 'grp'",
|
|
' description: Switching to another layout',
|
|
' allows_multiple: true',
|
|
' options:',
|
|
" - name: 'grp:switch'",
|
|
" brief: ''",
|
|
" description: 'Right Alt (while pressed)'",
|
|
' layout-specific: false',
|
|
].join('\n')
|
|
|
|
const briefs = model.layoutBriefs(listing)
|
|
|
|
assertEqual(briefs['English (US)'], 'en', 'the table reads a layout brief')
|
|
assertEqual(briefs['English (US, intl., with dead keys)'], 'en', 'the table reads a variant brief')
|
|
assertEqual(briefs['Generic 105-key PC'], undefined, 'the table skips keyboard models')
|
|
assertEqual(briefs['Switching to another layout'], undefined, 'the table skips option groups')
|
|
assertEqual(briefs["'Right Alt (while pressed)'"], undefined, 'the table skips the options under a group')
|
|
assertEqual(Object.keys(briefs).length, 6, 'the table holds nothing but the layouts')
|
|
|
|
assertEqual(model.shortLabel('English (US)', briefs), 'EN', 'the label is the language, not the country')
|
|
assertEqual(model.shortLabel('Portuguese (Brazil)', briefs), 'PT', 'a country variant keeps its language')
|
|
assertEqual(model.shortLabel('Esperanto', briefs), 'EO', 'a layout without a country still gets a code')
|
|
assertEqual(model.shortLabel('Spanish (Latin American)', briefs), 'ES', 'a layout spanning countries still gets a code')
|
|
assertEqual(model.shortLabel('Burmese (Zawgyi)', briefs), 'MY', 'a brief carrying a script drops it')
|
|
assertEqual(model.shortLabel('A user-defined custom Layout', { 'A user-defined custom Layout': 'custom' }), 'CUS', 'a brief that is a word is cut to size')
|
|
|
|
// A brief pairs with the description printed under it, so a block that prints
|
|
// one without the other must not hand its code to the block that follows.
|
|
const orphaned = model.layoutBriefs([
|
|
'layouts:',
|
|
"- layout: 'us'",
|
|
" brief: 'en'",
|
|
"- layout: 'gr'",
|
|
' description: Greek',
|
|
].join('\n'))
|
|
|
|
assertEqual(orphaned['Greek'], undefined, 'a brief stops at the end of its block')
|
|
|
|
assertEqual(model.shortLabel('Elvish (Tengwar)', briefs), 'ELV', 'an unlisted layout falls back to its description')
|
|
assertEqual(model.shortLabel('English (US)', {}), 'ENG', 'the label survives an empty table')
|
|
assertEqual(model.shortLabel('constructor', {}), 'CON', 'a description naming a built-in still falls back')
|
|
assertEqual(model.shortLabel('', briefs), '', 'no keymap means no label')
|
|
|
|
// The seat as hyprctl reports it, virtual keyboards already filtered out: the
|
|
// buttons libinput calls keyboards sit beside the one being typed on, and the
|
|
// main flag lands on either, or on the virtual keyboard that isn't here.
|
|
const seat = (activeIndex, main) => [
|
|
{ name: 'power-button', active_layout_index: 0, active_keymap: 'English (US)', main: main === 'power-button' },
|
|
{ name: 'at-translated-set-2-keyboard', active_layout_index: activeIndex, active_keymap: activeIndex ? 'French' : 'English (US)', main: main === 'keyboard' },
|
|
]
|
|
|
|
assertEqual(model.selectKeyboard(seat(1)).active_keymap, 'French', 'the keyboard that switched is read when nothing holds main')
|
|
assertEqual(model.selectKeyboard(seat(1, 'power-button')).active_keymap, 'French', 'the keyboard that switched outranks a button holding main')
|
|
assertEqual(model.selectKeyboard(seat(0)).active_keymap, 'English (US)', 'before a switch every keyboard reads the same layout')
|
|
assertEqual(model.selectKeyboard(seat(0), 'at-translated-set-2-keyboard').name, 'at-translated-set-2-keyboard', 'the keyboard activelayout named is kept once it switches back')
|
|
assertEqual(model.selectKeyboard([{ name: 'a' }, { name: 'b' }]).name, 'a', 'a keyboard reporting no index still gets picked')
|
|
assertEqual(model.selectKeyboard([]), undefined, 'a seat with no typed keyboard picks none')
|
|
|
|
// Two real keyboards on the layout the seat started on, so nothing but the name
|
|
// says which one is being typed on. The one that wrapped from the last layout
|
|
// back to the first sits behind the other and is still the one that switched.
|
|
const pair = (activeIndex, otherIndex) => [
|
|
{ name: 'at-translated-set-2-keyboard', active_layout_index: otherIndex, active_keymap: otherIndex ? 'German' : 'English (US)' },
|
|
{ name: 'usb-keyboard', active_layout_index: activeIndex, active_keymap: activeIndex ? 'German' : 'English (US)' },
|
|
]
|
|
|
|
assertEqual(model.selectKeyboard(pair(0, 0), 'usb-keyboard').name, 'usb-keyboard', 'the keyboard that switched is read once it is named')
|
|
assertEqual(model.selectKeyboard(pair(0, 2), 'usb-keyboard').name, 'usb-keyboard', 'a keyboard that wrapped back to the first layout is still the one that switched')
|
|
assertEqual(model.selectKeyboard(pair(0, 2), 'gone-keyboard').name, 'at-translated-set-2-keyboard', 'a name no keyboard answers to falls back to layout progress')
|
|
|
|
// Hyprland calls the ACPI buttons keyboards too, and hands them the seat's
|
|
// layout list. Reading one would describe a layout nobody typed, and switching
|
|
// one would leave the keyboard where it was.
|
|
assertEqual(model.isTypedKeyboard('at-translated-set-2-keyboard'), true, 'a real keyboard is typed on')
|
|
assertEqual(model.isTypedKeyboard('power-button'), false, 'a power button is not')
|
|
assertEqual(model.isTypedKeyboard('lid-switch'), false, 'a lid switch is not')
|
|
assertEqual(model.isTypedKeyboard('sleep-button'), false, 'a sleep button is not')
|
|
assertEqual(model.isTypedKeyboard('hl-virtual-keyboard-1'), false, 'the keyboard an input method injects through is not')
|
|
assertEqual(model.isTypedKeyboard(''), true, 'a keyboard reporting no name is left where it was found')
|
|
|
|
// The activelayout event names the keyboard ahead of the layout it moved to,
|
|
// and a description with a comma in it has to survive the split.
|
|
const rawEvent = data => ({ data })
|
|
const parsedEvent = data => ({ data, parse: count => data.split(',', count - 1).concat(data.split(',').slice(count - 1).join(',')) })
|
|
|
|
assertEqual(model.eventKeyboardName(rawEvent('at-translated-set-2-keyboard,French')), 'at-translated-set-2-keyboard', 'the event names its keyboard')
|
|
assertEqual(model.eventKeyboardName(parsedEvent('at-translated-set-2-keyboard,English (US, intl.)')), 'at-translated-set-2-keyboard', 'a description carrying a comma leaves the name alone')
|
|
assertEqual(model.eventKeyboardName(rawEvent('hl-virtual-keyboard,English (US)')), '', 'the keyboard an input method injects through is not typed on')
|
|
assertEqual(model.eventKeyboardName({ parse: () => { throw new Error('unsupported') }, data: 'kb,French' }), 'kb', 'a binding without parse falls back to the raw data')
|
|
assertEqual(model.eventKeyboardName({}), '', 'an event with nothing in it names no keyboard')
|
|
JS
|