#!/bin/bash
|
# Round01.1 Space filter fixture QA. It verifies click-to-lock filtering,
|
# unlock, switching locked Spaces, empty Space state, and single-fullscreen
|
# Space direct activation.
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
OUTPUT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
# shellcheck source=build-output-paths.sh
|
source "$SCRIPT_DIR/build-output-paths.sh"
|
APP="$BUILD_CURRENT_APP"
|
REPORT_WAIT="${ALIGNER_ROUND1_SPACE_FILTER_REPORT_WAIT:-6.0}"
|
REPORT_PREFIX="$BUILD_REPORT_ROOT/round01-space-filter"
|
LOCK_DIR="$BUILD_LOCK_ROOT/round1-space-filter-fixture-qa.lock"
|
APP_PID=""
|
LOCK_HELD=0
|
|
fail() {
|
echo "Round01.1 Space filter fixture QA failed: $*" >&2
|
exit 1
|
}
|
|
acquire_lock() {
|
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
|
fail "another round1-space-filter-fixture-qa.sh appears to be running; run packaging QA scripts serially"
|
fi
|
LOCK_HELD=1
|
}
|
|
release_lock() {
|
if [ "$LOCK_HELD" -eq 1 ]; then
|
rmdir "$LOCK_DIR" 2>/dev/null || true
|
LOCK_HELD=0
|
fi
|
}
|
|
aligner_pids_for_current_app() {
|
ps -axo pid=,args= | while read -r pid command; do
|
case "$command" in
|
"$APP/Contents/MacOS/Aligner"*) echo "$pid" ;;
|
esac
|
done
|
}
|
|
stop_current_aligner() {
|
for pid in $(aligner_pids_for_current_app); do
|
kill "$pid" 2>/dev/null || true
|
done
|
|
for _ in {1..30}; do
|
[ -z "$(aligner_pids_for_current_app)" ] && return
|
sleep 0.1
|
done
|
|
fail "current Aligner app did not exit before QA"
|
}
|
|
cleanup() {
|
if [ -n "${APP_PID:-}" ]; then
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
APP_PID=""
|
fi
|
stop_current_aligner
|
release_lock
|
}
|
|
wait_for_visible_report() {
|
local report="$1"
|
local expected_last_mouse="$2"
|
local expected_action="${3:-}"
|
|
/usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_last_mouse" "$expected_action" <<'PY'
|
import json
|
import sys
|
import time
|
|
path = sys.argv[1]
|
timeout = float(sys.argv[2])
|
expected_last_mouse = sys.argv[3]
|
expected_action = sys.argv[4]
|
deadline = time.monotonic() + timeout
|
last_report = None
|
|
while time.monotonic() < deadline:
|
try:
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
last_report = report
|
root = report.get("rootView", {})
|
if (
|
report.get("snapshotLoaded") is True
|
and report.get("quickSwitchVisible") is True
|
and root.get("lastMouseCommand") == expected_last_mouse
|
and (expected_action == "" or report.get("lastSpaceFilterAction") == expected_action)
|
):
|
sys.exit(0)
|
except FileNotFoundError:
|
pass
|
except json.JSONDecodeError:
|
pass
|
time.sleep(0.2)
|
|
if last_report is not None:
|
print(json.dumps(last_report, indent=2, ensure_ascii=False), file=sys.stderr)
|
print(
|
f"visible report did not reach mouse command {expected_last_mouse!r}"
|
f" and action {expected_action!r} within {timeout:.1f}s",
|
file=sys.stderr
|
)
|
sys.exit(1)
|
PY
|
}
|
|
wait_for_activation_report() {
|
local report="$1"
|
local expected_last_mouse="$2"
|
|
/usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_last_mouse" <<'PY'
|
import json
|
import sys
|
import time
|
|
path = sys.argv[1]
|
timeout = float(sys.argv[2])
|
expected_last_mouse = sys.argv[3]
|
deadline = time.monotonic() + timeout
|
last_report = None
|
|
while time.monotonic() < deadline:
|
try:
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
last_report = report
|
root = report.get("rootView", {})
|
if (
|
report.get("snapshotLoaded") is True
|
and report.get("quickSwitchVisible") is False
|
and report.get("lastSpaceFilterAction") == "activateSingleFullscreen"
|
and report.get("lastActivationResult") is not None
|
and root.get("lastMouseCommand") == expected_last_mouse
|
):
|
sys.exit(0)
|
except FileNotFoundError:
|
pass
|
except json.JSONDecodeError:
|
pass
|
time.sleep(0.2)
|
|
if last_report is not None:
|
print(json.dumps(last_report, indent=2, ensure_ascii=False), file=sys.stderr)
|
print(f"activation report did not reach mouse command {expected_last_mouse!r} within {timeout:.1f}s", file=sys.stderr)
|
sys.exit(1)
|
PY
|
}
|
|
wait_for_space_activation_report() {
|
local report="$1"
|
local expected_last_mouse="$2"
|
|
/usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_last_mouse" <<'PY'
|
import json
|
import sys
|
import time
|
|
path = sys.argv[1]
|
timeout = float(sys.argv[2])
|
expected_last_mouse = sys.argv[3]
|
deadline = time.monotonic() + timeout
|
last_report = None
|
|
while time.monotonic() < deadline:
|
try:
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
last_report = report
|
root = report.get("rootView", {})
|
if (
|
report.get("snapshotLoaded") is True
|
and report.get("quickSwitchVisible") is False
|
and report.get("lastSpaceFilterAction") == "activateSpace"
|
and report.get("lastSpaceActivationSpaceID") is not None
|
and root.get("lastMouseCommand") == expected_last_mouse
|
):
|
sys.exit(0)
|
except FileNotFoundError:
|
pass
|
except json.JSONDecodeError:
|
pass
|
time.sleep(0.2)
|
|
if last_report is not None:
|
print(json.dumps(last_report, indent=2, ensure_ascii=False), file=sys.stderr)
|
print(f"space activation report did not reach mouse command {expected_last_mouse!r} within {timeout:.1f}s", file=sys.stderr)
|
sys.exit(1)
|
PY
|
}
|
|
run_report() {
|
local report="$1"
|
local sequence="$2"
|
local expected_last_mouse="$3"
|
local quick_switch_expectation="${4:-visible}"
|
local expected_action="${5:-}"
|
|
rm -f "$report"
|
"$APP/Contents/MacOS/Aligner" \
|
--round0-skip-permissions \
|
--round01-open-quick-switch \
|
--round01-fixture-space-filter \
|
--round01-disable-screenshot-refresh \
|
--round01-debug-window-activation \
|
--round01-debug-mouse-sequence="$sequence" \
|
--round01-quick-switch-report="$report" &
|
|
APP_PID=$!
|
|
if [ "$quick_switch_expectation" = "hidden" ]; then
|
wait_for_activation_report "$report" "$expected_last_mouse"
|
swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch >&2
|
elif [ "$quick_switch_expectation" = "space-hidden" ]; then
|
wait_for_space_activation_report "$report" "$expected_last_mouse"
|
swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-no-quick-switch >&2
|
elif [ "$quick_switch_expectation" = "visible" ]; then
|
wait_for_visible_report "$report" "$expected_last_mouse" "$expected_action"
|
swift "$SCRIPT_DIR/window-logic-qa.swift" --expect-quick-switch >&2
|
else
|
fail "unknown quick switch expectation: $quick_switch_expectation"
|
fi
|
|
kill "$APP_PID" 2>/dev/null || true
|
wait "$APP_PID" 2>/dev/null || true
|
APP_PID=""
|
sleep 0.3
|
stop_current_aligner
|
}
|
|
assert_lock_a1_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
def is_locked_fill(color):
|
return (
|
color.get("blue", 0) >= 0.85
|
and color.get("green", 0) >= 0.55
|
and color.get("red", 1) <= 0.58
|
and color.get("alpha", 0) >= 0.70
|
)
|
|
def is_white(color):
|
return (
|
color.get("red", 0) >= 0.94
|
and color.get("green", 0) >= 0.94
|
and color.get("blue", 0) >= 0.94
|
and color.get("alpha", 0) >= 0.88
|
)
|
|
root = report.get("rootView", {})
|
items = root.get("appShelfItems", [])
|
columns = root.get("waterfallColumns", [])
|
segments = root.get("spaceLaneSegments", [])
|
cards = [card for column in columns for card in column.get("cards", [])]
|
segment_by_id = {segment.get("spaceID"): segment for segment in segments}
|
visible_frames = [column.get("visibleFrame", {}) for column in columns]
|
visible_min_x = min(frame.get("x", 0) for frame in visible_frames)
|
visible_max_x = max(frame.get("x", 0) + frame.get("width", 0) for frame in visible_frames)
|
left_blank_width = max(0, visible_min_x)
|
right_blank_width = max(0, root.get("waterfallVisibleWidth", 0) - visible_max_x)
|
|
require(report.get("snapshotLoaded") is True, "snapshot must load")
|
require(report.get("quickSwitchVisible") is True, "Quick Switch must stay visible after filtering")
|
require(report.get("spaceFilterActive") is True, "session filter must be active")
|
require(report.get("spaceFilterLockedSpaceID") == 1, "session locked Space must be A1")
|
require(report.get("lastSpaceFilterAction") == "lock", "first click must lock")
|
require(root.get("spaceFilterActive") is True, "root filter must be active")
|
require(root.get("spaceFilterLockedSpaceID") == 1, "root locked Space must be A1")
|
require(report.get("appShelfNames") == ["Alpha Space App", "Beta Space App"], "App Shelf must only include A1 apps")
|
require(root.get("appShelfNames") == ["Alpha Space App", "Beta Space App"], "root App Shelf must only include A1 apps")
|
require(root.get("waterfallColumnNames") == ["Alpha Space App", "Beta Space App"], "Waterfall must only include A1 columns")
|
require(abs(left_blank_width - right_blank_width) <= 2.0, "filtered Waterfall columns must be centered as one content block")
|
require(report.get("windowCount") == 3, "A1 must expose exactly three windows")
|
require(len(cards) == 3, "A1 card count must be three")
|
require(all(card.get("primarySpaceID") == 1 for card in cards), "every visible card must belong to A1")
|
require(50901 not in [card.get("windowID") for card in cards], "no-space window must not enter A1 filter")
|
require(root.get("selectedWindowID") == 50101, "lock should select first filtered window")
|
require(root.get("spaceLaneLabels") == ["A1", "A2", "A3", "B1", "B2"], "Space Lane must keep all Spaces")
|
require(len(segments) == 5, "Space Lane must keep all five fixture Spaces")
|
require(segment_by_id[1].get("windowCount") == 3, "A1 Space Lane global count must remain three")
|
require(segment_by_id[2].get("windowCount") == 1, "A2 Space Lane global count must remain one")
|
require(segment_by_id[3].get("windowCount") == 0, "A3 must remain visible as empty Space")
|
require(segment_by_id[4].get("windowCount") == 0, "B1 must remain visible as empty Space")
|
require(segment_by_id[5].get("windowCount") == 1, "B2 fullscreen Space must remain visible")
|
require("locked" in segment_by_id[1].get("visualStates", []), "locked Space must expose locked visual state")
|
require(segment_by_id[1].get("borderWidth", 99) <= 2.0, "locked Space must not use the old heavy border")
|
require(segment_by_id[1].get("backgroundKind") == "solid", "locked Space must use a solid light-blue fill")
|
require(is_locked_fill(segment_by_id[1].get("backgroundColor", {})), "locked Space fill must be clean light blue")
|
require(is_white(segment_by_id[1].get("labelColor", {})), "locked Space label text must be pure white")
|
require(is_white(segment_by_id[1].get("countColor", {})), "locked Space count text must be pure white")
|
require(is_white(segment_by_id[1].get("appColor", {})), "locked Space app text must be pure white")
|
require(all(is_white(color) for color in segment_by_id[1].get("windowBlockColors", [])), "locked Space window blocks must be pure white")
|
lock_shadow = segment_by_id[1].get("shadowColor", {})
|
require(lock_shadow.get("red", 0) >= 0.90 and lock_shadow.get("green", 0) >= 0.90 and lock_shadow.get("blue", 0) >= 0.90, "locked Space must use a white glow")
|
require(segment_by_id[1].get("shadowOpacity", 0) >= 0.60, "locked Space glow must be visibly stronger than hover")
|
require(segment_by_id[1].get("shadowRadius", 0) >= 26, "locked Space glow radius must be strong enough")
|
require("locked" not in segment_by_id[5].get("visualStates", []), "single fullscreen Space must not be marked locked")
|
require(root.get("projectionTransitionFadeOutLayerCount", 0) >= 1, "filter transition must fade out removed visual layers")
|
require(1 <= root.get("projectionTransitionDurationMilliseconds", 0) <= 220, "filter transition duration must stay within P0 native-feel budget")
|
|
print(json.dumps({
|
"case": "lock-a1",
|
"apps": root.get("appShelfNames"),
|
"columns": root.get("waterfallColumnNames"),
|
"cardWindowIDs": [card.get("windowID") for card in cards],
|
"lockedSpace": root.get("spaceFilterLockedSpaceID"),
|
"lockedBorderWidth": segment_by_id[1].get("borderWidth"),
|
"lockedBackgroundColor": segment_by_id[1].get("backgroundColor"),
|
"lockedLabelColor": segment_by_id[1].get("labelColor"),
|
"lockedWindowBlockColors": segment_by_id[1].get("windowBlockColors"),
|
"lockedShadowOpacity": segment_by_id[1].get("shadowOpacity"),
|
"lockedShadowRadius": segment_by_id[1].get("shadowRadius"),
|
"waterfallLeftBlankWidth": left_blank_width,
|
"waterfallRightBlankWidth": right_blank_width,
|
"fadeOutLayerCount": root.get("projectionTransitionFadeOutLayerCount"),
|
"transitionDurationMilliseconds": root.get("projectionTransitionDurationMilliseconds")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_switch_a2_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
columns = root.get("waterfallColumns", [])
|
cards = [card for column in columns for card in column.get("cards", [])]
|
|
require(report.get("spaceFilterActive") is True, "filter must remain active after switching Space")
|
require(report.get("spaceFilterLockedSpaceID") == 2, "locked Space must switch to A2")
|
require(report.get("lastSpaceFilterAction") == "switch", "second different Space click must switch")
|
require(root.get("appShelfNames") == ["Beta Space App"], "A2 should only show Beta app")
|
require(root.get("waterfallColumnNames") == ["Beta Space App"], "A2 should only show Beta column")
|
require(len(cards) == 1 and cards[0].get("windowID") == 50202, "A2 should only show the Beta A2 window")
|
require(cards[0].get("primarySpaceID") == 2, "A2 card must belong to Space 2")
|
require(root.get("selectedWindowID") == 50202, "switching lock should select first A2 window")
|
|
print(json.dumps({
|
"case": "switch-a2",
|
"apps": root.get("appShelfNames"),
|
"cardWindowIDs": [card.get("windowID") for card in cards],
|
"lockedSpace": root.get("spaceFilterLockedSpaceID")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_unlock_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
cards = [card for column in root.get("waterfallColumns", []) for card in column.get("cards", [])]
|
|
require(report.get("spaceFilterActive") is False, "same Space click must unlock session filter")
|
require(root.get("spaceFilterActive") is False, "same Space click must unlock root filter")
|
require(report.get("lastSpaceFilterAction") == "unlock", "same Space click must record unlock")
|
require(report.get("appShelfNames") == ["Alpha Space App", "Beta Space App", "Fullscreen Solo App", "No Space App"], "unlock must restore all fixture apps")
|
require(report.get("windowCount") == 6, "unlock must restore all six fixture windows")
|
require(50901 in [card.get("windowID") for card in cards], "no-space window should be visible again after unlock")
|
require(root.get("selectedWindowID") == 50101, "unlock should restore pre-filter selected window")
|
|
print(json.dumps({
|
"case": "unlock-a1",
|
"apps": report.get("appShelfNames"),
|
"windowCount": report.get("windowCount"),
|
"selectedWindowID": root.get("selectedWindowID")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_background_unlock_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
cards = [card for column in root.get("waterfallColumns", []) for card in column.get("cards", [])]
|
|
require(report.get("spaceFilterActive") is False, "background click must unlock session filter")
|
require(root.get("spaceFilterActive") is False, "background click must unlock root filter")
|
require(report.get("lastSpaceFilterAction") == "unlockBackground", "background click must record unlockBackground")
|
require(root.get("lastMouseCommand") == "click-background", "last mouse command must be click-background")
|
require(report.get("appShelfNames") == ["Alpha Space App", "Beta Space App", "Fullscreen Solo App", "No Space App"], "background unlock must restore all fixture apps")
|
require(report.get("windowCount") == 6, "background unlock must restore all six fixture windows")
|
require(50901 in [card.get("windowID") for card in cards], "no-space window should be visible again after background unlock")
|
|
print(json.dumps({
|
"case": "background-unlock",
|
"apps": report.get("appShelfNames"),
|
"windowCount": report.get("windowCount"),
|
"lastSpaceFilterAction": report.get("lastSpaceFilterAction")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_empty_a3_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
def is_locked_fill(color):
|
return (
|
color.get("blue", 0) >= 0.85
|
and color.get("green", 0) >= 0.55
|
and color.get("red", 1) <= 0.58
|
and color.get("alpha", 0) >= 0.70
|
)
|
|
def is_white(color):
|
return (
|
color.get("red", 0) >= 0.94
|
and color.get("green", 0) >= 0.94
|
and color.get("blue", 0) >= 0.94
|
and color.get("alpha", 0) >= 0.88
|
)
|
|
root = report.get("rootView", {})
|
segments = root.get("spaceLaneSegments", [])
|
segment_by_id = {segment.get("spaceID"): segment for segment in segments}
|
|
require(report.get("spaceFilterActive") is True, "empty Space click must still lock filter")
|
require(report.get("spaceFilterLockedSpaceID") == 3, "empty Space lock must target A3")
|
require(report.get("lastSpaceFilterAction") == "lock", "empty Space first click must record lock")
|
require(report.get("appCount") == 0, "empty Space must show no App Shelf items")
|
require(report.get("columnCount") == 0, "empty Space must show no Waterfall columns")
|
require(report.get("windowCount") == 0, "empty Space must show no windows")
|
require(root.get("appShelfItemCount") == 0, "root App Shelf must be empty")
|
require(root.get("waterfallColumnCount") == 0, "root Waterfall must be empty")
|
require(root.get("spaceLaneLabels") == ["A1", "A2", "A3", "B1", "B2"], "Space Lane must stay global while empty Space is locked")
|
require("locked" in segment_by_id[3].get("visualStates", []), "empty locked Space must expose locked visual state")
|
require(segment_by_id[3].get("borderWidth", 99) <= 2.0, "empty locked Space must not use the old heavy border")
|
require(segment_by_id[3].get("backgroundKind") == "solid", "empty locked Space must use the same light-blue fill")
|
require(is_locked_fill(segment_by_id[3].get("backgroundColor", {})), "empty locked Space fill must be clean light blue")
|
require(is_white(segment_by_id[3].get("labelColor", {})), "empty locked Space label text must be pure white")
|
require(is_white(segment_by_id[3].get("countColor", {})), "empty locked Space count text must be pure white")
|
require(is_white(segment_by_id[3].get("appColor", {})), "empty locked Space app text must be pure white")
|
empty_shadow = segment_by_id[3].get("shadowColor", {})
|
require(empty_shadow.get("red", 0) >= 0.90 and empty_shadow.get("green", 0) >= 0.90 and empty_shadow.get("blue", 0) >= 0.90, "empty locked Space must use white glow")
|
require(segment_by_id[3].get("shadowOpacity", 0) >= 0.60, "empty locked Space glow must be visible")
|
require(segment_by_id[4].get("backgroundKind") == "clear", "empty sibling Space must not inherit gray/gradient fill")
|
|
print(json.dumps({
|
"case": "empty-a3",
|
"lockedSpace": report.get("spaceFilterLockedSpaceID"),
|
"appCount": report.get("appCount"),
|
"columnCount": report.get("columnCount"),
|
"a3BorderWidth": segment_by_id[3].get("borderWidth"),
|
"a3BackgroundColor": segment_by_id[3].get("backgroundColor"),
|
"a3LabelColor": segment_by_id[3].get("labelColor"),
|
"a3BackgroundKind": segment_by_id[3].get("backgroundKind"),
|
"b1BackgroundKind": segment_by_id[4].get("backgroundKind")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_locked_space_double_click_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
|
require(report.get("quickSwitchVisible") is False, "double-click locked Space must close Quick Switch")
|
require(report.get("spaceFilterActive") is False, "double-click locked Space must release filter")
|
require(root.get("spaceFilterActive") is False, "root must show no filter after Space activation")
|
require(report.get("lastSpaceFilterAction") == "activateSpace", "double-click locked Space must record activateSpace")
|
require(report.get("lastCommittedWindowID") is None, "double-click Space must not commit a window")
|
require(report.get("lastActivationWindowID") is None, "double-click Space must not target a window")
|
require(report.get("lastActivationResult") is None, "double-click Space must not run window activation")
|
require(report.get("lastCommitSource") is None, "double-click Space must not have a commit source")
|
require(report.get("lastSpaceActivationSpaceID") == 1, "double-click locked A1 must request Space 1 activation")
|
require(report.get("lastSpaceActivationDidRequestFocus") is True, "debug Space activation must request focus")
|
require(report.get("lastSpaceActivationDisplayIdentifier") == "debug", "fixture must use debug Space activation service")
|
require(report.get("lastSpaceActivationError") is None, "debug Space activation must not fail")
|
require(root.get("lastMouseCommand") == "double-click-space:1", "last mouse command must be double-click-space:1")
|
|
print(json.dumps({
|
"case": "locked-space-double-click",
|
"quickSwitchVisible": report.get("quickSwitchVisible"),
|
"lastSpaceFilterAction": report.get("lastSpaceFilterAction"),
|
"lastSpaceActivationSpaceID": report.get("lastSpaceActivationSpaceID"),
|
"lastSpaceActivationDisplayIdentifier": report.get("lastSpaceActivationDisplayIdentifier")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_direct_space_double_click_report() {
|
/usr/bin/python3 - "$1" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
|
require(report.get("quickSwitchVisible") is False, "direct double-click Space must close Quick Switch")
|
require(report.get("spaceFilterActive") is False, "direct double-click Space must not leave filter active")
|
require(root.get("spaceFilterActive") is False, "root must show no filter after direct Space activation")
|
require(report.get("lastSpaceFilterAction") == "activateSpace", "direct double-click Space must record activateSpace")
|
require(report.get("lastCommittedWindowID") is None, "direct double-click Space must not commit a window")
|
require(report.get("lastActivationWindowID") is None, "direct double-click Space must not target a window")
|
require(report.get("lastActivationResult") is None, "direct double-click Space must not run window activation")
|
require(report.get("lastCommitSource") is None, "direct double-click Space must not have a commit source")
|
require(report.get("lastSpaceActivationSpaceID") == 1, "direct double-click A1 must request Space 1 activation")
|
require(report.get("lastSpaceActivationDidRequestFocus") is True, "debug Space activation must request focus")
|
require(report.get("lastSpaceActivationDisplayIdentifier") == "debug", "fixture must use debug Space activation service")
|
require(report.get("lastSpaceActivationError") is None, "debug Space activation must not fail")
|
require(root.get("lastMouseCommand") == "double-click-space:1", "last mouse command must be double-click-space:1")
|
|
print(json.dumps({
|
"case": "direct-space-double-click",
|
"quickSwitchVisible": report.get("quickSwitchVisible"),
|
"lastSpaceFilterAction": report.get("lastSpaceFilterAction"),
|
"lastSpaceActivationSpaceID": report.get("lastSpaceActivationSpaceID"),
|
"lastSpaceActivationDisplayIdentifier": report.get("lastSpaceActivationDisplayIdentifier")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
assert_single_fullscreen_report() {
|
local report="$1"
|
local expected_previous_action="$2"
|
|
/usr/bin/python3 - "$report" "$expected_previous_action" <<'PY'
|
import json
|
import sys
|
|
path = sys.argv[1]
|
expected_previous_action = sys.argv[2]
|
with open(path, "r", encoding="utf-8") as file:
|
report = json.load(file)
|
|
def require(condition, message):
|
if not condition:
|
print(message, file=sys.stderr)
|
print(json.dumps(report, indent=2, ensure_ascii=False), file=sys.stderr)
|
sys.exit(1)
|
|
root = report.get("rootView", {})
|
|
require(report.get("quickSwitchVisible") is False, "single fullscreen Space click must close Quick Switch")
|
require(report.get("spaceFilterActive") is False, "single fullscreen Space must not keep filter active")
|
require(root.get("spaceFilterActive") is False, "root must show no filter after direct activation")
|
require(report.get("lastSpaceFilterAction") == "activateSingleFullscreen", "single fullscreen Space must use direct activation action")
|
require(report.get("lastCommittedWindowID") == 50501, "direct activation must commit fullscreen window")
|
require(report.get("lastActivationWindowID") == 50501, "direct activation target must be fullscreen window")
|
require(report.get("lastActivationResult") == "activated", "debug activation should activate fullscreen window")
|
require(report.get("lastCommitSource") == "mouse", "direct fullscreen activation must be mouse sourced")
|
require(root.get("lastMouseCommand") == "click-space:5", "last mouse command must target B2")
|
|
print(json.dumps({
|
"case": expected_previous_action,
|
"quickSwitchVisible": report.get("quickSwitchVisible"),
|
"lastSpaceFilterAction": report.get("lastSpaceFilterAction"),
|
"lastCommittedWindowID": report.get("lastCommittedWindowID"),
|
"lastActivationResult": report.get("lastActivationResult")
|
}, indent=2, ensure_ascii=False))
|
PY
|
}
|
|
acquire_lock
|
trap cleanup EXIT
|
stop_current_aligner
|
"$SCRIPT_DIR/package-app.sh" >&2
|
|
LOCK_A1_REPORT="$REPORT_PREFIX-lock-a1-report.json"
|
SWITCH_A2_REPORT="$REPORT_PREFIX-switch-a2-report.json"
|
UNLOCK_REPORT="$REPORT_PREFIX-unlock-report.json"
|
BACKGROUND_UNLOCK_REPORT="$REPORT_PREFIX-background-unlock-report.json"
|
EMPTY_A3_REPORT="$REPORT_PREFIX-empty-a3-report.json"
|
FULLSCREEN_REPORT="$REPORT_PREFIX-fullscreen-report.json"
|
LOCK_THEN_FULLSCREEN_REPORT="$REPORT_PREFIX-lock-then-fullscreen-report.json"
|
LOCKED_DOUBLE_CLICK_REPORT="$REPORT_PREFIX-locked-double-click-report.json"
|
DIRECT_DOUBLE_CLICK_REPORT="$REPORT_PREFIX-direct-double-click-report.json"
|
|
run_report "$LOCK_A1_REPORT" "click-space:1" "click-space:1" "visible" "lock"
|
assert_lock_a1_report "$LOCK_A1_REPORT"
|
|
run_report "$SWITCH_A2_REPORT" "click-space:1,click-space:2" "click-space:2" "visible" "switch"
|
assert_switch_a2_report "$SWITCH_A2_REPORT"
|
|
run_report "$UNLOCK_REPORT" "click-space:1,click-space:1" "click-space:1" "visible" "unlock"
|
assert_unlock_report "$UNLOCK_REPORT"
|
|
run_report "$BACKGROUND_UNLOCK_REPORT" "click-space:1,click-background" "click-background" "visible" "unlockBackground"
|
assert_background_unlock_report "$BACKGROUND_UNLOCK_REPORT"
|
|
run_report "$EMPTY_A3_REPORT" "click-space:3" "click-space:3" "visible" "lock"
|
assert_empty_a3_report "$EMPTY_A3_REPORT"
|
|
run_report "$FULLSCREEN_REPORT" "click-space:5" "click-space:5" "hidden"
|
assert_single_fullscreen_report "$FULLSCREEN_REPORT" "single-fullscreen"
|
|
run_report "$LOCK_THEN_FULLSCREEN_REPORT" "click-space:1,click-space:5" "click-space:5" "hidden"
|
assert_single_fullscreen_report "$LOCK_THEN_FULLSCREEN_REPORT" "lock-then-single-fullscreen"
|
|
run_report "$LOCKED_DOUBLE_CLICK_REPORT" "click-space:1,double-click-space:1" "double-click-space:1" "space-hidden"
|
assert_locked_space_double_click_report "$LOCKED_DOUBLE_CLICK_REPORT"
|
|
run_report "$DIRECT_DOUBLE_CLICK_REPORT" "double-click-space:1" "double-click-space:1" "space-hidden"
|
assert_direct_space_double_click_report "$DIRECT_DOUBLE_CLICK_REPORT"
|
|
trap - EXIT
|
cleanup
|
|
echo "Round01.1 Space filter fixture QA passed"
|