Ariver
2026-06-14 ae0f48a9d9543d43b120674f0e58164d7cce95ad
C3.tools/round1-view-mode-entry-qa.sh
@@ -12,6 +12,8 @@
APP="$BUILD_CURRENT_APP"
REPORT_VERTICAL="$BUILD_REPORT_ROOT/round01-view-mode-entry-vertical-report.json"
REPORT_HORIZONTAL="$BUILD_REPORT_ROOT/round01-view-mode-entry-horizontal-report.json"
REPORT_VERTICAL_HOVER="$BUILD_REPORT_ROOT/round01-view-mode-entry-vertical-hover-report.json"
REPORT_HORIZONTAL_HOVER="$BUILD_REPORT_ROOT/round01-view-mode-entry-horizontal-hover-report.json"
REPORT_TOGGLE_TO_HORIZONTAL="$BUILD_REPORT_ROOT/round01-view-mode-entry-toggle-to-horizontal-report.json"
REPORT_TOGGLE_TO_VERTICAL="$BUILD_REPORT_ROOT/round01-view-mode-entry-toggle-to-vertical-report.json"
DOMAIN="com.ar.Aligner"
@@ -137,8 +139,9 @@
settings_window = report.get("settingsWindow", {})
root = report.get("rootView", {})
expected_target_mode = "verticalColumns" if expected_mode == "horizontalMasonry" else "horizontalMasonry"
expected_icon_kind = "verticalColumnsTarget" if expected_target_mode == "verticalColumns" else "horizontalMasonryTarget"
expected_icon_kind = "H2" if expected_mode == "horizontalMasonry" else "Z2"
expected_accessibility_label = "切换到纵栏瀑布" if expected_target_mode == "verticalColumns" else "切换到横栏瀑布"
standard_blue = {"red": 135 / 255, "green": 200 / 255, "blue": 1.0, "alpha": 1.0}
require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
require(report.get("quickSwitchVisible") is True, "Quick Switch must be visible during entry QA")
@@ -161,12 +164,18 @@
require(root.get("settingsButtonVisible") is False, "Quick Switch must not expose the old top-right Settings button")
require(root.get("viewModeToggleButtonVisible") is True, "Quick Switch must expose a top-right view-mode toggle button")
toggle_frame = root.get("viewModeToggleButtonFrame", {})
require(toggle_frame.get("width", 0) >= 30 and toggle_frame.get("height", 0) >= 30, "view-mode toggle button must have tappable size")
require(toggle_frame.get("width", 0) == 30 and toggle_frame.get("height", 0) == 30, "view-mode toggle visual button must be 30pt")
require(root.get("viewModeToggleHitSize") == 34, "view-mode toggle must keep a 34pt hit target")
require(toggle_frame.get("x", 0) + toggle_frame.get("width", 0) > root.get("bounds", {}).get("width", 0) * 0.90, "view-mode toggle button must sit near the top-right edge")
require(root.get("viewModeToggleCurrentMode") == expected_mode, "view-mode toggle must report the current mode")
require(root.get("viewModeToggleTargetMode") == expected_target_mode, "view-mode toggle must report the next target mode")
require(root.get("viewModeToggleIconKind") == expected_icon_kind, "view-mode toggle icon kind must represent the target mode")
require(root.get("viewModeToggleIconKind") == expected_icon_kind, "view-mode toggle icon kind must represent the selected current mode")
require(root.get("viewModeToggleVisualState") == expected_icon_kind, "view-mode toggle visual state must match the selected current mode")
require(root.get("viewModeToggleAccessibilityLabel") == expected_accessibility_label, "view-mode toggle label must name the target mode")
blue = root.get("viewModeToggleStandardBlueColor", {})
for channel, expected in standard_blue.items():
    require(abs(blue.get(channel, -1) - expected) <= 0.002, f"view-mode toggle standard blue must be #87C8FF for {channel}")
require(abs(root.get("viewModeToggleGlyphLineWidth", 0) - 1.65) <= 0.001, "view-mode toggle glyphs must share the standard line width")
require(settings_window.get("visible") is False, "Settings window should not be open in passive view-mode entry cases")
require(report.get("aboutVersion") == app_version, "About version must match app Info.plist")
require(report.get("aboutBuild") == app_build, "About build must match app Info.plist")
@@ -181,6 +190,88 @@
    "mainMenu": main_menu,
    "viewModeToggleButtonFrame": toggle_frame,
    "viewModeToggleTargetMode": root.get("viewModeToggleTargetMode"),
    "viewModeToggleIconKind": root.get("viewModeToggleIconKind"),
    "viewModeToggleVisualState": root.get("viewModeToggleVisualState")
}, indent=2, ensure_ascii=False))
PY
}
wait_for_hover_report() {
  local report="$1"
  local expected_mode="$2"
  /usr/bin/python3 - "$report" "$expected_mode" "$REPORT_WAIT" <<'PY'
import json
import sys
import time
path = sys.argv[1]
expected_mode = sys.argv[2]
timeout = float(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 True
            and report.get("resolvedWaterfallViewMode") == expected_mode
            and root.get("waterfallViewMode") == expected_mode
            and root.get("lastMouseCommand") == "hover-view-toggle"
        ):
            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"view-mode hover report did not reach {expected_mode} within {timeout:.1f}s", file=sys.stderr)
sys.exit(1)
PY
}
assert_hover_report() {
  local report="$1"
  local expected_mode="$2"
  local expected_visual_state="$3"
  /usr/bin/python3 - "$report" "$expected_mode" "$expected_visual_state" <<'PY'
import json
import sys
path = sys.argv[1]
expected_mode = sys.argv[2]
expected_visual_state = sys.argv[3]
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(root.get("waterfallViewMode") == expected_mode, "hover case must keep current view mode")
require(root.get("lastMouseCommand") == "hover-view-toggle", "hover case must use the view toggle hover command")
require(root.get("viewModeToggleButtonHovered") is True, "hover case must mark the toggle hovered")
require(root.get("viewModeToggleHoverPreviewSuppressed") is False, "hover preview must not be suppressed in hover case")
require(root.get("viewModeToggleVisualState") == expected_visual_state, "hover case must expose the target unselected visual state")
require(root.get("viewModeToggleIconKind") == expected_visual_state, "hover icon kind must match visual state")
print(json.dumps({
    "case": "view-mode-hover",
    "mode": expected_mode,
    "viewModeToggleVisualState": root.get("viewModeToggleVisualState"),
    "viewModeToggleIconKind": root.get("viewModeToggleIconKind")
}, indent=2, ensure_ascii=False))
PY
@@ -262,7 +353,7 @@
status_menu = report.get("statusMenu", {})
main_menu = report.get("mainMenu", {})
settings_window = report.get("settingsWindow", {})
expected_icon_kind = "verticalColumnsTarget" if initial_mode == "verticalColumns" else "horizontalMasonryTarget"
expected_icon_kind = "H2" if expected_mode == "horizontalMasonry" else "Z2"
expected_label = "切换到纵栏瀑布" if initial_mode == "verticalColumns" else "切换到横栏瀑布"
require(report.get("snapshotLoaded") is True, "toggle case must load the snapshot before clicking the view-mode toggle")
@@ -275,7 +366,9 @@
require(root.get("viewModeToggleButtonVisible") is True, "Quick Switch root must report the view-mode toggle")
require(root.get("viewModeToggleCurrentMode") == expected_mode, "toggle current mode must update after click")
require(root.get("viewModeToggleTargetMode") == initial_mode, "toggle target mode must flip back to the initial mode after click")
require(root.get("viewModeToggleIconKind") == expected_icon_kind, "toggle icon must represent the next target mode after click")
require(root.get("viewModeToggleIconKind") == expected_icon_kind, "toggle icon must represent the selected mode after click")
require(root.get("viewModeToggleVisualState") == expected_icon_kind, "toggle visual state must represent the selected mode after click")
require(root.get("viewModeToggleHoverPreviewSuppressed") is True, "click should suppress hover preview until pointer exits")
require(root.get("viewModeToggleAccessibilityLabel") == expected_label, "toggle label must represent the next target mode after click")
require(status_menu.get("verticalColumnsState") == expected_vertical_state, "Vertical Columns menu state must update after toggle")
require(status_menu.get("horizontalWaterfallState") == expected_horizontal_state, "Horizontal Waterfall menu state must update after toggle")
@@ -292,6 +385,7 @@
    "quickSwitchVisible": report.get("quickSwitchVisible"),
    "lastMouseCommand": root.get("lastMouseCommand"),
    "viewModeToggleIconKind": root.get("viewModeToggleIconKind"),
    "viewModeToggleVisualState": root.get("viewModeToggleVisualState"),
    "statusMenu": status_menu,
    "mainMenu": main_menu
}, indent=2, ensure_ascii=False))
@@ -322,6 +416,31 @@
  wait_for_loaded_report "$report" "$mode"
  assert_report "$report" "$mode" "$vertical_state" "$horizontal_state" "$app_version" "$app_build"
  wait "$!" || true
  stop_current_aligner
}
run_hover_case() {
  local mode="$1"
  local expected_visual_state="$2"
  local report="$3"
  stop_current_aligner
  rm -f "$report"
  /usr/bin/defaults write "$DOMAIN" "$WATERFALL_MODE_KEY" -string "$mode"
  "$APP/Contents/MacOS/Aligner" \
    --round0-skip-permissions \
    --round01-open-quick-switch \
    --round01-fixture-app-count=4 \
    --round01-fixture-windows-per-app=2 \
    --round01-disable-screenshot-refresh \
    --round01-debug-mouse-sequence="hover-view-toggle" \
    --round01-quick-switch-report="$report" \
    --round01-quick-switch-quit-after=1.2 &
  wait_for_hover_report "$report" "$mode"
  assert_hover_report "$report" "$mode" "$expected_visual_state"
  wait "$!" || true
  stop_current_aligner
}
@@ -367,6 +486,8 @@
run_case "verticalColumns" "$REPORT_VERTICAL" "on" "off" "$APP_VERSION" "$APP_BUILD"
run_case "horizontalMasonry" "$REPORT_HORIZONTAL" "off" "on" "$APP_VERSION" "$APP_BUILD"
run_hover_case "verticalColumns" "H1" "$REPORT_VERTICAL_HOVER"
run_hover_case "horizontalMasonry" "Z1" "$REPORT_HORIZONTAL_HOVER"
run_toggle_case "verticalColumns" "horizontalMasonry" "$REPORT_TOGGLE_TO_HORIZONTAL" "off" "on" "$APP_VERSION" "$APP_BUILD"
run_toggle_case "horizontalMasonry" "verticalColumns" "$REPORT_TOGGLE_TO_VERTICAL" "on" "off" "$APP_VERSION" "$APP_BUILD"