Unify input methods with the installer keyboard selection
This commit is contained in:
1 parent
402128b6a2
commit
9fe6a04715
34 files changed
+1207
-31
No files matched your search
Executable
+128
@@ -0,0 +1,128 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
||||
|
||||
require_command lua
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
|
||||
# Load the JIS bindings on a jp layout and fire one of them in a fake window,
|
||||
# printing what it hands to Hyprland. TEST_FCITX5_CONFIG is the fcitx5 config,
|
||||
# or unset for none.
|
||||
fire() {
|
||||
local keys="$1" class="$2" tags="${3-}"
|
||||
local home="$test_dir/home"
|
||||
|
||||
rm -rf "$home"
|
||||
mkdir -p "$home/.config/fcitx5"
|
||||
if [[ -v TEST_FCITX5_CONFIG ]]; then
|
||||
printf '%s\n' "$TEST_FCITX5_CONFIG" >"$home/.config/fcitx5/config"
|
||||
fi
|
||||
|
||||
if [[ ${TEST_KEYBOARD_ONLY:-false} == "true" ]]; then
|
||||
printf '[Groups/0/Items/0]\nName=keyboard-us\n' > "$home/.config/fcitx5/profile"
|
||||
else
|
||||
printf '[Groups/0/Items/0]\nName=keyboard-us\n[Groups/0/Items/1]\nName=mozc\n' > "$home/.config/fcitx5/profile"
|
||||
fi
|
||||
|
||||
HOME="$home" XDG_CONFIG_HOME="$home/.config" OMARCHY_PATH="$ROOT" \
|
||||
TEST_KEYS="$keys" TEST_CLASS="$class" TEST_TAGS="$tags" TEST_LAYOUT="${TEST_LAYOUT:-jp}" lua <<'LUA'
|
||||
package.path = os.getenv("OMARCHY_PATH") .. "/?.lua;" .. package.path
|
||||
|
||||
local real_open = io.open
|
||||
io.open = function(path, mode)
|
||||
if path == "/etc/vconsole.conf" then
|
||||
local file = io.tmpfile()
|
||||
file:write("XKBLAYOUT=" .. os.getenv("TEST_LAYOUT") .. "\n")
|
||||
file:seek("set")
|
||||
return file
|
||||
end
|
||||
|
||||
return real_open(path, mode)
|
||||
end
|
||||
|
||||
local binds = {}
|
||||
o = {
|
||||
bind = function(keys, description, dispatcher, options)
|
||||
binds[keys] = { dispatcher = dispatcher, options = options or {} }
|
||||
end,
|
||||
}
|
||||
|
||||
local window = { class = os.getenv("TEST_CLASS"), tags = {} }
|
||||
for tag in os.getenv("TEST_TAGS"):gmatch("[^,]+") do
|
||||
table.insert(window.tags, tag)
|
||||
end
|
||||
|
||||
hl = {
|
||||
dsp = {
|
||||
send_key_state = function(key)
|
||||
return ("send %s %s %s"):format(key.mods, key.key, key.state)
|
||||
end,
|
||||
},
|
||||
dispatch = function(action) print(action) end,
|
||||
timer = function(callback) callback() end,
|
||||
get_active_window = function() return window end,
|
||||
exec_cmd = function(command) print("exec " .. command) end,
|
||||
}
|
||||
|
||||
o.shell_succeeds = function(command) print("exec " .. command) end
|
||||
|
||||
require("default.hypr.bindings.input-method")
|
||||
require("default.hypr.bindings.jis")
|
||||
|
||||
local bind = binds[os.getenv("TEST_KEYS")]
|
||||
if bind.options.non_consuming then
|
||||
print("non_consuming")
|
||||
end
|
||||
bind.dispatcher()
|
||||
LUA
|
||||
}
|
||||
|
||||
assert_fires() {
|
||||
local description="$1" expected="$2"
|
||||
shift 2
|
||||
local actual
|
||||
|
||||
actual=$(fire "$@")
|
||||
[[ $actual == "$expected" ]] ||
|
||||
fail "$description" "expected: $expected"$'\n'"actual: $actual"
|
||||
pass "$description"
|
||||
}
|
||||
|
||||
zoom="CTRL + SHIFT + semicolon"
|
||||
assert_fires "the JIS + key zooms in with Ctrl + keypad plus" \
|
||||
$'send CTRL KP_Add down\nsend CTRL KP_Add up' "$zoom" "chromium"
|
||||
assert_fires "the JIS + key zooms in windows without a class" \
|
||||
$'send CTRL KP_Add down\nsend CTRL KP_Add up' "$zoom" ""
|
||||
assert_fires "the JIS + key zooms Obsidian in with Ctrl + ^" \
|
||||
$'send CTRL asciicircum down\nsend CTRL asciicircum up' "$zoom" "obsidian"
|
||||
assert_fires "the JIS + key zooms the Obsidian Flatpak in with Ctrl + ^" \
|
||||
$'send CTRL asciicircum down\nsend CTRL asciicircum up' "$zoom" "md.obsidian.Obsidian"
|
||||
|
||||
# The prefix bind has to pass Ctrl + Space on, or tmux and Herdr never see it.
|
||||
prefix="CTRL + SPACE"
|
||||
freed=$'[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n\n[Hotkey/ActivateKeys]\n0=Henkan'
|
||||
TEST_FCITX5_CONFIG=$freed assert_fires "the terminal prefix switches to direct input once Ctrl + Space no longer toggles" \
|
||||
$'non_consuming\nexec timeout 0.3 fcitx5-remote --check -c' "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
TEST_FCITX5_CONFIG=$freed assert_fires "the prefix leaves input alone outside terminals" \
|
||||
"non_consuming" "$prefix" "chromium" "browser"
|
||||
|
||||
# While fcitx5 still toggles on Ctrl + Space, switching to direct input would
|
||||
# undo the toggle it just made.
|
||||
assert_fires "the prefix leaves input alone without an fcitx5 config" \
|
||||
"non_consuming" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
TEST_FCITX5_CONFIG=$'[Behavior]\nShareInputState=No' assert_fires "the prefix leaves input alone with the default trigger keys" \
|
||||
"non_consuming" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
TEST_FCITX5_CONFIG=$'[Hotkey/TriggerKeys]\n0=Zenkaku_Hankaku\n1=Control+space' assert_fires "the prefix leaves input alone while Ctrl + Space is a trigger key" \
|
||||
"non_consuming" "$prefix" "com.mitchellh.ghostty" "terminal*"
|
||||
|
||||
for layout in us kr; do
|
||||
TEST_LAYOUT="$layout" TEST_FCITX5_CONFIG=$freed assert_fires "the terminal prefix works with $layout keyboards too" \
|
||||
$'non_consuming\nexec timeout 0.3 fcitx5-remote --check -c' "$prefix" "foot" "terminal"
|
||||
done
|
||||
|
||||
TEST_KEYBOARD_ONLY=true TEST_FCITX5_CONFIG=$freed assert_fires "keyboard-only profiles skip the terminal controller call" \
|
||||
"non_consuming" "$prefix" "foot" "terminal"
|
||||
@@ -13,6 +13,15 @@ local vconsole = os.getenv("OMARCHY_VCONSOLE")
|
||||
local real_open = io.open
|
||||
|
||||
io.open = function(path, mode)
|
||||
if path == "/etc/omarchy/input-method" then
|
||||
local preference = os.getenv("TEST_INPUT_PREFERENCE")
|
||||
if not preference then return nil end
|
||||
local file = io.tmpfile()
|
||||
file:write(preference)
|
||||
file:seek("set")
|
||||
return file
|
||||
end
|
||||
|
||||
if path ~= "/etc/vconsole.conf" then
|
||||
return real_open(path, mode)
|
||||
end
|
||||
@@ -128,3 +137,5 @@ sddm_layouts=$(sed -n '/^local non_latin_layouts =/,+1p' "$sddm_lua" | grep -o '
|
||||
[[ $hooks_layouts == "$sddm_layouts" ]] ||
|
||||
fail "greeter non-latin layout list stays in sync" "$(diff <(echo "$hooks_layouts") <(echo "$sddm_layouts"))"
|
||||
pass "greeter non-latin layout list stays in sync with the initramfs hook"
|
||||
|
||||
TEST_INPUT_PREFERENCE=$'INPUT_METHOD=hangul\n XKB_LAYOUT="kr" # desktop only\n' assert_input "Korean selection overrides desktop layout and clears the US variant" "[kr] [] [$base_options]" $'XKBLAYOUT=us\nXKBVARIANT=intl\n'
|
||||
@@ -0,0 +1,251 @@
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
ROOT = Path(os.environ["OMARCHY_PATH"])
|
||||
spec = importlib.util.spec_from_file_location("input_setup", ROOT / "default/input-methods/configure.py")
|
||||
setup = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(setup)
|
||||
|
||||
|
||||
class InputMethodTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temporary.cleanup)
|
||||
self.home = Path(self.temporary.name)
|
||||
self.config = self.home / "config"
|
||||
self.vconsole = self.home / "vconsole"
|
||||
self.preference = self.home / "preference"
|
||||
self.vconsole.write_text("XKBLAYOUT=us\n")
|
||||
self.preference.write_text("INPUT_METHOD=none\n")
|
||||
env = {"XDG_CONFIG_HOME": str(self.config), "XDG_DATA_HOME": str(self.home / "data"), "OMARCHY_VCONSOLE": str(self.vconsole), "OMARCHY_INPUT_SELECTION": str(self.preference)}
|
||||
self.environment = patch.dict(os.environ, env)
|
||||
self.environment.start()
|
||||
self.addCleanup(self.environment.stop)
|
||||
self.process = patch.object(setup.subprocess, "run", return_value=subprocess.CompletedProcess([], 1, stdout="", stderr=""))
|
||||
self.process.start()
|
||||
self.addCleanup(self.process.stop)
|
||||
|
||||
def seed(self, method=None):
|
||||
setup.configure(argparse.Namespace(method=method, seed=True, defaults=False))
|
||||
|
||||
def test_every_installer_engine_is_seeded_and_inactive(self):
|
||||
for method in ("mozc", "hangul", "pinyin", "chewing"):
|
||||
with self.subTest(method=method):
|
||||
profile = self.config / "fcitx5/profile"
|
||||
profile.unlink(missing_ok=True)
|
||||
self.preference.write_text(f"INPUT_METHOD={method}\nXKB_LAYOUT={'kr' if method == 'hangul' else ''}\n")
|
||||
self.seed()
|
||||
data = setup.sections(profile.read_text())
|
||||
self.assertEqual(data["Groups/0"]["DefaultIM"], method)
|
||||
self.assertEqual(data["Groups/0/Items/1"]["Name"], method)
|
||||
self.assertEqual(data["Groups/0/Items/0"]["Name"], "keyboard-kr" if method == "hangul" else "keyboard-us")
|
||||
keys = setup.sections((self.config / "fcitx5/config").read_text())
|
||||
self.assertEqual(keys["Behavior"]["ActiveByDefault"], "False")
|
||||
self.assertNotIn("Control+space", keys["Hotkey/TriggerKeys"].values())
|
||||
self.assertIn("Control+Shift+space", keys["Hotkey/TriggerKeys"].values())
|
||||
|
||||
def test_vconsole_variants_quotes_and_japanese_without_preference(self):
|
||||
self.preference.unlink()
|
||||
self.vconsole.write_text('XKBLAYOUT="jp" # JIS\n')
|
||||
self.assertEqual(setup.selection(), ("mozc", "jp"))
|
||||
self.vconsole.write_text("XKBLAYOUT='de'\nXKBVARIANT=nodeadkeys\n")
|
||||
self.seed()
|
||||
self.assertIn("Default Layout=de-nodeadkeys", (self.config / "fcitx5/profile").read_text())
|
||||
self.vconsole.write_text("XKBLAYOUT=ru\nXKBVARIANT=phonetic\n")
|
||||
self.assertEqual(setup.selection(), ("none", "us"))
|
||||
|
||||
def test_rerun_preserves_custom_multilingual_profile(self):
|
||||
self.seed("mozc")
|
||||
profile = self.config / "fcitx5/profile"
|
||||
original = profile.read_bytes() + b"\n# custom configuration\n"
|
||||
profile.write_bytes(original)
|
||||
self.seed("hangul")
|
||||
self.assertEqual(profile.read_bytes(), original)
|
||||
|
||||
def test_stock_profile_is_repaired_but_custom_single_keyboard_is_not(self):
|
||||
self.seed()
|
||||
self.vconsole.write_text("XKBLAYOUT=fr\n")
|
||||
self.seed()
|
||||
profile = self.config / "fcitx5/profile"
|
||||
self.assertIn("keyboard-fr", profile.read_text())
|
||||
self.seed("mozc")
|
||||
self.assertNotIn("Name=mozc", profile.read_text())
|
||||
|
||||
def test_existing_shortcuts_and_addon_options_are_preserved(self):
|
||||
path = self.config / "fcitx5/config"
|
||||
setup.atomic_write(path, "# user's keys\n[Hotkey/TriggerKeys]\n0=Alt+space\n\n[Behavior]\nActiveByDefault=True\n")
|
||||
quickphrase = self.config / "fcitx5/conf/quickphrase.conf"
|
||||
setup.atomic_write(quickphrase, "[TriggerKey]\n\n")
|
||||
setup.defaults(self.config, fresh=True)
|
||||
config = setup.sections(path.read_text())
|
||||
self.assertEqual(config["Hotkey/TriggerKeys"], {"0": "Alt+space"})
|
||||
self.assertEqual(config["Behavior"], {"ActiveByDefault": "True"})
|
||||
self.assertEqual(quickphrase.read_text(), "[TriggerKey]\n\n")
|
||||
|
||||
def test_generated_stock_shortcuts_are_repaired(self):
|
||||
path = self.config / "fcitx5/config"
|
||||
setup.atomic_write(path, "[Hotkey/TriggerKeys]\n0=Control+space\n1=Zenkaku_Hankaku\n2=Hangul\n\n[Hotkey/EnumerateGroupForwardKeys]\n0=Super+space\n")
|
||||
setup.defaults(self.config, fresh=True)
|
||||
config = setup.sections(path.read_text())
|
||||
self.assertEqual(config["Hotkey/EnumerateGroupForwardKeys"], {})
|
||||
self.assertNotIn("Control+space", config["Hotkey/TriggerKeys"].values())
|
||||
|
||||
def test_live_append_keeps_every_method_and_layout(self):
|
||||
info = json.dumps({"data": ["de-nodeadkeys", [["keyboard-de-nodeadkeys", ""], ["mozc", "jp"]]]})
|
||||
updated = json.dumps({"data": ["de-nodeadkeys", [["keyboard-de-nodeadkeys", ""], ["mozc", "jp"], ["hangul", ""]]]})
|
||||
with patch.object(setup, "run", side_effect=["Work", info, "", "", "Work", updated]) as calls:
|
||||
setup.live_append("hangul")
|
||||
self.assertEqual(calls.call_args_list[2].args[0][-10:], ["ssa(ss)", "Work", "de-nodeadkeys", "3", "keyboard-de-nodeadkeys", "", "mozc", "jp", "hangul", ""])
|
||||
self.assertEqual(calls.call_args_list[3].args[0][-1], "Save")
|
||||
info = json.dumps({"data": ["us", [["keyboard-us", ""], ["hangul", ""]]]})
|
||||
with patch.object(setup, "run", side_effect=["Default", info]) as calls:
|
||||
setup.live_append("hangul")
|
||||
self.assertEqual(calls.call_count, 2)
|
||||
|
||||
def test_seed_in_a_live_desktop_is_a_harmless_retry(self):
|
||||
self.seed()
|
||||
profile = self.config / "fcitx5/profile"
|
||||
original = profile.read_bytes()
|
||||
with patch.object(setup.subprocess, "run", return_value=subprocess.CompletedProcess([], 0)):
|
||||
self.seed("mozc")
|
||||
self.assertEqual(profile.read_bytes(), original)
|
||||
|
||||
def test_failed_seed_rolls_back_so_it_can_be_retried(self):
|
||||
with patch.object(setup, "defaults", side_effect=OSError("disk full")):
|
||||
with self.assertRaises(OSError):
|
||||
self.seed("mozc")
|
||||
self.assertFalse((self.config / "fcitx5/profile").exists())
|
||||
self.seed("mozc")
|
||||
self.assertIn("Name=mozc", (self.config / "fcitx5/profile").read_text())
|
||||
|
||||
def test_failed_live_setup_restores_disk_and_restarts_the_daemon(self):
|
||||
self.seed()
|
||||
profile = self.config / "fcitx5/profile"
|
||||
original = profile.read_bytes()
|
||||
events = []
|
||||
def append(method):
|
||||
profile.write_text("failed in-memory state")
|
||||
raise RuntimeError("save failed")
|
||||
def command(args):
|
||||
events.append(args)
|
||||
if args == ["omarchy-restart-xcompose"] and len(events) > 2:
|
||||
self.assertEqual(profile.read_bytes(), original)
|
||||
return ""
|
||||
probes = 0
|
||||
def process(args, **kwargs):
|
||||
nonlocal probes
|
||||
if args[0] == "pgrep":
|
||||
probes += 1
|
||||
return subprocess.CompletedProcess([], 0 if probes == 1 else 1)
|
||||
return subprocess.CompletedProcess([], 0)
|
||||
with patch.object(setup.subprocess, "run", side_effect=process), patch.object(setup, "run", side_effect=command), patch.object(setup, "wait_ready"), patch.object(setup, "live_append", side_effect=append), patch.object(setup.time, "sleep"):
|
||||
with self.assertRaisesRegex(RuntimeError, "save failed"):
|
||||
setup.configure(argparse.Namespace(method="mozc", seed=False, defaults=False))
|
||||
self.assertEqual(profile.read_bytes(), original)
|
||||
self.assertIn(["systemctl", "--user", "stop", "omarchy-fcitx5.service"], events)
|
||||
self.assertEqual(events.count(["omarchy-restart-xcompose"]), 2)
|
||||
|
||||
def test_readiness_checks_the_dbus_array_payload_and_group(self):
|
||||
available = json.dumps({"data": [[["mozc", "Mozc", "", "", "", "ja", False]]]})
|
||||
with patch.object(setup, "run", return_value=available), patch.object(setup, "live_group", return_value=("Default", "us", [["keyboard-us", ""]])):
|
||||
setup.wait_ready("mozc")
|
||||
|
||||
def test_a_silently_dropped_engine_is_rejected_and_group_restored(self):
|
||||
old = ("Default", "us", [["keyboard-us", ""]])
|
||||
with patch.object(setup, "live_group", side_effect=[old, old]), patch.object(setup, "live_set") as setter:
|
||||
with self.assertRaisesRegex(RuntimeError, "retain"):
|
||||
setup.live_append("mozc")
|
||||
self.assertEqual(setter.call_args_list[-1].args, old)
|
||||
|
||||
def test_migration_without_a_bus_keeps_written_activation_and_defaults(self):
|
||||
self.seed()
|
||||
with patch.object(setup, "run", side_effect=subprocess.CalledProcessError(1, "busctl")):
|
||||
setup.configure(argparse.Namespace(method=None, seed=False, defaults=True))
|
||||
self.assertTrue((self.home / "data/dbus-1/services/org.fcitx.Fcitx5.service").exists())
|
||||
|
||||
def test_live_migration_without_a_bus_does_not_restart_or_rewrite_profile(self):
|
||||
self.seed()
|
||||
profile = self.config / "fcitx5/profile"
|
||||
original = profile.read_bytes()
|
||||
with patch.object(setup.subprocess, "run", return_value=subprocess.CompletedProcess([], 0)), patch.object(setup, "run", side_effect=subprocess.CalledProcessError(1, "busctl")) as calls:
|
||||
setup.configure(argparse.Namespace(method=None, seed=False, defaults=True))
|
||||
self.assertEqual(profile.read_bytes(), original)
|
||||
self.assertFalse(any(call.args[0] == ["omarchy-restart-xcompose"] for call in calls.call_args_list))
|
||||
|
||||
def test_migration_does_not_add_an_engine_the_old_daemon_cannot_load(self):
|
||||
self.seed()
|
||||
self.vconsole.write_text("XKBLAYOUT=jp\n")
|
||||
self.preference.write_text("INPUT_METHOD=mozc\n")
|
||||
old = ("Default", "us", [["keyboard-us", ""]])
|
||||
new = ("Default", "jp", [["keyboard-jp", ""]])
|
||||
with patch.object(setup.subprocess, "run", return_value=subprocess.CompletedProcess([], 0)), patch.object(setup, "live_group", side_effect=[old, new]), patch.object(setup, "available_methods", return_value=[]), patch.object(setup, "live_set") as setter:
|
||||
setup.configure(argparse.Namespace(method=None, seed=False, defaults=True))
|
||||
self.assertEqual(setter.call_args.args, new)
|
||||
|
||||
def test_custom_keyboard_overrides_are_not_stock(self):
|
||||
self.assertFalse(setup.stock_profile(setup.profile_text("de", "none").replace("keyboard-de", "keyboard-us")))
|
||||
self.assertFalse(setup.stock_profile(setup.profile_text("us", "none").replace("Layout=\n", "Layout=ru\n")))
|
||||
|
||||
def test_selected_live_engine_is_verified_after_the_context_reconnects(self):
|
||||
self.seed("mozc")
|
||||
profile = self.config / "fcitx5/profile"
|
||||
saves = 0
|
||||
def command(args):
|
||||
nonlocal saves
|
||||
if args == setup.CONTROLLER + ["Save"]:
|
||||
saves += 1
|
||||
if saves == 2:
|
||||
profile.write_text(profile.read_text().replace("DefaultIM=mozc", "DefaultIM=hangul"))
|
||||
return "Default" if args == ["fcitx5-remote", "-q"] else ""
|
||||
with patch.object(setup, "run", side_effect=command) as commands, patch.object(setup.time, "sleep"):
|
||||
setup.select_live_method("hangul", profile)
|
||||
self.assertEqual(saves, 2)
|
||||
self.assertEqual(sum(call.args[0] == ["fcitx5-remote", "-c"] for call in commands.call_args_list), 2)
|
||||
|
||||
def test_live_setup_does_not_validate_an_unused_install_layout(self):
|
||||
self.seed("mozc")
|
||||
self.vconsole.write_text("XKBLAYOUT=invalid value\n")
|
||||
with patch.object(setup.subprocess, "run", return_value=subprocess.CompletedProcess([], 0)), patch.object(setup, "run", return_value=""), patch.object(setup, "wait_ready"), patch.object(setup, "live_append"), patch.object(setup, "select_live_method"):
|
||||
setup.configure(argparse.Namespace(method="hangul", seed=False, defaults=False))
|
||||
|
||||
def test_live_configuration_requires_a_desktop(self):
|
||||
with self.assertRaisesRegex(RuntimeError, "Log in"):
|
||||
setup.configure(argparse.Namespace(method="mozc", seed=False, defaults=False))
|
||||
self.assertFalse((self.config / "fcitx5/profile").exists())
|
||||
|
||||
def test_atomic_updates_keep_symlinks_and_modes(self):
|
||||
target = self.home / "real-config"
|
||||
target.write_text("before")
|
||||
target.chmod(0o600)
|
||||
link = self.home / "link"
|
||||
link.symlink_to(target)
|
||||
setup.atomic_write(link, "after")
|
||||
self.assertTrue(link.is_symlink())
|
||||
self.assertEqual(target.read_text(), "after")
|
||||
self.assertEqual(target.stat().st_mode & 0o777, 0o600)
|
||||
|
||||
def test_input_preferences_are_data_and_invalid_values_fail(self):
|
||||
self.preference.write_text("INPUT_METHOD=$(touch /tmp/should-not-exist)\n")
|
||||
with self.assertRaises(ValueError):
|
||||
self.seed()
|
||||
|
||||
def test_font_fallback_does_not_replace_existing_preference(self):
|
||||
setup.font_default(self.config, "mozc")
|
||||
font = self.config / "fontconfig/conf.d/50-omarchy-input-method.conf"
|
||||
self.assertIn("Noto Sans CJK JP", font.read_text())
|
||||
self.assertIn("Noto Sans Mono CJK JP", font.read_text())
|
||||
original = font.read_bytes()
|
||||
setup.font_default(self.config, "hangul")
|
||||
self.assertEqual(font.read_bytes(), original)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
OMARCHY_PATH="$ROOT" python "$ROOT/test/shell.d/input-method-checks.py"
|
||||
@@ -6,11 +6,12 @@ work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
mkdir -p "$work/bin" "$work/home"
|
||||
export TEST_CALLS="$work/calls"
|
||||
for command in omarchy-pkg-add omarchy-hook-install systemctl; do
|
||||
for command in omarchy-pkg-add omarchy-hook-install omarchy-input-method systemctl; do
|
||||
cat >"$work/bin/$command" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >>"$TEST_CALLS"
|
||||
case "$*" in
|
||||
'configure --defaults') [[ ${TEST_INPUT_FAIL:-0} == 0 ]] ;;
|
||||
'--user enable owed.service') [[ ${TEST_TTY:-0} == 0 ]] ;;
|
||||
'--user is-active --quiet graphical-session.target') [[ ${TEST_TTY:-0} == 0 ]] ;;
|
||||
'--user start owed.service') [[ ${TEST_START_FAIL:-0} == 0 ]] ;;
|
||||
@@ -58,6 +59,12 @@ grep -E '^systemctl --user enable --now .*owed.service' "$TEST_CALLS" >/dev/null
|
||||
grep -Fx 'omarchy-hook-install theme-set /usr/share/owe/10-owe-sync' "$TEST_CALLS" >/dev/null
|
||||
pass "fresh installs enable OWE and install the theme refresh hook"
|
||||
|
||||
: >"$TEST_CALLS"
|
||||
HOME="$work/home" PATH="$work/bin:$PATH" TEST_INPUT_FAIL=1 bash "$ROOT/install/user/first-run/enable-user-units.sh"
|
||||
grep -Fx 'omarchy-input-method configure --defaults' "$TEST_CALLS" >/dev/null
|
||||
grep -E '^systemctl --user enable --now .*owed.service' "$TEST_CALLS" >/dev/null
|
||||
pass "failed input defaults do not block enabling first-run services"
|
||||
|
||||
cat >"$work/bin/ffmpegthumbnailer" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'thumbnail\n' >>"$TEST_CALLS"
|
||||
|
||||
@@ -63,6 +63,8 @@ trap 'if [[ ${FUNCNAME[0]:-} == "$PROMPT_FN" ]]; then printf "returned\n" >>"$MA
|
||||
|
||||
printf 'keyboard=%s\n' "${keyboard:-}"
|
||||
printf 'keyboard_label=%s\n' "${keyboard_label:-}"
|
||||
printf 'keyboard_input_method=%s\n' "${keyboard_input_method:-}"
|
||||
printf 'keyboard_xkb_layout=%s\n' "${keyboard_xkb_layout:-}"
|
||||
printf 'username=%s\n' "${username:-}"
|
||||
printf 'password=%s\n' "${password:-}"
|
||||
printf 'password_confirmation=%s\n' "${password_confirmation:-}"
|
||||
@@ -133,6 +135,19 @@ assert_status 0 "keyboard prompt succeeds"
|
||||
grep -qF -- '--selected English (US)' "$GUM_ARGS" || fail "keyboard prompt preselects English (US)"
|
||||
pass "keyboard prompt maps the chosen label to its keymap"
|
||||
|
||||
for selection in 'Japanese|jp106|mozc|' 'Japanese (US keyboard)|us|mozc|' 'Korean|us|hangul|kr' 'Chinese (Simplified, Pinyin)|us|pinyin|' 'Chinese (Traditional, Zhuyin)|us|chewing|'; do
|
||||
IFS='|' read -r label keymap method layout <<< "$selection"
|
||||
run_prompt omarchy_prompt_keyboard "0:$label"
|
||||
assert_status 0 "$label can be selected at install"
|
||||
[[ $(field keyboard) == "$keymap" && $(field keyboard_input_method) == "$method" && $(field keyboard_xkb_layout) == "$layout" ]] ||
|
||||
fail "$label carries a real console keymap and a separate input preference"
|
||||
done
|
||||
pass "language keyboard choices carry their keymap, input method, and desktop override"
|
||||
|
||||
keyboard_input_method=hangul keyboard_xkb_layout=kr omarchy_persist_input_selection "$tmp_dir/target"
|
||||
[[ $(<"$tmp_dir/target/etc/omarchy/input-method") == $'INPUT_METHOD=hangul\nXKB_LAYOUT=kr' ]] || fail "first-boot setup persists the selection for user finalization"
|
||||
pass "first-boot setup persists the selection for user finalization"
|
||||
|
||||
run_prompt omarchy_prompt_keyboard "1:"
|
||||
assert_status "$OMARCHY_FORM_BACK" "keyboard prompt reports Esc as back"
|
||||
assert_returned "keyboard prompt survives Esc under set -e"
|
||||
|
||||
@@ -71,7 +71,7 @@ grep -Fx 'WantedBy=graphical-session.target' "$fcitx_service" >/dev/null ||
|
||||
grep -Fx 'ConditionEnvironment=WAYLAND_DISPLAY' "$fcitx_service" >/dev/null ||
|
||||
fail "an update over SSH has a live user manager and no display; starting fcitx5 there wedges the unit active-but-blind, and Wants= will not replace it at graphical login"
|
||||
|
||||
grep -F 'pkill -x fcitx5' "$ROOT/bin/omarchy-restart-xcompose" >/dev/null ||
|
||||
grep -F 'pkill -u "$UID" -x fcitx5' "$ROOT/bin/omarchy-restart-xcompose" >/dev/null ||
|
||||
fail "restart-xcompose cannot reload a fcitx5 running outside the unit, so it silently keeps serving the old table"
|
||||
|
||||
grep -F 'omarchy-fcitx5.service' "$first_run_units" >/dev/null ||
|
||||
|
||||
Reference in new issue
Block a user