#!/bin/bash # omarchy-test-lab:requires=pointer set -Eeuo pipefail source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" FIXTURE="$ROOT/test/acceptance.d/fixtures/plugin" PLUGIN_ID=$(jq -er '.id' "$FIXTURE/manifest.json") [[ $PLUGIN_ID =~ ^[a-z0-9][a-z0-9._-]*$ && $PLUGIN_ID != *".."* ]] || fail "Fukan plugin id is safe" PLUGIN_DIR="$HOME/.config/omarchy/plugins/$PLUGIN_ID" LAYER_NAMESPACE="omarchy-fukan" QMLLINT_BIN=$(command -v qmllint || true) : "${QMLLINT_BIN:=/usr/lib/qt6/bin/qmllint}" screen_lacks() { ! screen_contains "$1" } plugin_absent() { ! omarchy plugin list --json | jq -e --arg id "$PLUGIN_ID" 'any(.[]; .id == $id)' } pointer_is_near() { local expected_x="$1" local expected_y="$2" local actual_x actual_y actual_x=$(hyprctl -j cursorpos | jq -er '.x | round') || return 1 actual_y=$(hyprctl -j cursorpos | jq -er '.y | round') || return 1 (( actual_x >= expected_x - 3 && actual_x <= expected_x + 3 && actual_y >= expected_y - 3 && actual_y <= expected_y + 3 )) } move_pointer_to_visible_text() { local target_text="$1" local snapshot="$ARTIFACTS/fukan-pointer-target.png" local bounds pixel_x pixel_y scale cursor_x cursor_y current_x current_y delta_x delta_y attempt command -v ydotool >/dev/null 2>&1 || fail "pointer automation is available" (( $(hyprctl -j monitors | jq 'length') == 1 )) || fail "pointer test has one guest monitor" timeout 10 grim "$snapshot" 2>/dev/null || fail "pointer target screenshot is captured" bounds=$(tesseract "$snapshot" stdout --psm 11 tsv 2>/dev/null | awk -F '\t' -v target="$target_text" ' tolower($12) == tolower(target) { print $7 + int($9 / 2), $8 + int($10 / 2) exit } ') [[ $bounds =~ ^[0-9]+[[:space:]][0-9]+$ ]] || fail "pointer target '$target_text' is located" read -r pixel_x pixel_y <<<"$bounds" scale=$(hyprctl -j monitors | jq -er '.[0].scale') cursor_x=$(jq -nr --argjson pixel "$pixel_x" --argjson scale "$scale" '$pixel / $scale | floor') cursor_y=$(jq -nr --argjson pixel "$pixel_y" --argjson scale "$scale" '$pixel / $scale | floor') for (( attempt = 0; attempt < 6; attempt++ )); do if pointer_is_near "$cursor_x" "$cursor_y"; then pass "the pointer reaches '$target_text'" return 0 fi current_x=$(hyprctl -j cursorpos | jq -er '.x | round') current_y=$(hyprctl -j cursorpos | jq -er '.y | round') delta_x=$((cursor_x - current_x)) delta_y=$((cursor_y - current_y)) ydotool mousemove -- "$delta_x" "$delta_y" >/dev/null || fail "the virtual pointer moves to '$target_text'" sleep 0.25 done fail "the pointer reaches '$target_text'" } cleanup_fukan() { omarchy-shell shell hide "$PLUGIN_ID" >/dev/null 2>&1 || true if [[ -e $PLUGIN_DIR ]]; then omarchy plugin remove "$PLUGIN_ID" --yes >/dev/null 2>&1 || omarchy plugin disable "$PLUGIN_ID" >/dev/null 2>&1 || true fi close_windows 'fukan-(alpha|beta)' } trap cleanup_fukan EXIT collect_shell_diagnostics() { { echo "==> omarchy-shell ping" omarchy-shell shell ping 2>&1 || true echo "==> quickshell instances" quickshell list -p "$OMARCHY_PATH/shell" --any-display 2>&1 || true echo "==> quickshell log" quickshell --no-color log -p "$OMARCHY_PATH/shell" --any-display --tail 200 2>&1 || true echo "==> omarchy-shell journal" journalctl -t omarchy-shell -n 200 --no-pager --quiet 2>&1 || true } >"$ARTIFACTS/fukan-shell-diagnostics.log" } handle_unexpected_error() { local status=$? local line="$1" local command="$2" trap - ERR collect_shell_diagnostics { printf '\n==> unexpected test command failure\n' printf 'line=%s status=%s command=%s\n' "$line" "$status" "$command" echo "==> Hyprland clients" hyprctl -j clients 2>&1 || true echo "==> Hyprland workspaces" hyprctl -j workspaces 2>&1 || true } >>"$ARTIFACTS/fukan-shell-diagnostics.log" screenshot "failure-fukan-unexpected-command" printf 'not ok - Fukan guest acceptance stopped unexpectedly\n' >&2 exit "$status" } trap 'handle_unexpected_error "$LINENO" "$BASH_COMMAND"' ERR [[ ! -e $PLUGIN_DIR ]] || fail "Fukan is absent before installation" [[ -x $FIXTURE/bin/fukan-clients ]] || fail "Fukan bounded client helper is executable" [[ -x $QMLLINT_BIN ]] || fail "the Quattro guest provides qmllint" if ! "$QMLLINT_BIN" -I "$OMARCHY_PATH/shell" "$FIXTURE/Fukan.qml" >"$ARTIFACTS/fukan-qmllint.log" 2>&1; then fail "Fukan passes qmllint" "$(<"$ARTIFACTS/fukan-qmllint.log")" fi pass "Fukan passes qmllint" "$OMARCHY_PATH/bin/omarchy-plugin-validate" "$FIXTURE" || fail "Fukan passes the host validator" pass "Fukan passes the host validator" mkdir -p "$(dirname "$PLUGIN_DIR")" cp -a "$FIXTURE" "$PLUGIN_DIR" if ! rescan_output=$(omarchy-shell shell rescanPlugins 2>&1); then collect_shell_diagnostics fail "Fukan is discovered" "$rescan_output" fi wait_until "Fukan is discovered" 15 \ bash -c "omarchy plugin list --json | jq -e --arg id '$PLUGIN_ID' 'any(.[]; .id == \$id)'" omarchy plugin enable "$PLUGIN_ID" >/dev/null wait_until "Fukan is enabled" 15 \ bash -c "omarchy plugin list --json | jq -e --arg id '$PLUGIN_ID' 'any(.[]; .id == \$id and .enabled)'" omarchy-shell shell summon "$PLUGIN_ID" '{}' >/dev/null wait_until "Fukan opens through the shell" 20 layer_on_screen "$LAYER_NAMESPACE" omarchy-shell shell hide "$PLUGIN_ID" >/dev/null wait_until "Fukan closes through the shell" 20 layer_absent "$LAYER_NAMESPACE" hyprctl dispatch 'hl.dsp.focus({ workspace = "1" })' >/dev/null launch_app "foot --app-id=fukan-alpha --title='Fukan Alpha' -e sleep 300" wait_until "the workspace 1 fixture window opens" 20 \ bash -c "hyprctl -j clients | jq -e 'any(.[]; .class == \"fukan-alpha\")'" hyprctl dispatch 'hl.dsp.focus({ workspace = "2" })' >/dev/null launch_app "foot --app-id=fukan-beta --title='Fukan Beta' -e sleep 300" wait_until "the workspace 2 fixture window opens" 20 \ bash -c "hyprctl -j clients | jq -e 'any(.[]; .class == \"fukan-beta\")'" omarchy-shell notifications dismissAll >/dev/null 2>&1 || true omarchy-shell shell summon "$PLUGIN_ID" '{"query":"Fukan Beta"}' >/dev/null wait_until "Fukan opens for pointer activation" 20 layer_on_screen "$LAYER_NAMESPACE" wait_until "the filtered Beta window is visible" 20 screen_contains "Fukan Beta" move_pointer_to_visible_text "Beta" screenshot "success-fukan-01-pointer-beta-ready" ydotool click 0xC0 >/dev/null || fail "the virtual pointer clicks Beta" wait_until "clicking Beta focuses the Beta window" 20 \ bash -c "hyprctl -j activewindow | jq -e '.class == \"fukan-beta\"'" wait_until "clicking Beta moves to workspace 2" 20 \ bash -c "hyprctl -j activeworkspace | jq -e '.id == 2'" wait_until "Fukan closes after pointer activation" 20 layer_absent "$LAYER_NAMESPACE" wait_until "Fukan pixels clear after pointer activation" 10 screen_lacks "Find an open window across every workspace" screenshot "success-fukan-02-focused-beta" omarchy-shell notifications dismissAll >/dev/null 2>&1 || true omarchy-shell shell summon "$PLUGIN_ID" '{"query":"Fukan Alpha"}' >/dev/null wait_until "Fukan opens on screen" 20 layer_on_screen "$LAYER_NAMESPACE" wait_until "Fukan heading is visible" 20 screen_contains "Fukan" wait_until "the filtered Alpha window is visible" 20 screen_contains "Fukan Alpha" screenshot "success-fukan-03-keyboard-alpha" wtype -k Return wait_until "Enter focuses the Alpha window" 20 \ bash -c "hyprctl -j activewindow | jq -e '.class == \"fukan-alpha\"'" wait_until "focusing Alpha moves to workspace 1" 20 \ bash -c "hyprctl -j activeworkspace | jq -e '.id == 1'" wait_until "Fukan closes after activation" 20 layer_absent "$LAYER_NAMESPACE" wait_until "Fukan pixels clear after activation" 10 screen_lacks "Find an open window across every workspace" screenshot "success-fukan-04-focused-alpha" omarchy-shell shell summon "$PLUGIN_ID" '{"query":"no matching window"}' >/dev/null wait_until "Fukan reopens for dismissal testing" 20 layer_on_screen "$LAYER_NAMESPACE" wait_until "Fukan is ready for dismissal input" 10 screen_contains "No windows match" wtype -k Escape wait_until "the first Escape keeps Fukan open" 10 layer_on_screen "$LAYER_NAMESPACE" wait_until "the first Escape clears the query" 10 screen_contains "Fukan Alpha" wtype -k Escape wait_until "the second Escape closes Fukan" 20 layer_absent "$LAYER_NAMESPACE" omarchy plugin disable "$PLUGIN_ID" >/dev/null wait_until "Fukan is disabled" 15 \ bash -c "omarchy plugin list --json | jq -e --arg id '$PLUGIN_ID' 'any(.[]; .id == \$id and (.enabled | not))'" omarchy plugin enable "$PLUGIN_ID" >/dev/null wait_until "Fukan is re-enabled" 15 \ bash -c "omarchy plugin list --json | jq -e --arg id '$PLUGIN_ID' 'any(.[]; .id == \$id and .enabled)'" omarchy-restart-shell wait_until "the Omarchy shell restarts" 20 omarchy-shell shell ping wait_until "Fukan remains enabled after shell restart" 20 \ bash -c "omarchy plugin list --json | jq -e --arg id '$PLUGIN_ID' 'any(.[]; .id == \$id and .enabled)'" omarchy-shell notifications dismissAll >/dev/null 2>&1 || true omarchy-shell shell summon "$PLUGIN_ID" '{}' >/dev/null wait_until "Fukan opens after shell restart" 20 layer_on_screen "$LAYER_NAMESPACE" wait_until "Fukan renders after shell restart" 20 screen_contains "Fukan Alpha" screenshot "success-fukan-05-after-shell-restart" omarchy-shell shell hide "$PLUGIN_ID" >/dev/null wait_until "Fukan closes after shell restart" 20 layer_absent "$LAYER_NAMESPACE" omarchy plugin remove "$PLUGIN_ID" --yes >/dev/null wait_until "Fukan is removed from disk" 15 test ! -e "$PLUGIN_DIR" wait_until "Fukan is removed from the shell registry" 15 plugin_absent pass "Fukan guest acceptance passed"