#!/bin/bash # Round01 Space Lane Split View fixture QA. It verifies that a fullscreen Space # containing two fullscreen windows is rendered as a left/right Split View tile, # with app labels ordered by the real window frame. 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="$BUILD_REPORT_ROOT/round01-space-lane-split-view-fixture-report.json" REPORT_WAIT="${ALIGNER_ROUND1_SPLIT_VIEW_REPORT_WAIT:-6.0}" APP_PID="" fail() { echo "Round01 Space Lane Split View fixture QA failed: $*" >&2 exit 1 } 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 } wait_for_loaded_report() { /usr/bin/python3 - "$REPORT" "$REPORT_WAIT" <<'PY' import json import sys import time path = sys.argv[1] timeout = float(sys.argv[2]) 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 if report.get("snapshotLoaded") is True: 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"Split View fixture report did not become loaded within {timeout:.1f}s", file=sys.stderr) sys.exit(1) PY } assert_report() { /usr/bin/python3 - "$REPORT" <<'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 mid_x(frame): return frame.get("x", 0) + frame.get("width", 0) / 2 def mid_y(frame): return frame.get("y", 0) + frame.get("height", 0) / 2 require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true") root = report.get("rootView", {}) segments = root.get("spaceLaneSegments", []) require(segments, "Space Lane segments must be reported") split_segments = [ segment for segment in segments if segment.get("fullscreenMarkerKind") == "splitViewPair" ] require(len(split_segments) == 1, "fixture must expose exactly one Split View Space tile") split = split_segments[0] require(split.get("label") == "02", "Split View fixture Space must be 02") require(split.get("type") == "fullscreen", "Split View tile must still be a fullscreen Space") require(split.get("windowCount") == 2, "Split View tile must count both fullscreen windows") require(split.get("appCount") == 2, "Split View tile must count both Split View apps") require(split.get("countVisible") is False, "Split View tile must hide the top-right count label") require(split.get("countText") == "", "Split View tile must clear the top-right count text") require(split.get("windowBlockCount") == 0, "Split View tile must not draw normal window blocks") require("splitView" in split.get("visualStates", []), "Split View tile must expose splitView visual state") require(split.get("fullscreenMarkerPathStyle") == "centerDividerOnly", "Split View marker must draw only the center divider") require( split.get("splitViewAppNames") == ["左侧应用", "右侧应用"], "Split View app names must be ordered by physical window frame from left to right" ) marker = split.get("fullscreenMarkerFrame", {}) labels = split.get("splitViewLabelFrames", []) require(isinstance(marker, dict) and marker.get("width", 0) > 0, "Split View marker frame must be exposed") require(len(labels) == 2, "Split View tile must expose exactly two app label frames") left, right = labels require(mid_x(left) < marker.get("x", 0) + marker.get("width", 0) / 2, "left Split View label must sit in the left half") require(mid_x(right) > marker.get("x", 0) + marker.get("width", 0) / 2, "right Split View label must sit in the right half") require(mid_x(left) < mid_x(right), "Split View label frames must be ordered left to right") require(abs(mid_y(left) - mid_y(marker)) <= 2, "left Split View label must be vertically centered") require(abs(mid_y(right) - mid_y(marker)) <= 2, "right Split View label must be vertically centered") single_fullscreen = [ segment for segment in segments if segment.get("type") == "fullscreen" and segment.get("windowCount") == 1 ] require(single_fullscreen, "fixture must include a single fullscreen regression Space") require( all(segment.get("fullscreenMarkerKind") == "cornerBracket" for segment in single_fullscreen), "single fullscreen Spaces must keep the corner bracket marker" ) require( all(not segment.get("splitViewAppNames") for segment in single_fullscreen), "single fullscreen Spaces must not expose Split View labels" ) print(json.dumps({ "splitSpace": split.get("label"), "splitNames": split.get("splitViewAppNames"), "markerKind": split.get("fullscreenMarkerKind"), "markerPathStyle": split.get("fullscreenMarkerPathStyle"), "countVisible": split.get("countVisible"), "singleFullscreenLabels": [segment.get("label") for segment in single_fullscreen], }, indent=2, ensure_ascii=False)) PY } trap cleanup EXIT stop_current_aligner "$SCRIPT_DIR/package-app.sh" >&2 rm -f "$REPORT" "$APP/Contents/MacOS/Aligner" \ --round0-skip-permissions \ --round01-open-quick-switch \ --round01-fixture-split-view \ --round01-disable-screenshot-refresh \ --round01-quick-switch-report="$REPORT" & APP_PID=$! wait_for_loaded_report assert_report trap - EXIT cleanup echo "Round01 Space Lane Split View fixture QA passed"