From ea8c934ef857ce7fafb109f60c579a33f5b6bd21 Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Fri, 12 Jun 2026 13:07:05 +0800
Subject: [PATCH] Update candidate filter QA fixture expectation
---
C3.tools/round1-space-filter-fixture-qa.sh | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 164 insertions(+), 7 deletions(-)
diff --git a/C3.tools/round1-space-filter-fixture-qa.sh b/C3.tools/round1-space-filter-fixture-qa.sh
index cc1b1a2..9ca4e13 100755
--- a/C3.tools/round1-space-filter-fixture-qa.sh
+++ b/C3.tools/round1-space-filter-fixture-qa.sh
@@ -69,8 +69,9 @@
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" <<'PY'
+ /usr/bin/python3 - "$report" "$REPORT_WAIT" "$expected_last_mouse" "$expected_action" <<'PY'
import json
import sys
import time
@@ -78,6 +79,7 @@
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
@@ -91,6 +93,7 @@
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:
@@ -101,7 +104,11 @@
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} within {timeout:.1f}s", 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
}
@@ -148,11 +155,54 @@
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" \
@@ -169,8 +219,11 @@
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"
+ 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"
@@ -204,6 +257,11 @@
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")
@@ -215,6 +273,7 @@
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")
@@ -228,6 +287,11 @@
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", 0) >= 5.5, "locked Space must use a heavy blue border")
+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")
@@ -238,6 +302,11 @@
"columns": root.get("waterfallColumnNames"),
"cardWindowIDs": [card.get("windowID") for card in cards],
"lockedSpace": root.get("spaceFilterLockedSpaceID"),
+ "lockedBorderWidth": segment_by_id[1].get("borderWidth"),
+ "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))
@@ -316,6 +385,41 @@
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
@@ -345,6 +449,10 @@
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", 0) >= 5.5, "empty locked Space must still use a heavy locked border")
+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[3].get("backgroundKind") == "clear", "empty locked Space must not use gray/gradient fill")
require(segment_by_id[4].get("backgroundKind") == "clear", "empty sibling Space must not inherit gray/gradient fill")
@@ -355,6 +463,47 @@
"columnCount": report.get("columnCount"),
"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
}
@@ -408,20 +557,25 @@
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"
-run_report "$LOCK_A1_REPORT" "click-space:1" "click-space:1" "visible"
+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"
+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"
+run_report "$UNLOCK_REPORT" "click-space:1,click-space:1" "click-space:1" "visible" "unlock"
assert_unlock_report "$UNLOCK_REPORT"
-run_report "$EMPTY_A3_REPORT" "click-space:3" "click-space:3" "visible"
+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"
@@ -430,6 +584,9 @@
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"
+
trap - EXIT
cleanup
--
Gitblit v1.9.3