From 4b00f2e7d1437e921a5c72e24dd1a7a07840e7a0 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 13 Oct 2026 10:53:53 +0700 Subject: [PATCH] refactor: validate editor zoom outside the menu validation hub --- .github/hub-ratchet-baseline.json | 2 +- ...inSplitViewController+MenuValidation.swift | 19 +++++------------- ...nSplitViewController+ViewMenuActions.swift | 8 ++++++++ .../MenuValidationCoverageTests.swift | 2 ++ .../Models/Settings/EditorZoomTests.swift | 20 +++++++++++-------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/.github/hub-ratchet-baseline.json b/.github/hub-ratchet-baseline.json index 59ec5fc9a5..db770637ce 100644 --- a/.github/hub-ratchet-baseline.json +++ b/.github/hub-ratchet-baseline.json @@ -1,6 +1,6 @@ { "MenuValidationContext stored properties": 86, - "menu validation `case #selector` arms": 116, + "menu validation `case #selector` arms": 115, "ShortcutAction cases": 98, "TabType cases": 9, "SettingsPane cases": 12, diff --git a/TablePro/Core/Services/Infrastructure/MainSplitViewController+MenuValidation.swift b/TablePro/Core/Services/Infrastructure/MainSplitViewController+MenuValidation.swift index 6742a437d5..57e9f6d788 100644 --- a/TablePro/Core/Services/Infrastructure/MainSplitViewController+MenuValidation.swift +++ b/TablePro/Core/Services/Infrastructure/MainSplitViewController+MenuValidation.swift @@ -153,8 +153,6 @@ struct MenuValidationContext: Equatable { var hasDatabaseFilter = false var canFilterSchemas = false var hasSchemaFilter = false - var canZoomIn = false - var canZoomOut = false } extension MainSplitViewController: NSMenuItemValidation { @@ -418,11 +416,6 @@ extension MainSplitViewController: NSMenuItemValidation { /// connection and something that can hold a selection, not merely a window. case #selector(clearSelection(_:)): return context.isConnected - /// The window's zoom changes the editor font, an app setting that needs no session. A focused - /// diagram claims these first. - case #selector(zoomIn(_:)): return context.canZoomIn - case #selector(zoomOut(_:)): return context.canZoomOut - default: return isAgentSessionCommandEnabled(selector, context: context) } } @@ -604,7 +597,6 @@ extension MainSplitViewController: NSMenuItemValidation { /// command actions, so read from them, the one command that reaches its composer was dimmed. var menuValidationContext: MenuValidationContext { let conversations = assistantConversationModel - let typography = AppSettingsManager.shared.typography guard let actions = commandActions else { return MenuValidationContext( hasSelectedWorkspace: workspaces.selectedConnectionId != nil, @@ -615,9 +607,7 @@ extension MainSplitViewController: NSMenuItemValidation { canFocusAssistant: canFocusAssistant, canMoveTabToConnection: canMoveSelectedTabToConnection, hasOtherWindowTabs: hasOtherWindowTabs, - canToggleWorkspaceRail: canToggleWorkspaceRail, - canZoomIn: typography.editorFontSize(zoomedBy: 1) != nil, - canZoomOut: typography.editorFontSize(zoomedBy: -1) != nil + canToggleWorkspaceRail: canToggleWorkspaceRail ) } return MenuValidationContext( @@ -705,9 +695,7 @@ extension MainSplitViewController: NSMenuItemValidation { canFavoriteActiveDatabase: actions.canFavoriteActiveDatabase, hasDatabaseFilter: actions.hasDatabaseFilter, canFilterSchemas: actions.canFilterSchemas, - hasSchemaFilter: actions.hasSchemaFilter, - canZoomIn: typography.editorFontSize(zoomedBy: 1) != nil, - canZoomOut: typography.editorFontSize(zoomedBy: -1) != nil + hasSchemaFilter: actions.hasSchemaFilter ) } @@ -725,6 +713,9 @@ extension MainSplitViewController: NSMenuItemValidation { if action == #selector(setSafeModeLevel(_:)) { return canChooseSafeModeLevel(menuItem) } if action == #selector(requestDisconnect) { return canDisconnect } if action == #selector(retryConnection) { return canReconnect } + if let zoom = Self.zoomEnablement(action, typography: AppSettingsManager.shared.typography) { + return zoom + } return Self.isEnabled(action, context: menuValidationContext(naming: menuItem)) } diff --git a/TablePro/Core/Services/Infrastructure/MainSplitViewController+ViewMenuActions.swift b/TablePro/Core/Services/Infrastructure/MainSplitViewController+ViewMenuActions.swift index fdcda67941..55bcfba40f 100644 --- a/TablePro/Core/Services/Infrastructure/MainSplitViewController+ViewMenuActions.swift +++ b/TablePro/Core/Services/Infrastructure/MainSplitViewController+ViewMenuActions.swift @@ -151,6 +151,14 @@ extension MainSplitViewController: ZoomCommandResponding { adjustEditorTextSize(by: -1) } + /// Nil for any other command. The size is the app's setting, not this window's, so it is read + /// live instead of through the captured validation context. + static func zoomEnablement(_ action: Selector, typography: TypographySettings) -> Bool? { + if action == #selector(zoomIn(_:)) { return typography.editorFontSize(zoomedBy: 1) != nil } + if action == #selector(zoomOut(_:)) { return typography.editorFontSize(zoomedBy: -1) != nil } + return nil + } + private func adjustEditorTextSize(by delta: Int) { var typography = AppSettingsManager.shared.typography guard let size = typography.editorFontSize(zoomedBy: delta) else { return } diff --git a/TableProTests/Core/Services/Infrastructure/MenuValidationCoverageTests.swift b/TableProTests/Core/Services/Infrastructure/MenuValidationCoverageTests.swift index 2700e19db0..00e3db8664 100644 --- a/TableProTests/Core/Services/Infrastructure/MenuValidationCoverageTests.swift +++ b/TableProTests/Core/Services/Infrastructure/MenuValidationCoverageTests.swift @@ -29,6 +29,8 @@ private let liveValidatedSelectors: Set = [ #selector(MainSplitViewController.toggleContentModeFromMenu(_:)), #selector(MainSplitViewController.requestDisconnect), #selector(MainSplitViewController.retryConnection), + #selector(MainSplitViewController.zoomIn(_:)), + #selector(MainSplitViewController.zoomOut(_:)), ] @MainActor diff --git a/TableProTests/Models/Settings/EditorZoomTests.swift b/TableProTests/Models/Settings/EditorZoomTests.swift index 3f225bc270..8c277c6abb 100644 --- a/TableProTests/Models/Settings/EditorZoomTests.swift +++ b/TableProTests/Models/Settings/EditorZoomTests.swift @@ -33,14 +33,18 @@ struct EditorZoomTests { let zoomIn = #selector(MainSplitViewController.zoomIn(_:)) let zoomOut = #selector(MainSplitViewController.zoomOut(_:)) - var context = MenuValidationContext() - context.canZoomOut = true - #expect(MainSplitViewController.resolvedEnablement(zoomIn, context: context) == false) - #expect(MainSplitViewController.resolvedEnablement(zoomOut, context: context) == true) + let middle = TypographySettings(editorFontSize: 13) + let largest = TypographySettings(editorFontSize: TypographySettings.sizeRange.upperBound) + let smallest = TypographySettings(editorFontSize: TypographySettings.sizeRange.lowerBound) + + #expect(MainSplitViewController.zoomEnablement(zoomIn, typography: middle) == true) + #expect(MainSplitViewController.zoomEnablement(zoomOut, typography: middle) == true) + #expect(MainSplitViewController.zoomEnablement(zoomIn, typography: largest) == false) + #expect(MainSplitViewController.zoomEnablement(zoomOut, typography: largest) == true) + #expect(MainSplitViewController.zoomEnablement(zoomIn, typography: smallest) == true) + #expect(MainSplitViewController.zoomEnablement(zoomOut, typography: smallest) == false) - context.canZoomIn = true - context.canZoomOut = false - #expect(MainSplitViewController.resolvedEnablement(zoomIn, context: context) == true) - #expect(MainSplitViewController.resolvedEnablement(zoomOut, context: context) == false) + let other = #selector(MainSplitViewController.clearSelection(_:)) + #expect(MainSplitViewController.zoomEnablement(other, typography: middle) == nil) } }