Files
Panama/tests/quickshell/lock-screen-helper-contract
T
Gabriel Brown f8f5b25510 Make Shell a category, the bar legible, and the dock a real dock
Desktop & Dock becomes Shell — Bar, Dock, Control Center, Tiling,
Workspaces — the home for everything Quickshell draws. The settings-
management cluster moves to System as Sync & Backup, Appearance's
Shell tab dissolves, and 24-hour time finally lives on Date & Time,
which always owned it.

The bar gets what it never had: a way to survive the wallpaper. A
second neutral text family (follow theme, or forced light or dark),
a one-layer shadow under every glyph, and a gradient scrim for
wallpapers nothing else survives — all off by default, pixel-identical
until asked. Widgets earn toggles (weather, media, clipboard, calendar
countdown), the vitals cluster stops leaving a dead pill behind, and
Control Center's sections learn to step aside.

The dock graduates from MVP: a context menu with window rows, pin,
unpin, quit and new-window; scroll an icon to cycle its windows; drag
to reorder on the dock itself; hover previews with one-shot captures;
and "Add App to Dock" in the launcher. Three real bugs died en route —
menus that slid away with the autohide, a readonly-property crash on
every menu open, and a drag that drifted half a slot per icon on side
docks. The pinned-apps editor in Settings becomes a drag strip.

166 contracts; the full suite is green except two live display and
switcher tests that cannot run behind a locked session — re-verified
on unlock.

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
2026-08-24 04:28:20 -04:00

212 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
helper="$repo_dir/config/dot/quickshell/scripts/panama-lock"
schema="$repo_dir/config/dot/quickshell/config/PreferenceSchema.qml"
fixture="$(mktemp -d /tmp/panama-lock-helper.XXXXXX)"
fixture_home="$fixture/home"
config_home="$fixture/config"
state_home="$fixture/state"
test_bin="$fixture/bin"
settings="$config_home/panama/settings.json"
generated="$state_home/panama/hyprlock.conf"
hyprlock_log="$fixture/hyprlock.log"
theme_helper="$repo_dir/config/dot/quickshell/scripts/panama-theme-apps"
shipped_wallpaper="$fixture_home/Pictures/Wallpapers/faroe_islands.jpg"
global_wallpaper="$fixture_home/Pictures/Wallpapers/global.jpg"
portrait_wallpaper="$fixture_home/Pictures/Wallpapers/portrait.jpg"
fail() {
printf 'lock screen helper contract: %s\n' "$1" >&2
exit 1
}
cleanup() {
chmod 700 "$state_home/panama" 2>/dev/null || true
rm -rf "$fixture"
}
trap cleanup EXIT
mkdir -p "$fixture_home/Pictures/Wallpapers" "$config_home/panama" \
"$config_home/hypr" "$state_home" "$test_bin"
printf 'shipped wallpaper\n' >"$shipped_wallpaper"
printf 'global wallpaper\n' >"$global_wallpaper"
printf 'portrait wallpaper\n' >"$portrait_wallpaper"
cp "$repo_dir/config/dot/hypr/hyprlock.conf.template" \
"$config_home/hypr/hyprlock.conf.template"
HOME="$fixture_home" XDG_CONFIG_HOME="$config_home" \
"$theme_helper" dark >/dev/null
[[ -s "$config_home/hypr/hyprlock.conf" ]] \
|| fail 'the themed fallback renderer did not create hyprlock.conf'
cat >"$test_bin/hyprctl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
if [[ "${1:-}" == "-j" && "${2:-}" == "monitors" ]]; then
printf '%s\n' '[{"name":"DP-2"},{"name":"HDMI-A-1"}]'
exit 0
fi
exit 91
EOF
chmod +x "$test_bin/hyprctl"
cat >"$test_bin/hyprlock" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
printf '%s\n' "$*" >>"$PANAMA_TEST_HYPRLOCK_LOG"
EOF
chmod +x "$test_bin/hyprlock"
run_helper() {
HOME="$fixture_home" XDG_CONFIG_HOME="$config_home" XDG_STATE_HOME="$state_home" \
PATH="$test_bin:$PATH" PANAMA_TEST_HYPRLOCK_LOG="$hyprlock_log" "$helper" "$@"
}
write_settings() {
printf '%s\n' "$1" >"$settings"
}
assert_schema_entry() {
local key="$1"
rg -Fq "key: \"$key\"" "$schema" || fail "schema is missing $key"
}
[[ -x "$helper" ]] || fail 'panama-lock helper is missing or not executable'
for key in lockBackgroundMode lockBlurLevel lockShowClock lockShowDate lockShowUser lockFadeOnEmpty accentName; do
assert_schema_entry "$key"
done
write_settings '{}'
run_helper generate
[[ -s "$generated" ]] || fail 'default generation did not create a config'
rg -Fq 'path = screenshot' "$generated" || fail 'default background is not a screenshot'
rg -Fq 'blur_passes = 3' "$generated" || fail 'default blur pass count is not 3'
rg -Fq 'blur_size = 8' "$generated" || fail 'default blur size is not 8'
rg -Fq 'text = cmd[update:1000] date +"%-I:%M"' "$generated" || fail 'default clock is not 12-hour'
rg -Fq 'text = cmd[update:60000] date +"%A, %B %-d"' "$generated" || fail 'default date is missing'
rg -Fq 'text = $USER' "$generated" || fail 'default user label is missing'
rg -Fq 'fade_on_empty = false' "$generated" || fail 'default password field fades when empty'
jq -e '.generated == true and .fallback == false and .error == "" and (.path | endswith("/panama/hyprlock.conf"))' \
<<<"$(run_helper status)" >/dev/null || fail 'status did not report the generated config'
write_settings '{
"lockBackgroundMode":"solid",
"lockBlurLevel":0,
"lockShowClock":false,
"lockShowDate":false,
"lockShowUser":false,
"lockFadeOnEmpty":true,
"use24Hour":true,
"colorScheme":"light"
}'
run_helper generate
rg -Fq 'color = rgba(225, 226, 231, 1.0)' "$generated" || fail 'light solid mode does not match Theme.bg'
rg -Fq 'inner_color = rgba(217, 218, 227, 0.85)' "$generated" || fail 'light password field does not match the themed fallback'
rg -Fq 'font_color = rgba(55, 96, 191, 1.0)' "$generated" || fail 'light foreground does not match Theme.fg'
rg -Fq 'outer_color = rgba(46, 125, 233, 0.9)' "$generated" || fail 'light focus ring does not match Theme.accent'
rg -Fq 'check_color = rgba(46, 125, 233, 1.0)' "$generated" || fail 'light success color does not match Theme.accent'
rg -Fq 'fail_color = rgba(245, 42, 101, 1.0)' "$generated" || fail 'light error color does not match Theme.red'
rg -Fq 'foreground="##848cb5"' "$generated" || fail 'light placeholder markup retained the dark muted color'
rg -Fq 'foreground="##f52a65"' "$generated" || fail 'light failure markup retained the dark error color'
rg -Fq 'blur_passes = 0' "$generated" || fail 'blur level zero did not disable passes'
rg -Fq 'blur_size = 1' "$generated" || fail 'blur level zero did not use the safe size'
rg -Fq 'fade_on_empty = true' "$generated" || fail 'fade-on-empty setting was ignored'
if rg -q '^label \{' "$generated"; then
fail 'hidden clock, date, and user labels were still generated'
fi
# ── The focus ring follows the active theme's accent, not just the scheme ──
# A pinned blue literal only ever proves the DEFAULT accent renders correctly.
# Since themes carry their accent pair, the helper reads the active profile's
# accent; a custom orchid profile is used because its hex is nowhere close to
# blue's, so a helper that quietly kept emitting the default would be caught.
# (accentName alone can no longer disagree with the profile: every commit path
# recomputes it from the active accent.)
write_settings '{"colorScheme":"dark","accentName":"orchid","themeProfileId":"custom-orchid","themeProfiles":[{"id":"custom-orchid","name":"Orchid test","scheme":"dark","accent":"#c099ff","secondary":"#fca7ea","shipped":false}]}'
run_helper generate
rg -Fq 'outer_color = rgba(192, 153, 255, 0.9)' "$generated" || fail 'dark orchid accent did not drive the focus ring'
rg -Fq 'check_color = rgba(192, 153, 255, 1.0)' "$generated" || fail 'dark orchid accent did not drive the success color'
write_settings '{"colorScheme":"light","accentName":"orchid","themeProfileId":"custom-orchid-light","themeProfiles":[{"id":"custom-orchid-light","name":"Orchid light","scheme":"light","accent":"#7847bd","secondary":"#9854f1","shipped":false}]}'
run_helper generate
rg -Fq 'outer_color = rgba(120, 71, 189, 0.9)' "$generated" || fail 'light orchid accent did not drive the focus ring'
rg -Fq 'check_color = rgba(120, 71, 189, 1.0)' "$generated" || fail 'light orchid accent did not drive the success color'
write_settings '{"accentName":"not-a-real-accent","colorScheme":"dark"}'
run_helper generate
rg -Fq 'outer_color = rgba(130, 170, 255, 0.9)' "$generated" || fail 'an unknown accent name did not fall back to blue'
write_settings "$(jq -n \
--arg global "$global_wallpaper" \
--arg portrait "$portrait_wallpaper" \
'{lockBackgroundMode:"wallpaper",wallpaperMode:"per-monitor",wallpaperPath:$global,wallpaperPerMonitor:{"DP-2":$portrait}}')"
run_helper generate
[[ "$(rg -c '^background \{' "$generated")" -eq 2 ]] || fail 'wallpaper mode did not create one block per output'
awk -v path="$portrait_wallpaper" '/^background \{/{block++} block==1 && /monitor = DP-2/{monitor=1} block==1 && index($0, "path = " path){path_seen=1} END{exit !(monitor && path_seen)}' "$generated" \
|| fail 'per-monitor wallpaper was not used for DP-2'
awk -v path="$global_wallpaper" '/^background \{/{block++} block==2 && /monitor = HDMI-A-1/{monitor=1} block==2 && index($0, "path = " path){path_seen=1} END{exit !(monitor && path_seen)}' "$generated" \
|| fail 'missing monitor assignment did not fall back to the global wallpaper'
# A selected but unreadable image never reaches hyprlock. One bounded warning
# covers both missing global and per-monitor choices while the shipped image is
# still readable.
write_settings "$(jq -n \
--arg missing "$fixture/missing.jpg" \
'{lockBackgroundMode:"wallpaper",wallpaperMode:"per-monitor",wallpaperPath:$missing,wallpaperPerMonitor:{"DP-2":$missing}}')"
run_helper generate
[[ "$(rg -c "path = $shipped_wallpaper" "$generated")" -eq 2 ]] \
|| fail 'missing selected wallpapers did not fall back to the readable shipped image'
jq -e '.generated == true and .fallback == false
and .error == "One or more lock-screen wallpapers were unavailable; a safe fallback is in use."' \
<<<"$(run_helper status)" >/dev/null \
|| fail 'missing wallpaper fallback did not report one bounded warning'
# If no image is readable, screenshot is the only background source that
# hyprlock can still render safely.
rm -f "$shipped_wallpaper"
run_helper generate
[[ "$(rg -c 'path = screenshot' "$generated")" -eq 2 ]] \
|| fail 'missing wallpapers without the shipped image did not fall back to screenshots'
write_settings '{"lockBackgroundMode":"screenshot","lockShowClock":true,"lockShowDate":false,"lockShowUser":false,"use24Hour":true}'
run_helper generate
rg -Fq 'text = cmd[update:1000] date +"%H:%M"' "$generated" || fail '24-hour clock setting was ignored'
write_settings '{"lockBackgroundMode":"nope","lockBlurLevel":99,"lockShowClock":"yes","lockShowDate":0,"lockShowUser":null,"lockFadeOnEmpty":[]}'
run_helper generate
rg -Fq 'path = screenshot' "$generated" || fail 'invalid background mode did not use the default'
rg -Fq 'blur_passes = 3' "$generated" || fail 'invalid blur did not use the default'
rg -Fq 'text = cmd[update:1000] date +"%-I:%M"' "$generated" || fail 'invalid clock boolean did not use the default'
rg -Fq 'text = cmd[update:60000] date +"%A, %B %-d"' "$generated" || fail 'invalid date boolean did not use the default'
rg -Fq 'text = $USER' "$generated" || fail 'invalid user boolean did not use the default'
rg -Fq 'fade_on_empty = false' "$generated" || fail 'invalid fade boolean did not use the default'
write_settings 'not json'
run_helper generate
rg -Fq 'path = screenshot' "$generated" || fail 'malformed JSON did not use shipped defaults'
before_checksum="$(sha256sum "$generated" | cut -d' ' -f1)"
chmod 500 "$state_home/panama"
if run_helper generate >/dev/null 2>&1; then
fail 'generation unexpectedly succeeded with an unwritable state directory'
fi
after_checksum="$(sha256sum "$generated" | cut -d' ' -f1)"
[[ "$before_checksum" == "$after_checksum" ]] || fail 'failed generation replaced the last valid config'
chmod 700 "$state_home/panama"
: >"$hyprlock_log"
run_helper run
[[ "$(tail -1 "$hyprlock_log")" == "-c $generated" ]] || fail 'run did not use the generated config after success'
chmod 500 "$state_home/panama"
run_helper run 2>"$fixture/fallback.stderr"
chmod 700 "$state_home/panama"
[[ "$(tail -1 "$hyprlock_log")" == "-c $config_home/hypr/hyprlock.conf" ]] \
|| fail 'run did not use the tracked fallback after generation failure'
[[ ! -s "$fixture/fallback.stderr" ]] || fail 'safe fallback leaked an internal filesystem error'
printf 'lock screen helper contract: PASS\n'