| | |
| | | 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_SETTINGS="$BUILD_REPORT_ROOT/round01-view-mode-entry-settings-report.json" |
| | | DOMAIN="com.ar.Aligner" |
| | | WATERFALL_MODE_KEY="com.ar.Aligner.preferences.quickSwitch.waterfallViewMode" |
| | | REPORT_WAIT="${ALIGNER_ROUND1_VIEW_MODE_ENTRY_REPORT_WAIT:-6.0}" |
| | |
| | | sys.exit(1) |
| | | |
| | | status_menu = report.get("statusMenu", {}) |
| | | main_menu = report.get("mainMenu", {}) |
| | | settings_window = report.get("settingsWindow", {}) |
| | | root = report.get("rootView", {}) |
| | | |
| | | require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true") |
| | |
| | | require(report.get("resolvedWaterfallViewMode") == expected_mode, "resolved Waterfall mode must match expected mode") |
| | | require(root.get("waterfallViewMode") == expected_mode, "root view Waterfall mode must match expected mode") |
| | | require(status_menu.get("hasAboutItem") is True, "status menu must expose About Aligner") |
| | | require(status_menu.get("hasSettingsItem") is True, "status menu must expose Settings") |
| | | require(status_menu.get("hasQuickSwitchViewSubmenu") is True, "status menu must expose Quick Switch View submenu") |
| | | require(status_menu.get("hasVerticalColumnsItem") is True, "status menu must expose Vertical Columns item") |
| | | require(status_menu.get("hasHorizontalWaterfallItem") is True, "status menu must expose Horizontal Waterfall item") |
| | | require(status_menu.get("verticalColumnsState") == expected_vertical_state, "Vertical Columns check state must match expected mode") |
| | | require(status_menu.get("horizontalWaterfallState") == expected_horizontal_state, "Horizontal Waterfall check state must match expected mode") |
| | | require(main_menu.get("hasMenu") is True, "main menu must exist") |
| | | require(main_menu.get("hasAppMenu") is True, "main menu must expose app menu") |
| | | require(main_menu.get("hasSettingsItem") is True, "main menu must expose Settings") |
| | | require(main_menu.get("settingsKeyEquivalent") == ",", "Settings must use Cmd+, key equivalent") |
| | | require(main_menu.get("hasQuickSwitchViewSubmenuInAppMenu") is True, "app menu must expose Quick Switch View submenu") |
| | | require(main_menu.get("hasQuickSwitchViewSubmenuInViewMenu") is True, "View menu must expose Quick Switch View submenu") |
| | | require(root.get("settingsButtonVisible") is True, "Quick Switch must expose a top-right Settings button") |
| | | settings_frame = root.get("settingsButtonFrame", {}) |
| | | require(settings_frame.get("width", 0) >= 30 and settings_frame.get("height", 0) >= 30, "Settings button must have tappable size") |
| | | require(settings_frame.get("x", 0) + settings_frame.get("width", 0) > root.get("bounds", {}).get("width", 0) * 0.90, "Settings button must sit near the top-right edge") |
| | | 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") |
| | | require(re.match(r"^[0-9]+\.[0-9]+\.[0-9]+$", app_version) is not None, "version must be MAJOR.MINOR.PATCH") |
| | |
| | | "mode": expected_mode, |
| | | "aboutVersion": report.get("aboutVersion"), |
| | | "aboutBuild": report.get("aboutBuild"), |
| | | "statusMenu": status_menu |
| | | "statusMenu": status_menu, |
| | | "mainMenu": main_menu, |
| | | "settingsButtonFrame": settings_frame |
| | | }, indent=2, ensure_ascii=False)) |
| | | PY |
| | | } |
| | | |
| | | wait_for_settings_report() { |
| | | local report="$1" |
| | | |
| | | /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("settingsWindow", {}).get("visible") 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"settings report did not become visible within {timeout:.1f}s", file=sys.stderr) |
| | | sys.exit(1) |
| | | PY |
| | | } |
| | | |
| | | assert_settings_report() { |
| | | local report="$1" |
| | | local app_version="$2" |
| | | local app_build="$3" |
| | | |
| | | /usr/bin/python3 - "$report" "$app_version" "$app_build" <<'PY' |
| | | import json |
| | | import sys |
| | | |
| | | path = sys.argv[1] |
| | | app_version = sys.argv[2] |
| | | app_build = 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", {}) |
| | | main_menu = report.get("mainMenu", {}) |
| | | settings_window = report.get("settingsWindow", {}) |
| | | |
| | | require(report.get("snapshotLoaded") is True, "settings case must load the snapshot before clicking the gear") |
| | | require(report.get("quickSwitchVisible") is False, "clicking the Settings gear must close Quick Switch") |
| | | require(root.get("lastMouseCommand") == "click-settings", "debug mouse sequence must click the Settings gear") |
| | | require(root.get("settingsButtonVisible") is True, "Quick Switch root must report the Settings gear") |
| | | require(main_menu.get("hasSettingsItem") is True, "main menu must expose Settings") |
| | | require(main_menu.get("settingsKeyEquivalent") == ",", "Settings must use Cmd+, key equivalent") |
| | | require(settings_window.get("exists") is True, "Settings window controller must be created") |
| | | require(settings_window.get("visible") is True, "Settings window must be visible after clicking gear") |
| | | require(settings_window.get("selectedTabLabel") == "视图", "Settings must open on the View tab") |
| | | require(settings_window.get("selectedTabIdentifier") == "view", "Settings View tab identifier must be stable") |
| | | require(settings_window.get("hasViewTab") is True, "Settings window must expose View tab") |
| | | require(settings_window.get("usesSwiftUI") is False, "Settings window must not use SwiftUI") |
| | | require(settings_window.get("waterfallViewMode") == "verticalColumns", "Settings must read the current Waterfall view mode") |
| | | 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") |
| | | |
| | | print(json.dumps({ |
| | | "case": "settings-entry", |
| | | "quickSwitchVisible": report.get("quickSwitchVisible"), |
| | | "lastMouseCommand": root.get("lastMouseCommand"), |
| | | "settingsWindow": settings_window, |
| | | "mainMenu": main_menu |
| | | }, indent=2, ensure_ascii=False)) |
| | | PY |
| | | } |
| | |
| | | stop_current_aligner |
| | | } |
| | | |
| | | run_settings_case() { |
| | | local report="$1" |
| | | local app_version="$2" |
| | | local app_build="$3" |
| | | |
| | | stop_current_aligner |
| | | rm -f "$report" |
| | | /usr/bin/defaults write "$DOMAIN" "$WATERFALL_MODE_KEY" -string "verticalColumns" |
| | | |
| | | "$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="click-settings" \ |
| | | --round01-quick-switch-report="$report" \ |
| | | --round01-quick-switch-quit-after=1.2 & |
| | | |
| | | wait_for_settings_report "$report" |
| | | assert_settings_report "$report" "$app_version" "$app_build" |
| | | wait "$!" || true |
| | | stop_current_aligner |
| | | } |
| | | |
| | | [ -x "$APP/Contents/MacOS/Aligner" ] || fail "missing executable app at $APP" |
| | | INFO_PLIST="$APP/Contents/Info.plist" |
| | | APP_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$INFO_PLIST" 2>/dev/null)" \ |
| | |
| | | |
| | | run_case "verticalColumns" "$REPORT_VERTICAL" "on" "off" "$APP_VERSION" "$APP_BUILD" |
| | | run_case "horizontalMasonry" "$REPORT_HORIZONTAL" "off" "on" "$APP_VERSION" "$APP_BUILD" |
| | | run_settings_case "$REPORT_SETTINGS" "$APP_VERSION" "$APP_BUILD" |
| | | |
| | | echo "Round01.1 view-mode entry QA passed" |