diff --git a/config/dot/quickshell/displays-harness.qml b/config/dot/quickshell/displays-harness.qml index 37008bc..05d460c 100644 --- a/config/dot/quickshell/displays-harness.qml +++ b/config/dot/quickshell/displays-harness.qml @@ -32,7 +32,14 @@ ShellRoot { canConfirm: Displays.canConfirm, secondsLeft: Displays.secondsLeft, lastError: Displays.lastError, - overridden: monitor ? Displays.isOverridden(monitor.name) : false + overridden: monitor ? Displays.isOverridden(monitor.name) : false, + // The compositor being visually restored is not the service + // being done: revert verification keeps its own readback + // running for a few ticks, and busy blocks a new apply until + // it settles. A caller that only watched `awaiting` raced + // this and got a refusal with no error text. + settled: !Displays.busy && !Displays.revertVerificationActive + && !Displays.awaitingConfirmation }); } diff --git a/tests/quickshell/declared-dependencies-contract b/tests/quickshell/declared-dependencies-contract index 888722a..865c163 100755 --- a/tests/quickshell/declared-dependencies-contract +++ b/tests/quickshell/declared-dependencies-contract @@ -47,6 +47,15 @@ SESSION='^(systemctl|busctl|journalctl|loginctl|hostnamectl|localectl|systemd-in # Anything added here needs a matching install block and a stated reason. SELF_INSTALLED='^(bun|claude|node|npm|pnpm)$' +# Tools an alias may lean on without Panama installing them anywhere. The +# docker aliases serve the machines that run Docker by deliberate choice; +# Panama's container runtime is rootless podman (development-packages), and +# declaring docker in a list would put a second container daemon on every +# fresh machine to keep five aliases company. Where docker is absent the +# aliases fail by naming the missing command, which is the honest outcome. +# Anything added here needs that same property: absence must be loud. +OPTIONAL='^(docker)$' + # jq programs are quoted arguments, but the scanner is line-based and cannot # tell a filter from a command. `not` is a jq builtin appearing inside one. JQ_BUILTINS='^(not|empty|error|env|input|inputs)$' @@ -105,6 +114,7 @@ while read -r script; do [[ "$cmd" =~ $BASELINE ]] && continue [[ "$cmd" =~ $SESSION ]] && continue [[ "$cmd" =~ $SELF_INSTALLED ]] && continue + [[ "$cmd" =~ $OPTIONAL ]] && continue [[ "$cmd" =~ $JQ_BUILTINS ]] && continue pkg="$(package_for "$cmd")" diff --git a/tests/quickshell/displays-contract b/tests/quickshell/displays-contract index 793531e..3746676 100755 --- a/tests/quickshell/displays-contract +++ b/tests/quickshell/displays-contract @@ -309,7 +309,10 @@ target_scale=$(awk -v s="$original_scale" 'BEGIN { print (s == 1.25) ? 1.5 : 1.2 run ipc call displays-test revertChange >/dev/null immediate_reverted=false for _ in $(seq 1 60); do - if display_is_restored && [[ "$(status | jq -r .awaiting)" == "false" ]]; then + # settled, not awaiting: the compositor can look restored while the + # service's revert verification is still reading back, and an apply in + # that window is refused as busy. See the settled field in the harness. + if display_is_restored && [[ "$(status | jq -r .settled)" == "true" ]]; then immediate_reverted=true break fi @@ -345,6 +348,11 @@ done [[ "$(status | jq -r .overridden)" == "false" ]] || fail 'an unconfirmed change was written to the settings store' # ── A confirmed change is what writes ──────────────────────────────────────── +# Wait out the revert readback before applying again -- same race as above. +for _ in $(seq 1 40); do + [[ "$(status | jq -r .settled)" == "true" ]] && break + sleep 0.2 +done [[ "$(run ipc call displays-test applyScale "$target_scale")" == "true" ]] \ || fail 'the confirmed-change fixture could not apply' diff --git a/tests/quickshell/settings-system-contract b/tests/quickshell/settings-system-contract index ce50c80..b143cab 100755 --- a/tests/quickshell/settings-system-contract +++ b/tests/quickshell/settings-system-contract @@ -41,8 +41,19 @@ for _ in $(seq 1 40); do fi sleep 0.1 done -jq -e '.monitorName == "DP-2" and .width == 4500 and .height == 3000 and .scale == 1.5 and .format == "XRGB2101010"' <<<"$status" >/dev/null \ - || fail "live monitor data was not normalized: $status" +# Against the live compositor, not a named machine: this once asserted DP-2 at +# 4500x3000 in XRGB2101010, which pinned the desktop it was written on and +# could never pass on a laptop's eDP-1. The property is that SystemSettings +# mirrors whatever monitor is actually primary, normalized -- so ask Hyprland +# what that is. +live_monitor="$(hyprctl -j monitors | jq -c '.[0]')" +jq -e --argjson live "$live_monitor" ' + .monitorName == $live.name + and .width == $live.width + and .height == $live.height + and ((.scale - $live.scale) | fabs) < 0.001 + and (.format | length) > 0' <<<"$status" >/dev/null \ + || fail "live monitor data was not normalized: $status (compositor: $live_monitor)" [[ "$(qs_for_harness ipc call settings-system-test panelAllowed '__definitely_not_a_panel__' | jq -r .)" == "false" ]] \ || fail 'unsupported GNOME panel was accepted' diff --git a/tests/quickshell/ssh-keys-contract b/tests/quickshell/ssh-keys-contract index 60a2896..77e28c8 100755 --- a/tests/quickshell/ssh-keys-contract +++ b/tests/quickshell/ssh-keys-contract @@ -96,9 +96,18 @@ grep -q 'durableRemoval' "$helper" \ kind="$(printf '%s' "$state" | field "['agent'].get('kind','')")" if [[ "$kind" == "gnome-keyring" ]]; then - reason="$(printf '%s' "$("$helper" agent-remove "$HOME/.ssh/id_ed25519")" | field "['error']")" - [[ "$reason" == *"does not stick"* ]] \ - || fail "removal against a keyring agent was not refused with its reason (got: $reason)" + # Whichever key this machine actually has. This used to hardcode + # id_ed25519, which asserted the author's machine: any other key name + # earned "That key no longer exists" instead of the refusal under test. + # No key at all means the property cannot be exercised here, not that it + # failed. + real_key="$(compgen -G "$HOME/.ssh/id_*.pub" | head -1)" + real_key="${real_key%.pub}" + if [[ -n "$real_key" ]]; then + reason="$(printf '%s' "$("$helper" agent-remove "$real_key")" | field "['error']")" + [[ "$reason" == *"does not stick"* ]] \ + || fail "removal against a keyring agent was not refused with its reason (got: $reason)" + fi grep -q 'does not stick' "$page" \ || fail 'the page does not say that removing a key from this agent has no effect' fi diff --git a/tests/quickshell/switcher-contract b/tests/quickshell/switcher-contract index fecb7e3..5c44ceb 100755 --- a/tests/quickshell/switcher-contract +++ b/tests/quickshell/switcher-contract @@ -73,7 +73,12 @@ grep -q 'WlrKeyboardFocus.None' "$window" \ # ── Live ──────────────────────────────────────────────────────────────────── -if command -v qs >/dev/null 2>&1 && qs ipc call switcher cancel >/dev/null 2>&1; then +# Stepping refuses to open with fewer than two windows (see step() in the +# state service) -- that is designed behavior, not a failure, so a session +# with one window skips the live half rather than failing against it. +open_windows="$(hyprctl -j clients 2>/dev/null | jq length 2>/dev/null || echo 0)" +if command -v qs >/dev/null 2>&1 && (( open_windows >= 2 )) \ + && qs ipc call switcher cancel >/dev/null 2>&1; then qs ipc call switcher next >/dev/null 2>&1 sleep 0.6 mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"