mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:07:04 +01:00
Phase 2 of the leisure rollout, dormant until daily-limits' cache says "leisure_active": true. gaming_budget.sh then labels the block "🛋 Leisure" (short "🛋", same colour logic, stale state keeps the label); leisure_shadow.sh prints nothing so i3bar hides it. A missing, false or non-boolean key keeps today's output. Also: gaming_budget.sh floors generated_at and says "no data" for a non-numeric one; a fractional timestamp used to make it exit 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dsh4Znwh2KnC7q6vbGpX1N
207 lines
8.9 KiB
Bash
Executable File
207 lines
8.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# Behaviour tests for the i3blocks gaming_budget block: it renders daily-limits'
|
|
# cache ($DAILY_LIMITS_CACHE). Covers text, colour thresholds, the not-done
|
|
# earner suffix (todo and unknown), the gaming-day note, the stale/missing/
|
|
# garbage states and the "re-parse only when the cache changes" contract.
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
REPO_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
|
BLOCK="$REPO_DIR/i3blocks/gaming_budget.sh"
|
|
TMP_DIR=$(mktemp -d)
|
|
|
|
cleanup() {
|
|
rm -rf "$TMP_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
fail() {
|
|
printf 'FAIL: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
assert_eq() {
|
|
local expected=$1
|
|
local actual=$2
|
|
local context=$3
|
|
if [[ "$expected" != "$actual" ]]; then
|
|
fail "$context (expected '$expected', actual '$actual')"
|
|
fi
|
|
}
|
|
|
|
printf '{}' | jq -e . >/dev/null 2>&1 ||
|
|
fail 'a working jq is required for the gaming_budget tests (pacman -S jq)'
|
|
|
|
CACHE="$TMP_DIR/daily-limits.json"
|
|
RENDER="$TMP_DIR/render"
|
|
NOW=1790000000
|
|
|
|
# write_cache USED BUDGET GENERATED_AT GAMING_DAY EARNER... where EARNER is
|
|
# NAME:STATUS:MINUTES. USED may be "null".
|
|
write_cache() {
|
|
local used=$1 budget=$2 generated=$3 day=$4
|
|
shift 4
|
|
local rows=() name status minutes spec
|
|
for spec in "$@"; do
|
|
IFS=: read -r name status minutes <<<"$spec"
|
|
rows+=("{\"name\":\"$name\",\"label\":\"$name\",\"status\":\"$status\",\"shutdown_minutes\":$minutes,\"gaming_minutes\":$minutes}")
|
|
done
|
|
local IFS=,
|
|
printf '{"date":"2026-10-09","generated_at":%s,"shutdown":{"applied":"20:00","resolved":"20:00","floor":"20:00","ceiling":"23:00"},"gaming":{"budget_minutes":%s,"used_minutes":%s,"ceiling_minutes":480,"day":"%s"},"earners":[%s],"todo":[]}\n' \
|
|
"$generated" "$budget" "$used" "$day" "${rows[*]}" >"$CACHE"
|
|
rm -f "$RENDER" # force a re-render for the new content
|
|
}
|
|
run_block() {
|
|
NOW_EPOCH="$NOW" DAILY_LIMITS_CACHE="$CACHE" DAILY_LIMITS_RENDER_CACHE="$RENDER" bash "$BLOCK"
|
|
}
|
|
|
|
line() {
|
|
run_block | sed -n "${1}p"
|
|
}
|
|
|
|
D=2026-10-09
|
|
|
|
printf 'Checking the normal day: time left, used/total, not-done bonus...\n'
|
|
write_cache 360 480 "$NOW" "$D" workout:done:120 leetcode:done:60 reading:todo:60
|
|
assert_eq '🎮 2h00 left · 6h00/8h00 +📖1h' "$(line 1)" 'full text'
|
|
assert_eq '🎮 2h00' "$(line 2)" 'short text'
|
|
assert_eq '#50FA7B' "$(line 3)" 'green above 1h'
|
|
|
|
printf 'Checking colour thresholds follow the enforcer warn_at...\n'
|
|
write_cache 430 480 "$NOW" "$D" workout:done:120
|
|
assert_eq '#F1FA8C' "$(line 3)" 'yellow at <=1h'
|
|
assert_eq '🎮 0h50 left · 7h10/8h00' "$(line 1)" 'no suffix when every bonus is earned'
|
|
write_cache 460 480 "$NOW" "$D"
|
|
assert_eq '#FFB86C' "$(line 3)" 'orange at <=30m'
|
|
write_cache 475 480 "$NOW" "$D"
|
|
assert_eq '#FF5555' "$(line 3)" 'red at <=10m'
|
|
|
|
printf 'Checking the blocked state...\n'
|
|
write_cache 300 300 "$NOW" "$D" workout:todo:120 leetcode:todo:60 reading:todo:60
|
|
assert_eq '🎮 BLOCKED · 5h00/5h00 +💪2h +🧩1h +📖1h' "$(line 1)" 'blocked text lists what can still be earned'
|
|
assert_eq '🎮 BLOCKED' "$(line 2)" 'blocked short text'
|
|
assert_eq '#FF5555' "$(line 3)" 'blocked is red'
|
|
write_cache 302 300 "$NOW" "$D"
|
|
assert_eq '🎮 BLOCKED' "$(line 2)" 'overspent counts as blocked'
|
|
|
|
printf 'Checking earner icons, labels, minutes and the unknown marker...\n'
|
|
write_cache 0 240 "$NOW" "$D" workout:done:120 leetcode:todo:60 reading:todo:60 anki:todo:30 automation:todo:30 piano:todo:90
|
|
assert_eq '🎮 4h00 left · 0h00/4h00 +🧩1h +📖1h +🗂30m +⚙30m +piano1h30' "$(line 1)" \
|
|
'cache order, done omitted, label for an unmapped earner'
|
|
write_cache 0 240 "$NOW" "$D" leetcode:unknown:60 anki:unknown:30 reading:done:60
|
|
assert_eq '🎮 4h00 left · 0h00/4h00 +🧩?1h +🗂?30m' "$(line 1)" \
|
|
'unknown earners are shown with a ? marker, not hidden'
|
|
|
|
printf 'Checking used_minutes null shows only the budget...\n'
|
|
write_cache null 180 "$NOW" "$D" leetcode:todo:60
|
|
assert_eq '🎮 budget 3h00 +🧩1h' "$(line 1)" 'budget only'
|
|
assert_eq '🎮 3h00' "$(line 2)" 'budget only short text'
|
|
|
|
printf 'Checking the gaming day shows only when it differs from the date...\n'
|
|
write_cache 60 180 "$NOW" 2026-10-08 leetcode:todo:60
|
|
assert_eq '🎮 2h00 left · 1h00/3h00 (10-08) +🧩1h' "$(line 1)" 'previous gaming day noted'
|
|
write_cache 60 180 "$NOW" "$D" leetcode:todo:60
|
|
assert_eq '🎮 2h00 left · 1h00/3h00 +🧩1h' "$(line 1)" 'same day: no note'
|
|
|
|
# set_live VALUE -- rewrite the cache's top-level leisure_active (Phase 2).
|
|
set_live() {
|
|
jq -c ".leisure_active = $1" "$CACHE" >"$CACHE.new" && mv "$CACHE.new" "$CACHE"
|
|
rm -f "$RENDER"
|
|
}
|
|
|
|
printf 'Checking the leisure budget, once live, is labelled 🛋 Leisure...\n'
|
|
write_cache 360 480 "$NOW" "$D" workout:done:120 reading:todo:60
|
|
set_live true
|
|
assert_eq '🛋 Leisure 2h00 left · 6h00/8h00 +📖1h' "$(line 1)" 'live full text'
|
|
assert_eq '🛋 2h00' "$(line 2)" 'live short text'
|
|
assert_eq '#50FA7B' "$(line 3)" 'live keeps the colour logic (green)'
|
|
assert_eq '3' "$(run_block | wc -l)" 'live emits exactly full/short/colour'
|
|
write_cache 460 480 "$NOW" "$D"
|
|
set_live true
|
|
assert_eq '#FFB86C' "$(line 3)" 'live keeps the colour logic (orange)'
|
|
write_cache 300 300 "$NOW" "$D" leetcode:todo:60
|
|
set_live true
|
|
assert_eq '🛋 Leisure BLOCKED · 5h00/5h00 +🧩1h' "$(line 1)" 'live blocked text'
|
|
assert_eq '🛋 BLOCKED' "$(line 2)" 'live blocked short text'
|
|
assert_eq '#FF5555' "$(line 3)" 'live blocked is red'
|
|
write_cache null 180 "$NOW" "$D"
|
|
set_live true
|
|
assert_eq '🛋 Leisure budget 3h00' "$(line 1)" 'live budget only'
|
|
assert_eq '🛋 3h00' "$(line 2)" 'live budget only short text'
|
|
write_cache 60 180 "$((NOW - 181))" "$D"
|
|
set_live true
|
|
assert_eq '🛋 Leisure ⏱? stale 3m' "$(line 1)" 'live stale keeps the leisure label'
|
|
assert_eq '🛋 ⏱?' "$(line 2)" 'live stale short text'
|
|
for value in false null '"yes"'; do
|
|
write_cache 60 180 "$NOW" "$D"
|
|
set_live "$value"
|
|
assert_eq '🎮 2h00 left · 1h00/3h00' "$(line 1)" "leisure_active $value is not live"
|
|
done
|
|
printf '%s\n%s\n%s\n%s\n' "$NOW" '🎮 old render' '🎮 old' '#50FA7B' >"$RENDER"
|
|
touch -d '+1 min' "$RENDER"
|
|
assert_eq '🎮 old render' "$(line 1)" 'a 4-line render from before the label line still shows'
|
|
|
|
printf 'Checking a stale or missing cache never shows numbers as fresh...\n'
|
|
write_cache 60 180 "$((NOW - 181))" "$D"
|
|
assert_eq '🎮 ⏱? stale 3m' "$(line 1)" 'older than 180 s'
|
|
assert_eq '#FFB86C' "$(line 3)" 'stale is orange'
|
|
write_cache 60 180 "$((NOW - 180))" "$D"
|
|
assert_eq '🎮 2h00 left · 1h00/3h00' "$(line 1)" '180 s is still fresh'
|
|
rm -f "$CACHE"
|
|
assert_eq '🎮 ⏱? no data' "$(line 1)" 'missing cache'
|
|
assert_eq '🎮 ⏱?' "$(line 2)" 'missing cache short text'
|
|
|
|
printf 'Checking an odd generated_at never crashes the block (always exit 0)...\n'
|
|
write_cache 60 180 "$NOW.75" "$D"
|
|
assert_eq '🎮 2h00 left · 1h00/3h00' "$(line 1)" 'a fractional generated_at is a timestamp'
|
|
for generated in null '"soon"'; do
|
|
write_cache 60 180 "$generated" "$D"
|
|
set +e
|
|
output=$(run_block 2>&1)
|
|
status=$?
|
|
set -e
|
|
assert_eq '0' "$status" "generated_at $generated still exits 0"
|
|
assert_eq '🎮 ⏱? no data' "$(sed -n 1p <<<"$output")" "generated_at $generated says no data"
|
|
done
|
|
|
|
printf 'Checking an unreadable cache is red...\n'
|
|
printf 'not json' >"$CACHE"
|
|
rm -f "$RENDER"
|
|
assert_eq '🎮 unreadable cache' "$(line 1)" 'garbage body'
|
|
assert_eq '#FF5555' "$(line 3)" 'garbage is red'
|
|
|
|
printf 'Checking jq runs only when the cache file changes...\n'
|
|
write_cache 60 180 "$NOW" "$D"
|
|
mkdir -p "$TMP_DIR/bin"
|
|
printf '#!/bin/bash\necho x >>"%s/jq.calls"\nexec %s "$@"\n' "$TMP_DIR" "$(command -v jq)" >"$TMP_DIR/bin/jq"
|
|
chmod +x "$TMP_DIR/bin/jq"
|
|
PATH="$TMP_DIR/bin:$PATH" run_block >/dev/null
|
|
PATH="$TMP_DIR/bin:$PATH" run_block >/dev/null
|
|
assert_eq '1' "$(wc -l <"$TMP_DIR/jq.calls")" 'two ticks, one unchanged cache: one jq run'
|
|
sleep 0.05
|
|
touch "$CACHE"
|
|
PATH="$TMP_DIR/bin:$PATH" run_block >/dev/null
|
|
assert_eq '2' "$(wc -l <"$TMP_DIR/jq.calls")" 'a rewritten cache is re-parsed'
|
|
|
|
printf 'Checking a left-click opens the popup without ~/.local/bin on PATH...\n'
|
|
mkdir -p "$TMP_DIR/click-bin"
|
|
printf '#!/bin/bash\necho "gui $*" >>"%s/clicks"\n' "$TMP_DIR" >"$TMP_DIR/daily-limits-stub"
|
|
printf '#!/bin/bash\necho "notify $*" >>"%s/clicks"\n' "$TMP_DIR" >"$TMP_DIR/click-bin/notify-send"
|
|
chmod +x "$TMP_DIR/daily-limits-stub" "$TMP_DIR/click-bin/notify-send"
|
|
# i3blocks' PATH has no ~/.local/bin: the launcher must be found by its path.
|
|
BLOCK_BUTTON=1 DAILY_LIMITS_BIN="$TMP_DIR/daily-limits-stub" PATH="$TMP_DIR/click-bin:/usr/bin:/bin" run_block >/dev/null
|
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
|
[[ -s "$TMP_DIR/clicks" ]] && break
|
|
sleep 0.1
|
|
done
|
|
assert_eq 'gui --gui' "$(cat "$TMP_DIR/clicks" 2>/dev/null)" 'left-click runs daily-limits --gui'
|
|
rm -f "$TMP_DIR/clicks"
|
|
# A missing launcher says so instead of a silent no-op.
|
|
BLOCK_BUTTON=1 DAILY_LIMITS_BIN="$TMP_DIR/absent" PATH="$TMP_DIR/click-bin:/usr/bin:/bin" run_block >/dev/null
|
|
assert_eq "notify -u critical daily-limits not installed: $TMP_DIR/absent" "$(cat "$TMP_DIR/clicks" 2>/dev/null)" \
|
|
'a missing launcher raises a notification'
|
|
rm -f "$TMP_DIR/clicks"
|
|
|
|
printf 'All gaming_budget checks passed.\n'
|