From 4d4294f8c375a06ad87e12a2c73a98a19eb7ed7e Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sun, 14 Jun 2026 18:46:39 +0800
Subject: [PATCH] Add Quick Switch view mode toggle

---
 C3.tools/round1-view-mode-entry-qa.sh |  125 ++++++++++++++++++++++++++++-------------
 1 files changed, 84 insertions(+), 41 deletions(-)

diff --git a/C3.tools/round1-view-mode-entry-qa.sh b/C3.tools/round1-view-mode-entry-qa.sh
index cb53ff4..1dfb68f 100755
--- a/C3.tools/round1-view-mode-entry-qa.sh
+++ b/C3.tools/round1-view-mode-entry-qa.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 # Round01.1 view-mode entry QA. It verifies that the installed-app path exposes
-# a discoverable status-menu entry for switching Quick Switch Waterfall view
-# modes and reports visible About version/build metadata.
+# menu entries plus the in-panel Quick Switch view-mode toggle for switching
+# Waterfall view modes, and reports visible About version/build metadata.
 
 set -euo pipefail
 
@@ -12,7 +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_SETTINGS="$BUILD_REPORT_ROOT/round01-view-mode-entry-settings-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"
 WATERFALL_MODE_KEY="com.ar.Aligner.preferences.quickSwitch.waterfallViewMode"
 REPORT_WAIT="${ALIGNER_ROUND1_VIEW_MODE_ENTRY_REPORT_WAIT:-6.0}"
@@ -135,6 +136,9 @@
 main_menu = report.get("mainMenu", {})
 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_accessibility_label = "切换到纵栏瀑布" if expected_target_mode == "verticalColumns" else "切换到横栏瀑布"
 
 require(report.get("snapshotLoaded") is True, "snapshotLoaded must be true")
 require(report.get("quickSwitchVisible") is True, "Quick Switch must be visible during entry QA")
@@ -154,10 +158,15 @@
 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(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("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("viewModeToggleAccessibilityLabel") == expected_accessibility_label, "view-mode toggle label must name the target mode")
 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")
@@ -170,21 +179,25 @@
     "aboutBuild": report.get("aboutBuild"),
     "statusMenu": status_menu,
     "mainMenu": main_menu,
-    "settingsButtonFrame": settings_frame
+    "viewModeToggleButtonFrame": toggle_frame,
+    "viewModeToggleTargetMode": root.get("viewModeToggleTargetMode"),
+    "viewModeToggleIconKind": root.get("viewModeToggleIconKind")
 }, indent=2, ensure_ascii=False))
 PY
 }
 
-wait_for_settings_report() {
+wait_for_toggle_report() {
   local report="$1"
+  local expected_mode="$2"
 
-  /usr/bin/python3 - "$report" "$REPORT_WAIT" <<'PY'
+  /usr/bin/python3 - "$report" "$expected_mode" "$REPORT_WAIT" <<'PY'
 import json
 import sys
 import time
 
 path = sys.argv[1]
-timeout = float(sys.argv[2])
+expected_mode = sys.argv[2]
+timeout = float(sys.argv[3])
 deadline = time.monotonic() + timeout
 last_report = None
 
@@ -193,7 +206,14 @@
         with open(path, "r", encoding="utf-8") as file:
             report = json.load(file)
         last_report = report
-        if report.get("settingsWindow", {}).get("visible") is True:
+        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") == "click-view-toggle"
+        ):
             sys.exit(0)
     except FileNotFoundError:
         pass
@@ -203,23 +223,31 @@
 
 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)
+print(f"view-mode toggle report did not reach {expected_mode} within {timeout:.1f}s", file=sys.stderr)
 sys.exit(1)
 PY
 }
 
-assert_settings_report() {
+assert_toggle_report() {
   local report="$1"
-  local app_version="$2"
-  local app_build="$3"
+  local initial_mode="$2"
+  local expected_mode="$3"
+  local expected_vertical_state="$4"
+  local expected_horizontal_state="$5"
+  local app_version="$6"
+  local app_build="$7"
 
-  /usr/bin/python3 - "$report" "$app_version" "$app_build" <<'PY'
+  /usr/bin/python3 - "$report" "$initial_mode" "$expected_mode" "$expected_vertical_state" "$expected_horizontal_state" "$app_version" "$app_build" <<'PY'
 import json
 import sys
 
 path = sys.argv[1]
-app_version = sys.argv[2]
-app_build = sys.argv[3]
+initial_mode = sys.argv[2]
+expected_mode = sys.argv[3]
+expected_vertical_state = sys.argv[4]
+expected_horizontal_state = sys.argv[5]
+app_version = sys.argv[6]
+app_build = sys.argv[7]
 
 with open(path, "r", encoding="utf-8") as file:
     report = json.load(file)
@@ -231,30 +259,40 @@
         sys.exit(1)
 
 root = report.get("rootView", {})
+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_label = "切换到纵栏瀑布" if initial_mode == "verticalColumns" else "切换到横栏瀑布"
 
-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(report.get("snapshotLoaded") is True, "toggle case must load the snapshot before clicking the view-mode toggle")
+require(report.get("quickSwitchVisible") is True, "clicking the view-mode toggle must keep Quick Switch visible")
+require(report.get("preferenceWaterfallViewMode") == expected_mode, "preference Waterfall mode must switch after toggle")
+require(report.get("resolvedWaterfallViewMode") == expected_mode, "resolved Waterfall mode must switch after toggle")
+require(root.get("waterfallViewMode") == expected_mode, "root view Waterfall mode must switch after toggle")
+require(root.get("lastMouseCommand") == "click-view-toggle", "debug mouse sequence must click the view-mode toggle")
+require(root.get("settingsButtonVisible") is False, "old Settings button must stay hidden")
+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("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")
 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(settings_window.get("visible") is False, "view-mode toggle must not open Settings")
 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",
+    "case": "view-mode-toggle",
+    "initialMode": initial_mode,
+    "expectedMode": expected_mode,
     "quickSwitchVisible": report.get("quickSwitchVisible"),
     "lastMouseCommand": root.get("lastMouseCommand"),
-    "settingsWindow": settings_window,
+    "viewModeToggleIconKind": root.get("viewModeToggleIconKind"),
+    "statusMenu": status_menu,
     "mainMenu": main_menu
 }, indent=2, ensure_ascii=False))
 PY
@@ -288,14 +326,18 @@
   stop_current_aligner
 }
 
-run_settings_case() {
-  local report="$1"
-  local app_version="$2"
-  local app_build="$3"
+run_toggle_case() {
+  local initial_mode="$1"
+  local expected_mode="$2"
+  local report="$3"
+  local vertical_state="$4"
+  local horizontal_state="$5"
+  local app_version="$6"
+  local app_build="$7"
 
   stop_current_aligner
   rm -f "$report"
-  /usr/bin/defaults write "$DOMAIN" "$WATERFALL_MODE_KEY" -string "verticalColumns"
+  /usr/bin/defaults write "$DOMAIN" "$WATERFALL_MODE_KEY" -string "$initial_mode"
 
   "$APP/Contents/MacOS/Aligner" \
     --round0-skip-permissions \
@@ -303,12 +345,12 @@
     --round01-fixture-app-count=4 \
     --round01-fixture-windows-per-app=2 \
     --round01-disable-screenshot-refresh \
-    --round01-debug-mouse-sequence="click-settings" \
+    --round01-debug-mouse-sequence="click-view-toggle" \
     --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_for_toggle_report "$report" "$expected_mode"
+  assert_toggle_report "$report" "$initial_mode" "$expected_mode" "$vertical_state" "$horizontal_state" "$app_version" "$app_build"
   wait "$!" || true
   stop_current_aligner
 }
@@ -325,6 +367,7 @@
 
 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"
+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"
 
 echo "Round01.1 view-mode entry QA passed"

--
Gitblit v1.9.3