cmux/cmuxTests/ShortcutAndCommandPaletteTests.swift
Austin Wang e203c51c7a
Show update-available banner automatically on launch (#1651)
* Show update-available banner automatically on launch

Probe for updates immediately on launch via Sparkle's
checkForUpdateInformation() so the sidebar surfaces a passive update
indicator without waiting for the 24h scheduler. When Sparkle detects
an available update in the background, the pill now shows
"Update Available: X.Y.Z" with accent styling while the updater is
idle. Clicking it triggers the full interactive update flow.

Also fixes thread safety in delegate callbacks by dispatching
@Published mutations to the main queue.

Closes #1643

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add periodic background update probe every 15 minutes

The launch-only probe wouldn't catch updates published while the app
is already running. Add a repeating 15-minute timer that calls
checkForUpdateInformation() so the sidebar banner appears within a
reasonable window after a new version is published.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Change background update probe interval to 30 minutes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Change update check interval to 1 hour and migrate existing users

Reduce Sparkle's scheduled check interval from 24h to 1h so update
banners appear sooner. Migrate users stuck on the old 24h default by
bumping the migration key to v2. Align background probe interval with
the Sparkle check interval instead of hardcoding 30 minutes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 12:30:44 -07:00

988 lines
35 KiB
Swift

import XCTest
import AppKit
import SwiftUI
import UniformTypeIdentifiers
import WebKit
import ObjectiveC.runtime
import Bonsplit
import UserNotifications
#if canImport(cmux_DEV)
@testable import cmux_DEV
#elseif canImport(cmux)
@testable import cmux
#endif
final class SplitShortcutTransientFocusGuardTests: XCTestCase {
func testSuppressesWhenFirstResponderFallsBackAndHostedViewIsTiny() {
XCTAssertTrue(
shouldSuppressSplitShortcutForTransientTerminalFocusInputs(
firstResponderIsWindow: true,
hostedSize: CGSize(width: 79, height: 0),
hostedHiddenInHierarchy: false,
hostedAttachedToWindow: true
)
)
}
func testSuppressesWhenFirstResponderFallsBackAndHostedViewIsDetached() {
XCTAssertTrue(
shouldSuppressSplitShortcutForTransientTerminalFocusInputs(
firstResponderIsWindow: true,
hostedSize: CGSize(width: 1051.5, height: 1207),
hostedHiddenInHierarchy: false,
hostedAttachedToWindow: false
)
)
}
func testAllowsWhenFirstResponderFallsBackButGeometryIsHealthy() {
XCTAssertFalse(
shouldSuppressSplitShortcutForTransientTerminalFocusInputs(
firstResponderIsWindow: true,
hostedSize: CGSize(width: 1051.5, height: 1207),
hostedHiddenInHierarchy: false,
hostedAttachedToWindow: true
)
)
}
func testAllowsWhenFirstResponderIsTerminalEvenIfViewIsTiny() {
XCTAssertFalse(
shouldSuppressSplitShortcutForTransientTerminalFocusInputs(
firstResponderIsWindow: false,
hostedSize: CGSize(width: 79, height: 0),
hostedHiddenInHierarchy: false,
hostedAttachedToWindow: true
)
)
}
}
final class FullScreenShortcutTests: XCTestCase {
func testMatchesCommandControlF() {
XCTAssertTrue(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "f",
keyCode: 3
)
)
}
func testMatchesCommandControlFFromKeyCodeWhenCharsAreUnavailable() {
XCTAssertTrue(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "",
keyCode: 3,
layoutCharacterProvider: { _, _ in nil }
)
)
}
func testDoesNotFallbackToANSIWhenLayoutTranslationReturnsNonFCharacter() {
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "",
keyCode: 3,
layoutCharacterProvider: { _, _ in "u" }
)
)
}
func testMatchesCommandControlFWhenCommandAwareLayoutTranslationProvidesF() {
XCTAssertTrue(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "",
keyCode: 3,
layoutCharacterProvider: { _, modifierFlags in
modifierFlags.contains(.command) ? "f" : "u"
}
)
)
}
func testMatchesCommandControlFWhenCharsAreControlSequence() {
XCTAssertTrue(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "\u{06}",
keyCode: 3,
layoutCharacterProvider: { _, _ in nil }
)
)
}
func testRejectsPhysicalFWhenCharacterRepresentsDifferentLayoutKey() {
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "u",
keyCode: 3
)
)
}
func testIgnoresCapsLockForCommandControlF() {
XCTAssertTrue(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control, .capsLock],
chars: "f",
keyCode: 3
)
)
}
func testRejectsWhenControlIsMissing() {
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command],
chars: "f",
keyCode: 3
)
)
}
func testRejectsAdditionalModifiers() {
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control, .shift],
chars: "f",
keyCode: 3
)
)
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control, .option],
chars: "f",
keyCode: 3
)
)
}
func testRejectsWhenCommandIsMissing() {
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.control],
chars: "f",
keyCode: 3
)
)
}
func testRejectsNonFKey() {
XCTAssertFalse(
shouldToggleMainWindowFullScreenForCommandControlFShortcut(
flags: [.command, .control],
chars: "r",
keyCode: 15
)
)
}
}
final class CommandPaletteKeyboardNavigationTests: XCTestCase {
func testArrowKeysMoveSelectionWithoutModifiers() {
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [],
chars: "",
keyCode: 125
),
1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [],
chars: "",
keyCode: 126
),
-1
)
XCTAssertNil(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.shift],
chars: "",
keyCode: 125
)
)
}
func testControlLetterNavigationSupportsPrintableAndControlChars() {
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "n",
keyCode: 45
),
1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "\u{0e}",
keyCode: 45
),
1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "p",
keyCode: 35
),
-1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "\u{10}",
keyCode: 35
),
-1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "j",
keyCode: 38
),
1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "\u{0a}",
keyCode: 38
),
1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "k",
keyCode: 40
),
-1
)
XCTAssertEqual(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "\u{0b}",
keyCode: 40
),
-1
)
}
func testIgnoresUnsupportedModifiersAndKeys() {
XCTAssertNil(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.command],
chars: "n",
keyCode: 45
)
)
XCTAssertNil(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control, .shift],
chars: "n",
keyCode: 45
)
)
XCTAssertNil(
commandPaletteSelectionDeltaForKeyboardNavigation(
flags: [.control],
chars: "x",
keyCode: 7
)
)
}
}
final class CommandPaletteOpenShortcutConsumptionTests: XCTestCase {
func testDoesNotConsumeWhenPaletteIsNotVisible() {
XCTAssertFalse(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: false,
normalizedFlags: [.command],
chars: "n",
keyCode: 45
)
)
}
func testConsumesAppCommandShortcutsWhenPaletteIsVisible() {
XCTAssertTrue(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command],
chars: "n",
keyCode: 45
)
)
XCTAssertTrue(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command],
chars: "t",
keyCode: 17
)
)
XCTAssertTrue(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command, .shift],
chars: ",",
keyCode: 43
)
)
}
func testAllowsClipboardAndUndoShortcutsForPaletteTextEditing() {
XCTAssertFalse(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command],
chars: "v",
keyCode: 9
)
)
XCTAssertFalse(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command],
chars: "z",
keyCode: 6
)
)
XCTAssertFalse(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command, .shift],
chars: "z",
keyCode: 6
)
)
}
func testAllowsArrowAndDeleteEditingCommandsForPaletteTextEditing() {
XCTAssertFalse(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command],
chars: "",
keyCode: 123
)
)
XCTAssertFalse(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [.command],
chars: "",
keyCode: 51
)
)
}
func testConsumesEscapeWhenPaletteIsVisible() {
XCTAssertTrue(
shouldConsumeShortcutWhileCommandPaletteVisible(
isCommandPaletteVisible: true,
normalizedFlags: [],
chars: "",
keyCode: 53
)
)
}
}
final class CommandPaletteRestoreFocusStateMachineTests: XCTestCase {
func testRestoresBrowserAddressBarWhenPaletteOpenedFromFocusedAddressBar() {
let panelId = UUID()
XCTAssertTrue(
ContentView.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss(
focusedPanelIsBrowser: true,
focusedBrowserAddressBarPanelId: panelId,
focusedPanelId: panelId
)
)
}
func testDoesNotRestoreBrowserAddressBarWhenFocusedPanelIsNotBrowser() {
let panelId = UUID()
XCTAssertFalse(
ContentView.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss(
focusedPanelIsBrowser: false,
focusedBrowserAddressBarPanelId: panelId,
focusedPanelId: panelId
)
)
}
func testDoesNotRestoreBrowserAddressBarWhenAnotherPanelHadAddressBarFocus() {
XCTAssertFalse(
ContentView.shouldRestoreBrowserAddressBarAfterCommandPaletteDismiss(
focusedPanelIsBrowser: true,
focusedBrowserAddressBarPanelId: UUID(),
focusedPanelId: UUID()
)
)
}
}
final class CommandPaletteRenameSelectionSettingsTests: XCTestCase {
private let suiteName = "cmux.tests.commandPaletteRenameSelection.\(UUID().uuidString)"
private func makeDefaults() -> UserDefaults {
let defaults = UserDefaults(suiteName: suiteName)!
defaults.removePersistentDomain(forName: suiteName)
return defaults
}
func testDefaultsToSelectAllWhenUnset() {
let defaults = makeDefaults()
XCTAssertTrue(CommandPaletteRenameSelectionSettings.selectAllOnFocusEnabled(defaults: defaults))
}
func testReturnsFalseWhenStoredFalse() {
let defaults = makeDefaults()
defaults.set(false, forKey: CommandPaletteRenameSelectionSettings.selectAllOnFocusKey)
XCTAssertFalse(CommandPaletteRenameSelectionSettings.selectAllOnFocusEnabled(defaults: defaults))
}
func testReturnsTrueWhenStoredTrue() {
let defaults = makeDefaults()
defaults.set(true, forKey: CommandPaletteRenameSelectionSettings.selectAllOnFocusKey)
XCTAssertTrue(CommandPaletteRenameSelectionSettings.selectAllOnFocusEnabled(defaults: defaults))
}
}
final class CommandPaletteSelectionScrollBehaviorTests: XCTestCase {
func testFirstEntryPinsToTopAnchor() {
let anchor = ContentView.commandPaletteScrollPositionAnchor(
selectedIndex: 0,
resultCount: 20
)
XCTAssertEqual(anchor, UnitPoint.top)
}
func testLastEntryPinsToBottomAnchor() {
let anchor = ContentView.commandPaletteScrollPositionAnchor(
selectedIndex: 19,
resultCount: 20
)
XCTAssertEqual(anchor, UnitPoint.bottom)
}
func testMiddleEntryUsesNilAnchorForMinimalScroll() {
let anchor = ContentView.commandPaletteScrollPositionAnchor(
selectedIndex: 6,
resultCount: 20
)
XCTAssertNil(anchor)
}
func testEmptyResultsProduceNoAnchor() {
let anchor = ContentView.commandPaletteScrollPositionAnchor(
selectedIndex: 0,
resultCount: 0
)
XCTAssertNil(anchor)
}
}
final class ShortcutHintModifierPolicyTests: XCTestCase {
func testShortcutHintRequiresEnabledCommandOnlyModifier() {
withDefaultsSuite { defaults in
defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertTrue(ShortcutHintModifierPolicy.shouldShowHints(for: [.command], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command, .shift], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control, .shift], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command, .option], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control, .option], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command, .control], defaults: defaults))
}
}
func testCommandHintCanBeDisabledInSettings() {
withDefaultsSuite { defaults in
defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.command], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control], defaults: defaults))
}
}
func testCommandHintDefaultsToEnabledWhenSettingMissing() {
withDefaultsSuite { defaults in
defaults.removeObject(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertTrue(ShortcutHintModifierPolicy.shouldShowHints(for: [.command], defaults: defaults))
XCTAssertFalse(ShortcutHintModifierPolicy.shouldShowHints(for: [.control], defaults: defaults))
}
}
func testShortcutHintUsesIntentionalHoldDelay() {
XCTAssertEqual(ShortcutHintModifierPolicy.intentionalHoldDelay, 0.30, accuracy: 0.001)
}
func testCurrentWindowRequiresHostWindowToBeKeyAndMatchEventWindow() {
XCTAssertTrue(
ShortcutHintModifierPolicy.isCurrentWindow(
hostWindowNumber: 42,
hostWindowIsKey: true,
eventWindowNumber: 42,
keyWindowNumber: 42
)
)
XCTAssertFalse(
ShortcutHintModifierPolicy.isCurrentWindow(
hostWindowNumber: 42,
hostWindowIsKey: true,
eventWindowNumber: 7,
keyWindowNumber: 42
)
)
XCTAssertFalse(
ShortcutHintModifierPolicy.isCurrentWindow(
hostWindowNumber: 42,
hostWindowIsKey: false,
eventWindowNumber: 42,
keyWindowNumber: 42
)
)
}
func testWindowScopedShortcutHintsUseKeyWindowWhenNoEventWindowIsAvailable() {
withDefaultsSuite { defaults in
defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertTrue(
ShortcutHintModifierPolicy.shouldShowHints(
for: [.command],
hostWindowNumber: 42,
hostWindowIsKey: true,
eventWindowNumber: nil,
keyWindowNumber: 42,
defaults: defaults
)
)
XCTAssertFalse(
ShortcutHintModifierPolicy.shouldShowHints(
for: [.command],
hostWindowNumber: 42,
hostWindowIsKey: true,
eventWindowNumber: nil,
keyWindowNumber: 7,
defaults: defaults
)
)
XCTAssertTrue(
ShortcutHintModifierPolicy.shouldShowHints(
for: [.command],
hostWindowNumber: 42,
hostWindowIsKey: true,
eventWindowNumber: nil,
keyWindowNumber: 42,
defaults: defaults
)
)
XCTAssertFalse(
ShortcutHintModifierPolicy.shouldShowHints(
for: [.control],
hostWindowNumber: 42,
hostWindowIsKey: true,
eventWindowNumber: nil,
keyWindowNumber: 42,
defaults: defaults
)
)
}
}
private func withDefaultsSuite(_ body: (UserDefaults) -> Void) {
let suiteName = "ShortcutHintModifierPolicyTests-\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create defaults suite")
return
}
defaults.removePersistentDomain(forName: suiteName)
body(defaults)
defaults.removePersistentDomain(forName: suiteName)
}
}
final class ShortcutHintDebugSettingsTests: XCTestCase {
func testClampKeepsValuesWithinSupportedRange() {
XCTAssertEqual(ShortcutHintDebugSettings.clamped(0.0), 0.0)
XCTAssertEqual(ShortcutHintDebugSettings.clamped(4.0), 4.0)
XCTAssertEqual(ShortcutHintDebugSettings.clamped(-100.0), ShortcutHintDebugSettings.offsetRange.lowerBound)
XCTAssertEqual(ShortcutHintDebugSettings.clamped(100.0), ShortcutHintDebugSettings.offsetRange.upperBound)
}
func testDefaultOffsetsMatchCurrentBadgePlacements() {
XCTAssertEqual(ShortcutHintDebugSettings.defaultSidebarHintX, 0.0)
XCTAssertEqual(ShortcutHintDebugSettings.defaultSidebarHintY, 0.0)
XCTAssertEqual(ShortcutHintDebugSettings.defaultTitlebarHintX, 4.0)
XCTAssertEqual(ShortcutHintDebugSettings.defaultTitlebarHintY, 0.0)
XCTAssertEqual(ShortcutHintDebugSettings.defaultPaneHintX, 0.0)
XCTAssertEqual(ShortcutHintDebugSettings.defaultPaneHintY, 0.0)
XCTAssertFalse(ShortcutHintDebugSettings.defaultAlwaysShowHints)
XCTAssertTrue(ShortcutHintDebugSettings.defaultShowHintsOnCommandHold)
}
func testShowHintsOnCommandHoldSettingRespectsStoredValue() {
let suiteName = "ShortcutHintDebugSettingsTests-\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create defaults suite")
return
}
defaults.removePersistentDomain(forName: suiteName)
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.removeObject(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertTrue(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults))
defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertFalse(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults))
defaults.set(true, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
XCTAssertTrue(ShortcutHintDebugSettings.showHintsOnCommandHoldEnabled(defaults: defaults))
}
func testResetVisibilityDefaultsRestoresAlwaysShowAndCommandHoldFlags() {
let suiteName = "ShortcutHintDebugSettingsTests-\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create defaults suite")
return
}
defaults.removePersistentDomain(forName: suiteName)
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(true, forKey: ShortcutHintDebugSettings.alwaysShowHintsKey)
defaults.set(false, forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey)
ShortcutHintDebugSettings.resetVisibilityDefaults(defaults: defaults)
XCTAssertEqual(
defaults.object(forKey: ShortcutHintDebugSettings.alwaysShowHintsKey) as? Bool,
ShortcutHintDebugSettings.defaultAlwaysShowHints
)
XCTAssertEqual(
defaults.object(forKey: ShortcutHintDebugSettings.showHintsOnCommandHoldKey) as? Bool,
ShortcutHintDebugSettings.defaultShowHintsOnCommandHold
)
}
}
final class DevBuildBannerDebugSettingsTests: XCTestCase {
func testShowSidebarBannerDefaultsToVisible() {
let suiteName = "DevBuildBannerDebugSettingsTests.Default.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.removeObject(forKey: DevBuildBannerDebugSettings.sidebarBannerVisibleKey)
XCTAssertTrue(DevBuildBannerDebugSettings.showSidebarBanner(defaults: defaults))
}
func testShowSidebarBannerRespectsStoredValue() {
let suiteName = "DevBuildBannerDebugSettingsTests.Stored.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(false, forKey: DevBuildBannerDebugSettings.sidebarBannerVisibleKey)
XCTAssertFalse(DevBuildBannerDebugSettings.showSidebarBanner(defaults: defaults))
defaults.set(true, forKey: DevBuildBannerDebugSettings.sidebarBannerVisibleKey)
XCTAssertTrue(DevBuildBannerDebugSettings.showSidebarBanner(defaults: defaults))
}
}
final class ShortcutHintLanePlannerTests: XCTestCase {
func testAssignLanesKeepsSeparatedIntervalsOnSingleLane() {
let intervals: [ClosedRange<CGFloat>] = [0...20, 28...40, 48...64]
XCTAssertEqual(ShortcutHintLanePlanner.assignLanes(for: intervals, minSpacing: 4), [0, 0, 0])
}
func testAssignLanesStacksOverlappingIntervalsIntoAdditionalLanes() {
let intervals: [ClosedRange<CGFloat>] = [0...20, 18...34, 22...38, 40...56]
XCTAssertEqual(ShortcutHintLanePlanner.assignLanes(for: intervals, minSpacing: 4), [0, 1, 2, 0])
}
}
final class ShortcutHintHorizontalPlannerTests: XCTestCase {
func testAssignRightEdgesResolvesOverlapWithMinimumSpacing() {
let intervals: [ClosedRange<CGFloat>] = [0...20, 18...34, 30...46]
let rightEdges = ShortcutHintHorizontalPlanner.assignRightEdges(for: intervals, minSpacing: 6)
XCTAssertEqual(rightEdges.count, intervals.count)
let adjustedIntervals = zip(intervals, rightEdges).map { interval, rightEdge in
let width = interval.upperBound - interval.lowerBound
return (rightEdge - width)...rightEdge
}
XCTAssertGreaterThanOrEqual(adjustedIntervals[1].lowerBound - adjustedIntervals[0].upperBound, 6)
XCTAssertGreaterThanOrEqual(adjustedIntervals[2].lowerBound - adjustedIntervals[1].upperBound, 6)
}
func testAssignRightEdgesKeepsAlreadySeparatedIntervalsInPlace() {
let intervals: [ClosedRange<CGFloat>] = [0...12, 20...32, 40...52]
let rightEdges = ShortcutHintHorizontalPlanner.assignRightEdges(for: intervals, minSpacing: 4)
XCTAssertEqual(rightEdges, [12, 32, 52])
}
}
final class LastSurfaceCloseShortcutSettingsTests: XCTestCase {
func testDefaultClosesWorkspace() {
let suiteName = "LastSurfaceCloseShortcutSettingsTests.Default.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
XCTAssertTrue(LastSurfaceCloseShortcutSettings.closesWorkspace(defaults: defaults))
}
func testStoredTrueClosesWorkspace() {
let suiteName = "LastSurfaceCloseShortcutSettingsTests.Enabled.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(true, forKey: LastSurfaceCloseShortcutSettings.key)
XCTAssertTrue(LastSurfaceCloseShortcutSettings.closesWorkspace(defaults: defaults))
}
func testStoredFalseKeepsWorkspaceOpen() {
let suiteName = "LastSurfaceCloseShortcutSettingsTests.Disabled.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(false, forKey: LastSurfaceCloseShortcutSettings.key)
XCTAssertFalse(LastSurfaceCloseShortcutSettings.closesWorkspace(defaults: defaults))
}
}
final class AppearanceSettingsTests: XCTestCase {
func testResolvedModeDefaultsToSystemWhenUnset() {
let suiteName = "AppearanceSettingsTests.Default.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.removeObject(forKey: AppearanceSettings.appearanceModeKey)
let resolved = AppearanceSettings.resolvedMode(defaults: defaults)
XCTAssertEqual(resolved, .system)
XCTAssertEqual(defaults.string(forKey: AppearanceSettings.appearanceModeKey), AppearanceMode.system.rawValue)
}
}
final class QuitWarningSettingsTests: XCTestCase {
func testDefaultWarnBeforeQuitIsEnabledWhenUnset() {
let suiteName = "QuitWarningSettingsTests.Default.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.removeObject(forKey: QuitWarningSettings.warnBeforeQuitKey)
XCTAssertTrue(QuitWarningSettings.isEnabled(defaults: defaults))
}
func testStoredPreferenceOverridesDefault() {
let suiteName = "QuitWarningSettingsTests.Stored.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
XCTFail("Failed to create isolated UserDefaults suite")
return
}
defer { defaults.removePersistentDomain(forName: suiteName) }
defaults.set(false, forKey: QuitWarningSettings.warnBeforeQuitKey)
XCTAssertFalse(QuitWarningSettings.isEnabled(defaults: defaults))
defaults.set(true, forKey: QuitWarningSettings.warnBeforeQuitKey)
XCTAssertTrue(QuitWarningSettings.isEnabled(defaults: defaults))
}
}
final class UpdateChannelSettingsTests: XCTestCase {
func testResolvedFeedFallsBackWhenInfoFeedMissing() {
let resolved = UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: nil)
XCTAssertEqual(resolved.url, UpdateFeedResolver.fallbackFeedURL)
XCTAssertFalse(resolved.isNightly)
XCTAssertTrue(resolved.usedFallback)
}
func testResolvedFeedFallsBackWhenInfoFeedEmpty() {
let resolved = UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: "")
XCTAssertEqual(resolved.url, UpdateFeedResolver.fallbackFeedURL)
XCTAssertFalse(resolved.isNightly)
XCTAssertTrue(resolved.usedFallback)
}
func testResolvedFeedUsesInfoFeedForStableChannel() {
let infoFeed = "https://example.com/custom/appcast.xml"
let resolved = UpdateFeedResolver.resolvedFeedURLString(infoFeedURL: infoFeed)
XCTAssertEqual(resolved.url, infoFeed)
XCTAssertFalse(resolved.isNightly)
XCTAssertFalse(resolved.usedFallback)
}
func testResolvedFeedDetectsNightlyFromInfoFeedURL() {
let resolved = UpdateFeedResolver.resolvedFeedURLString(
infoFeedURL: "https://example.com/nightly/appcast.xml"
)
XCTAssertEqual(resolved.url, "https://example.com/nightly/appcast.xml")
XCTAssertTrue(resolved.isNightly)
XCTAssertFalse(resolved.usedFallback)
}
}
final class UpdateSettingsTests: XCTestCase {
func testApplyEnablesAutomaticChecksAndDailySchedule() {
let defaults = makeDefaults()
UpdateSettings.apply(to: defaults)
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.automaticChecksKey))
XCTAssertEqual(defaults.double(forKey: UpdateSettings.scheduledCheckIntervalKey), UpdateSettings.scheduledCheckInterval)
XCTAssertFalse(defaults.bool(forKey: UpdateSettings.automaticallyUpdateKey))
XCTAssertFalse(defaults.bool(forKey: UpdateSettings.sendProfileInfoKey))
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.migrationKey))
}
func testApplyRepairsLegacyDisabledAutomaticChecksOnce() {
let defaults = makeDefaults()
defaults.set(false, forKey: UpdateSettings.automaticChecksKey)
defaults.set(0, forKey: UpdateSettings.scheduledCheckIntervalKey)
defaults.set(true, forKey: UpdateSettings.automaticallyUpdateKey)
UpdateSettings.apply(to: defaults)
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.automaticChecksKey))
XCTAssertEqual(defaults.double(forKey: UpdateSettings.scheduledCheckIntervalKey), UpdateSettings.scheduledCheckInterval)
XCTAssertTrue(defaults.bool(forKey: UpdateSettings.automaticallyUpdateKey))
defaults.set(false, forKey: UpdateSettings.automaticChecksKey)
UpdateSettings.apply(to: defaults)
XCTAssertFalse(defaults.bool(forKey: UpdateSettings.automaticChecksKey))
}
private func makeDefaults() -> UserDefaults {
let suiteName = "UpdateSettingsTests.\(UUID().uuidString)"
guard let defaults = UserDefaults(suiteName: suiteName) else {
fatalError("Failed to create isolated UserDefaults suite")
}
defaults.removePersistentDomain(forName: suiteName)
return defaults
}
}
final class UpdateViewModelPresentationTests: XCTestCase {
func testDetectedBackgroundUpdateShowsPillWhileIdle() {
let viewModel = UpdateViewModel()
viewModel.detectedUpdateVersion = "9.9.9"
XCTAssertTrue(viewModel.showsPill)
XCTAssertTrue(viewModel.showsDetectedBackgroundUpdate)
XCTAssertEqual(viewModel.text, "Update Available: 9.9.9")
XCTAssertEqual(viewModel.iconName, "shippingbox.fill")
}
func testActiveUpdateStateTakesPrecedenceOverDetectedBackgroundVersion() {
let viewModel = UpdateViewModel()
viewModel.detectedUpdateVersion = "9.9.9"
viewModel.state = .checking(.init(cancel: {}))
XCTAssertTrue(viewModel.showsPill)
XCTAssertFalse(viewModel.showsDetectedBackgroundUpdate)
XCTAssertEqual(viewModel.text, "Checking for Updates…")
}
}
@MainActor
final class CommandPaletteOverlayPromotionPolicyTests: XCTestCase {
func testShouldPromoteWhenBecomingVisible() {
XCTAssertTrue(
CommandPaletteOverlayPromotionPolicy.shouldPromote(
previouslyVisible: false,
isVisible: true
)
)
}
func testShouldNotPromoteWhenAlreadyVisible() {
XCTAssertFalse(
CommandPaletteOverlayPromotionPolicy.shouldPromote(
previouslyVisible: true,
isVisible: true
)
)
}
func testShouldNotPromoteWhenHidden() {
XCTAssertFalse(
CommandPaletteOverlayPromotionPolicy.shouldPromote(
previouslyVisible: true,
isVisible: false
)
)
XCTAssertFalse(
CommandPaletteOverlayPromotionPolicy.shouldPromote(
previouslyVisible: false,
isVisible: false
)
)
}
}