| | |
| | | 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" |
| | |
| | | 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") |
| | |
| | | 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") |
| | |
| | | "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 |
| | |
| | | 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") |
| | |
| | | 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") |
| | |
| | | "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)) |
| | |
| | | |
| | | 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 |
| | | } |
| | |
| | | |
| | | 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" |
| | | |