#!/usr/bin/env bats # tests/desk.bats — black-box tests for scripts/desk.sh # # desk.sh contract (see script header): subcommands list/current/switch/ # cycle/match/capture driven by env overrides DESK_CFG_DIR, DESK_NIRI, # DESK_NOTIFY. Everything observable is via stdout/stderr/exit status, # the display.kdl symlink, and the stub invocation log. setup() { TEST_TMP="$(mktemp -d)" CFG="$TEST_TMP/cfg" LOG="$TEST_TMP/stub.log" mkdir -p "$CFG" # canned `niri msg outputs` payload (live niri human format) cat > "$TEST_TMP/outputs.txt" <<'EOF' Output "Sceptre Tech Inc Sceptre M34 Unknown" (DP-2) Current mode: 3440x1440 @ 239.999 Hz (current) Output "Lenovo Group Limited 0x40A9 Unknown" (eDP-1) Current mode: 1920x1080 @ 60.001 Hz (current, preferred) EOF # stub niri: logs every invocation, serves canned outputs, # validate exit code controlled by $STUB_VALIDATE (default 0) cat > "$TEST_TMP/niri" <<'EOF' #!/usr/bin/env bash echo "niri $*" >> "$STUB_LOG" case "$*" in "msg outputs") cat "$STUB_OUTPUTS" ;; "validate") exit "${STUB_VALIDATE:-0}" ;; "msg action load-config-file") exit 0 ;; *) exit 0 ;; esac EOF chmod +x "$TEST_TMP/niri" # stub notifier: records what it was shown cat > "$TEST_TMP/notify" <<'EOF' #!/usr/bin/env bash echo "notify $*" >> "$STUB_LOG" EOF chmod +x "$TEST_TMP/notify" export STUB_LOG="$LOG" export STUB_OUTPUTS="$TEST_TMP/outputs.txt" export DESK_CFG_DIR="$CFG" export DESK_NIRI="$TEST_TMP/niri" export DESK_NOTIFY="$TEST_TMP/notify" DESK="$BATS_TEST_DIRNAME/../scripts/desk.sh" } teardown() { rm -rf "$TEST_TMP" } make_profiles() { cat > "$CFG/desk-1-home.kdl" <<'EOF' // requires: Sceptre Tech Inc Sceptre M34 Unknown // requires: Lenovo Group Limited 0x40A9 Unknown output "Sceptre Tech Inc Sceptre M34 Unknown" { scale 1 } EOF cat > "$CFG/desk-2-portable.kdl" <<'EOF' // requires: Sceptre Tech Inc Sceptre M34 Unknown // requires: PM1601 Portable Monitor Unknown output "Sceptre Tech Inc Sceptre M34 Unknown" { scale 1 } EOF # non-profile files must be ignored echo 'include "display.kdl"' > "$CFG/config.kdl" echo 'output "X" {}' > "$CFG/keybinds.kdl" } @test "list: shows desk profiles only, sorted" { make_profiles run "$DESK" list [ "$status" -eq 0 ] [ "$output" = "$(printf 'desk-1-home\ndesk-2-portable')" ] } @test "list: no profiles yields empty output" { run "$DESK" list [ "$status" -eq 0 ] [ -z "$output" ] } @test "current: reports none without symlink, profile with symlink" { make_profiles run "$DESK" current [ "$status" -eq 0 ] [ "$output" = "none" ] ln -sfn desk-1-home.kdl "$CFG/display.kdl" run "$DESK" current [ "$output" = "desk-1-home" ] } @test "switch: relinks, validates, reloads, notifies" { make_profiles run "$DESK" switch desk-1-home [[ "$output" == *"switched to desk-1-home"* ]] [ "$(basename "$(readlink "$CFG/display.kdl")")" = "desk-1-home.kdl" ] run cat "$LOG" [[ "$output" == *"niri validate"* ]] [[ "$output" == *"niri msg action load-config-file"* ]] [[ "$output" == *"notify Desk switched to desk-1-home"* ]] } @test "switch: accepts .kdl suffix" { make_profiles run "$DESK" switch desk-2-portable.kdl [ "$status" -eq 0 ] [ "$(basename "$(readlink "$CFG/display.kdl")")" = "desk-2-portable.kdl" ] } @test "switch: unknown profile fails without touching the link" { make_profiles ln -sfn desk-1-home.kdl "$CFG/display.kdl" run "$DESK" switch nonexistent [ "$status" -ne 0 ] [[ "$output" == *"no such profile"* ]] [ "$(basename "$(readlink "$CFG/display.kdl")")" = "desk-1-home.kdl" ] # validate must never have run for a missing profile run cat "$LOG" [[ "$output" != *"niri validate"* ]] } @test "switch: failed validate rolls the link back to the previous profile" { make_profiles ln -sfn desk-1-home.kdl "$CFG/display.kdl" export STUB_VALIDATE=1 run "$DESK" switch desk-2-portable [ "$status" -ne 0 ] [[ "$output" == *"validate failed"* ]] [ "$(basename "$(readlink "$CFG/display.kdl")")" = "desk-1-home.kdl" ] # no reload, no notification after a failed validate [[ "$output" != *"load-config-file"* ]] run cat "$LOG" [[ "$output" != *"notify"* ]] } @test "switch: failed validate with no previous profile removes the link" { make_profiles export STUB_VALIDATE=1 run "$DESK" switch desk-1-home [ "$status" -ne 0 ] [ ! -L "$CFG/display.kdl" ] } @test "match: picks the profile whose requires are all connected" { make_profiles run "$DESK" match [ "$status" -eq 0 ] [ "$output" = "desk-1-home" ] # dry run: must not touch the symlink [ ! -L "$CFG/display.kdl" ] } @test "match --apply: applies the winner" { make_profiles run "$DESK" match --apply [ "$status" -eq 0 ] [[ "$output" == *"switched to desk-1-home"* ]] [ "$(basename "$(readlink "$CFG/display.kdl")")" = "desk-1-home.kdl" ] } @test "match: no matching profile exits nonzero and keeps the link" { make_profiles ln -sfn desk-2-portable.kdl "$CFG/display.kdl" cat > "$TEST_TMP/outputs.txt" <<'EOF' Output "Some Other Monitor 0x1234 Unknown" (DP-9) EOF run "$DESK" match [ "$status" -ne 0 ] [[ "$output" == *"no desk profile matches"* ]] [ "$(basename "$(readlink "$CFG/display.kdl")")" = "desk-2-portable.kdl" ] } @test "match: prefers the profile with more matched requires" { # both profiles match the connected set; desk-1 requires 2, desk-2 requires 1 cat > "$CFG/desk-1-home.kdl" <<'EOF' // requires: Sceptre Tech Inc Sceptre M34 Unknown // requires: Lenovo Group Limited 0x40A9 Unknown EOF cat > "$CFG/desk-9-guest.kdl" <<'EOF' // requires: Sceptre Tech Inc Sceptre M34 Unknown EOF run "$DESK" match [ "$status" -eq 0 ] [ "$output" = "desk-1-home" ] } @test "cycle: advances to the next profile and wraps around" { make_profiles ln -sfn desk-1-home.kdl "$CFG/display.kdl" run "$DESK" cycle [ "$status" -eq 0 ] [[ "$output" == *"switched to desk-2-portable"* ]] run "$DESK" cycle [ "$status" -eq 0 ] [[ "$output" == *"switched to desk-1-home"* ]] } @test "cycle: with no active profile applies the first" { make_profiles run "$DESK" cycle [ "$status" -eq 0 ] [[ "$output" == *"switched to desk-1-home"* ]] } @test "cycle: no profiles at all fails cleanly" { run "$DESK" cycle [ "$status" -ne 0 ] [[ "$output" == *"no desk profiles"* ]] } @test "capture: prints requires lines for connected outputs" { run "$DESK" capture [ "$status" -eq 0 ] [[ "$output" == *"// requires: Sceptre Tech Inc Sceptre M34 Unknown"* ]] [[ "$output" == *"// requires: Lenovo Group Limited 0x40A9 Unknown"* ]] }