125 lines
5.4 KiB
Bash
Executable File
125 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# The Shortcuts page is generated from the compositor, so it cannot drift from
|
|
# the real keymap. Two things have to hold for that to be true:
|
|
#
|
|
# * every bind Hyprland reports is presented -- the page count matches
|
|
# `hyprctl binds -j` exactly, so adding a bind cannot silently go missing;
|
|
# * every bind carries a description. Hyprland reports Lua-defined binds with
|
|
# dispatcher "__lua" and a bytecode offset as the argument, so a bind
|
|
# without a description has nothing a person could read beside its chord.
|
|
# The service drops those rather than showing a mystery row, which means an
|
|
# undescribed bind disappears from the page -- this contract is what stops
|
|
# that from being a silent loss.
|
|
|
|
set -euo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
harness="$repo_dir/config/dot/quickshell/keybinds-harness.qml"
|
|
settings_dir="$repo_dir/config/dot/quickshell/modules/settings"
|
|
page="$settings_dir/ShortcutsPage.qml"
|
|
row="$settings_dir/ShortcutRow.qml"
|
|
|
|
fail() {
|
|
printf 'keybinds contract: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# ── How the hundred and thirty are presented ─────────────────────────────────
|
|
#
|
|
# The count check below proves the page HAS every bind. These prove it is still
|
|
# something a person can find one in. A wall of a hundred and thirty rows and a
|
|
# searchable browser pass the count check identically.
|
|
|
|
[[ -r "$page" ]] || fail "cannot read $page"
|
|
[[ -r "$row" ]] || fail "cannot read $row -- the shortcut row component is gone"
|
|
browser="$(cat "$page" "$row")"
|
|
|
|
grep -Fq 'Keybinds.grouped()' <<<"$browser" \
|
|
|| fail 'the page no longer renders the service grouping, so the group order is a second opinion'
|
|
|
|
# Chords are drawn as keys. The chord string itself stays the service's -- the
|
|
# component is presentation over what Keybinds already produced, never a second
|
|
# spelling of a binding.
|
|
grep -Fq 'KeycapChord' <<<"$browser" \
|
|
|| fail 'chords are no longer drawn as keycaps'
|
|
|
|
# A filter, matching what a shortcut does rather than what it is bound to:
|
|
# nobody looking for the screenshot key knows it is Super+Shift+S, which is the
|
|
# entire reason for searching.
|
|
grep -Fq 'toLowerCase()' <<<"$browser" \
|
|
|| fail 'the shortcut filter is gone, or is case-sensitive'
|
|
grep -qE 'description[^\n]*toLowerCase|toLowerCase[^\n]*description' <<<"$browser" \
|
|
|| fail 'the filter does not match a shortcut by its description'
|
|
|
|
# Filtering must say what it hid. A list that silently shrinks reads as a
|
|
# shortcut having been lost.
|
|
grep -qi 'showing' <<<"$browser" \
|
|
|| fail 'a filtered list never says how many of how many it is showing'
|
|
|
|
# Counts come from the service, so adding a bind moves the number on the page.
|
|
grep -Fq 'Keybinds.binds.length' <<<"$browser" \
|
|
|| fail 'the header count is not read from the live keymap'
|
|
|
|
# The reason there is no GNOME keyboard button here, kept where the next person
|
|
# to wonder about it will look. gnome-handoff-contract cannot state a reason.
|
|
rg -Fq 'writes org.gnome.desktop input-source' "$page" \
|
|
|| fail 'the note explaining why there is no GNOME keyboard handoff is gone'
|
|
|
|
qs_for_harness() {
|
|
qs -p "$harness" "$@"
|
|
}
|
|
|
|
cleanup() {
|
|
qs_for_harness kill >/dev/null 2>&1 || true
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# ── Every bind in the running compositor must be describable ─────────────────
|
|
total="$(hyprctl -j binds | jq 'length')"
|
|
undescribed="$(hyprctl -j binds | jq '[.[] | select((.description // "") == "")] | length')"
|
|
|
|
[[ "$total" -gt 0 ]] || fail 'the compositor reports no binds at all'
|
|
|
|
if [[ "$undescribed" -ne 0 ]]; then
|
|
printf 'keybinds contract: %s bind(s) have no description and would be dropped from the page:\n' "$undescribed" >&2
|
|
hyprctl -j binds | jq -r '.[] | select((.description // "") == "") | " modmask=\(.modmask) key=\(.key)"' >&2
|
|
fail 'add a description to each in config/dot/hypr/keybinds.lua'
|
|
fi
|
|
|
|
# ── The page must present all of them ────────────────────────────────────────
|
|
qs_for_harness --daemonize >/dev/null
|
|
for _ in $(seq 1 40); do
|
|
qs_for_harness ipc show 2>/dev/null | rg -q '^target keybinds-test$' && break
|
|
sleep 0.1
|
|
done
|
|
qs_for_harness ipc show 2>/dev/null | rg -q '^target keybinds-test$' || fail 'test IPC target did not start'
|
|
|
|
state='{}'
|
|
for _ in $(seq 1 40); do
|
|
state="$(qs_for_harness ipc call keybinds-test status | jq -c .)"
|
|
jq -e '.loaded == true' <<<"$state" >/dev/null 2>&1 && break
|
|
sleep 0.1
|
|
done
|
|
|
|
jq -e '.loaded == true' <<<"$state" >/dev/null || fail "the keymap never loaded: $state"
|
|
|
|
presented="$(jq -r .count <<<"$state")"
|
|
[[ "$presented" == "$total" ]] \
|
|
|| fail "the page presents $presented of $total binds — the two must match exactly"
|
|
|
|
grouped_total="$(jq -r .groupedCount <<<"$state")"
|
|
[[ "$grouped_total" == "$total" ]] \
|
|
|| fail "grouping lost binds: $grouped_total grouped from $total"
|
|
|
|
# ── Chords must be rendered for people, not dumped raw ───────────────────────
|
|
jq -e '.sample | test("^Super \\+ ")' <<<"$state" >/dev/null \
|
|
|| fail "Super+T did not render as a readable chord: $(jq -r .sample <<<"$state")"
|
|
|
|
[[ "$(jq -r .emptyDescriptions <<<"$state")" == "0" ]] \
|
|
|| fail 'a presented bind has an empty description'
|
|
|
|
trap - EXIT
|
|
cleanup
|
|
printf 'keybinds contract: PASS (%s binds, all described)\n' "$total"
|