Slim down app and test suite
This commit is contained in:
@@ -4,21 +4,6 @@ import XCTest
|
||||
@testable import ClipBored
|
||||
|
||||
final class ClipboardPanelControllerTests: XCTestCase {
|
||||
private var tempURLs: [URL] = []
|
||||
private var defaultsSuites: [String] = []
|
||||
|
||||
override func tearDown() {
|
||||
for url in tempURLs {
|
||||
try? FileManager.default.removeItem(at: url)
|
||||
}
|
||||
tempURLs.removeAll()
|
||||
for suite in defaultsSuites {
|
||||
UserDefaults(suiteName: suite)?.removePersistentDomain(forName: suite)
|
||||
}
|
||||
defaultsSuites.removeAll()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testPanelFrameUsesRightSideShelfByDefault() {
|
||||
let screenFrame = CGRect(x: -1200, y: -200, width: 1200, height: 800)
|
||||
let frames = ClipboardPanelController.panelFrames(forScreenFrame: screenFrame, visibleFrame: screenFrame)
|
||||
@@ -31,7 +16,7 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
XCTAssertEqual(frames.hidden.minY, frames.shown.minY)
|
||||
}
|
||||
|
||||
func testOpenScreenSelectionUsesStatusClickScreenWhenProvided() {
|
||||
func testOpenScreenSelectionPrefersExplicitThenPointerScreen() {
|
||||
XCTAssertEqual(
|
||||
ClipboardPanelController.selectedOpenScreen(
|
||||
explicit: "status-item-screen",
|
||||
@@ -41,9 +26,6 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
),
|
||||
"status-item-screen"
|
||||
)
|
||||
}
|
||||
|
||||
func testOpenScreenSelectionFallsBackToPointerForGlobalShortcut() {
|
||||
XCTAssertEqual(
|
||||
ClipboardPanelController.selectedOpenScreen(
|
||||
explicit: Optional<String>.none,
|
||||
@@ -141,31 +123,6 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
XCTAssertEqual(frames.hidden.minX, screenFrame.maxX + 1)
|
||||
}
|
||||
|
||||
func testPanelFramePlanningIsDeterministicAcrossRepeatedToggles() {
|
||||
let screenFrame = CGRect(x: -1512, y: -120, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: -1512, y: -24, width: 1512, height: 861)
|
||||
let first = ClipboardPanelController.panelFrames(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
|
||||
for _ in 0..<50 {
|
||||
let frames = ClipboardPanelController.panelFrames(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
XCTAssertEqual(frames.shown, first.shown)
|
||||
XCTAssertEqual(frames.hidden, first.hidden)
|
||||
XCTAssertEqual(frames.hidden.minX, frames.shown.maxX + 1)
|
||||
}
|
||||
}
|
||||
|
||||
func testPanelAnimationProfileStaysShortForSixtyFpsFeel() {
|
||||
let profile = ClipboardPanelController.animationProfile
|
||||
|
||||
XCTAssertEqual(profile.showDuration, 0.22)
|
||||
XCTAssertEqual(profile.hideDuration, 0.16)
|
||||
XCTAssertEqual(profile.reflowDuration, 0.18)
|
||||
XCTAssertLessThanOrEqual(profile.showDuration * 60, 14)
|
||||
XCTAssertLessThanOrEqual(profile.hideDuration * 60, 10)
|
||||
XCTAssertLessThanOrEqual(profile.reflowDuration * 60, 11)
|
||||
XCTAssertEqual(profile.easing, .easeInEaseOut)
|
||||
}
|
||||
|
||||
func testPanelCollectionBehaviorStaysLocalToActiveSpaceAndSupportsFullscreen() {
|
||||
let behavior = ClipboardPanelController.panelCollectionBehavior
|
||||
|
||||
@@ -175,69 +132,6 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
XCTAssertFalse(behavior.contains(.canJoinAllSpaces))
|
||||
}
|
||||
|
||||
func testShowingPanelClearsAndCollapsesStaleSearchState() throws {
|
||||
let screen = try XCTUnwrap(NSScreen.screens.first)
|
||||
let (controller, _) = makeController(preferredScreen: screen)
|
||||
|
||||
controller.debugSetSearchFieldText("stale query")
|
||||
drainMainQueue()
|
||||
XCTAssertEqual(controller.debugSearchFieldText, "stale query")
|
||||
XCTAssertEqual(controller.debugSearchFieldWidth, 238, accuracy: 1)
|
||||
XCTAssertEqual(controller.debugSearchFieldPlaceholderText, "Search clips")
|
||||
XCTAssertTrue(controller.debugSearchFieldIsVisible)
|
||||
XCTAssertTrue(controller.debugSearchIconButtonIsVisible)
|
||||
|
||||
controller.show(preferredScreen: screen)
|
||||
defer { controller.hide(immediate: true) }
|
||||
drainMainQueue()
|
||||
|
||||
XCTAssertEqual(controller.debugSearchFieldText, "")
|
||||
XCTAssertEqual(controller.debugSearchFieldWidth, 30, accuracy: 1)
|
||||
XCTAssertEqual(controller.debugSearchFieldPlaceholderText, "")
|
||||
XCTAssertFalse(controller.debugSearchFieldIsVisible)
|
||||
XCTAssertTrue(controller.debugSearchIconButtonIsVisible)
|
||||
XCTAssertFalse(controller.debugIsSearchFieldEditing)
|
||||
}
|
||||
|
||||
func testReflowPlanKeepsOpenPanelOnRightSideWhenBottomDockIsVisible() {
|
||||
let screenFrame = CGRect(x: 0, y: 0, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: 0, y: 112, width: 1512, height: 845)
|
||||
|
||||
let plan = ClipboardPanelController.reflowPlan(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
|
||||
XCTAssertEqual(plan.frame.maxX, visibleFrame.maxX)
|
||||
XCTAssertEqual(plan.frame.minY, screenFrame.minY)
|
||||
XCTAssertEqual(plan.frame.maxY, visibleFrame.maxY)
|
||||
XCTAssertEqual(plan.frame.width, 336)
|
||||
XCTAssertEqual(plan.bottomSafeInset, 20)
|
||||
}
|
||||
|
||||
func testReflowPlanTouchesScreenBottomWhenBottomDockIsAutoHidden() {
|
||||
let screenFrame = CGRect(x: 0, y: 0, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: 0, y: 4, width: 1512, height: 953)
|
||||
|
||||
let plan = ClipboardPanelController.reflowPlan(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
|
||||
XCTAssertEqual(plan.frame.maxX, visibleFrame.maxX)
|
||||
XCTAssertEqual(plan.frame.minY, screenFrame.minY)
|
||||
XCTAssertEqual(plan.frame.maxY, visibleFrame.maxY)
|
||||
XCTAssertEqual(plan.frame.width, 336)
|
||||
XCTAssertEqual(plan.bottomSafeInset, 18)
|
||||
}
|
||||
|
||||
func testReflowPlanTracksSideDockVisibleFrameWithoutBottomInsetInflation() {
|
||||
let screenFrame = CGRect(x: 0, y: 0, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: 86, y: 0, width: 1426, height: 957)
|
||||
|
||||
let plan = ClipboardPanelController.reflowPlan(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
|
||||
XCTAssertEqual(plan.frame.maxX, visibleFrame.maxX)
|
||||
XCTAssertEqual(plan.frame.minX, visibleFrame.maxX - 336)
|
||||
XCTAssertEqual(plan.frame.minY, screenFrame.minY)
|
||||
XCTAssertEqual(plan.frame.height, 957)
|
||||
XCTAssertEqual(plan.bottomSafeInset, 18)
|
||||
}
|
||||
|
||||
func testPanelFrameUsesConfiguredRightSideShelf() {
|
||||
let screenFrame = CGRect(x: 0, y: 0, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: 0, y: 96, width: 1512, height: 861)
|
||||
@@ -289,22 +183,20 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
XCTAssertEqual(frames.shown.width, 336)
|
||||
}
|
||||
|
||||
func testContentBottomInsetReservesBottomDockSpace() {
|
||||
func testContentBottomInsetHandlesBottomAndSideDock() {
|
||||
let screenFrame = CGRect(x: 0, y: 0, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: 0, y: 96, width: 1512, height: 861)
|
||||
|
||||
let inset = ClipboardPanelController.contentBottomInset(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
|
||||
XCTAssertEqual(inset, 20)
|
||||
}
|
||||
|
||||
func testContentBottomInsetUsesMinimumWhenDockIsNotAtBottom() {
|
||||
let screenFrame = CGRect(x: 0, y: 0, width: 1512, height: 982)
|
||||
let visibleFrame = CGRect(x: 80, y: 0, width: 1432, height: 957)
|
||||
|
||||
let inset = ClipboardPanelController.contentBottomInset(forScreenFrame: screenFrame, visibleFrame: visibleFrame)
|
||||
|
||||
XCTAssertEqual(inset, 18)
|
||||
XCTAssertEqual(
|
||||
ClipboardPanelController.contentBottomInset(
|
||||
forScreenFrame: screenFrame,
|
||||
visibleFrame: CGRect(x: 80, y: 0, width: 1432, height: 957)
|
||||
),
|
||||
18
|
||||
)
|
||||
}
|
||||
|
||||
func testPanelSharingTypeHidesWindowFromScreenCaptureWhenEnabled() {
|
||||
@@ -326,41 +218,38 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCommandNumberShortcutsMapToQuickPasteSlots() {
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 18, modifiers: .command), 0)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 19, modifiers: .command), 1)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 20, modifiers: .command), 2)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 21, modifiers: .command), 3)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 23, modifiers: .command), 4)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 22, modifiers: .command), 5)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 26, modifiers: .command), 6)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 28, modifiers: .command), 7)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPasteIndex(forKeyCode: 25, modifiers: .command), 8)
|
||||
assertShortcutMappings([
|
||||
(18, .command, 0), (19, .command, 1), (20, .command, 2),
|
||||
(21, .command, 3), (23, .command, 4), (22, .command, 5),
|
||||
(26, .command, 6), (28, .command, 7), (25, .command, 8)
|
||||
], using: ClipboardPanelController.quickPasteIndex)
|
||||
}
|
||||
|
||||
func testShiftCommandNumberShortcutsMapToPlainTextQuickPasteSlots() {
|
||||
XCTAssertEqual(ClipboardPanelController.quickPastePlainTextIndex(forKeyCode: 18, modifiers: [.command, .shift]), 0)
|
||||
XCTAssertEqual(ClipboardPanelController.quickPastePlainTextIndex(forKeyCode: 25, modifiers: [.command, .shift]), 8)
|
||||
XCTAssertNil(ClipboardPanelController.quickPastePlainTextIndex(forKeyCode: 18, modifiers: .command))
|
||||
XCTAssertNil(ClipboardPanelController.quickPastePlainTextIndex(forKeyCode: 18, modifiers: [.command, .option, .shift]))
|
||||
assertShortcutMappings([
|
||||
(18, [.command, .shift], 0),
|
||||
(25, [.command, .shift], 8),
|
||||
(18, .command, nil),
|
||||
(18, [.command, .option, .shift], nil)
|
||||
], using: ClipboardPanelController.quickPastePlainTextIndex)
|
||||
}
|
||||
|
||||
func testCommandOptionNumberShortcutsMapToCollections() {
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 18, modifiers: [.command, .option]), .mostRecent)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 19, modifiers: [.command, .option]), .mostUsed)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 20, modifiers: [.command, .option]), .text)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 21, modifiers: [.command, .option]), .links)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 23, modifiers: [.command, .option]), .images)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 22, modifiers: [.command, .option]), .files)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 26, modifiers: [.command, .option]), .pinned)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 28, modifiers: [.command, .option]), .audio)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 25, modifiers: [.command, .option]), .colors)
|
||||
XCTAssertEqual(ClipboardPanelController.collectionShortcutMode(forKeyCode: 29, modifiers: [.command, .option]), .code)
|
||||
}
|
||||
|
||||
func testCollectionShortcutsRequireCommandOptionSoQuickPasteKeepsCommandNumbers() {
|
||||
XCTAssertNil(ClipboardPanelController.collectionShortcutMode(forKeyCode: 18, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.collectionShortcutMode(forKeyCode: 18, modifiers: .command))
|
||||
XCTAssertNil(ClipboardPanelController.collectionShortcutMode(forKeyCode: 29, modifiers: .command))
|
||||
assertShortcutMappings([
|
||||
(18, [.command, .option], .mostRecent),
|
||||
(19, [.command, .option], .mostUsed),
|
||||
(20, [.command, .option], .text),
|
||||
(21, [.command, .option], .links),
|
||||
(23, [.command, .option], .images),
|
||||
(22, [.command, .option], .files),
|
||||
(26, [.command, .option], .pinned),
|
||||
(28, [.command, .option], .audio),
|
||||
(25, [.command, .option], .colors),
|
||||
(29, [.command, .option], .code),
|
||||
(18, [], nil),
|
||||
(18, .command, nil),
|
||||
(29, .command, nil)
|
||||
], using: ClipboardPanelController.collectionShortcutMode)
|
||||
}
|
||||
|
||||
func testSearchFieldSpacePreviewShortcutRequiresEmptySearchAndNoModifiers() {
|
||||
@@ -372,60 +261,35 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testNavigationShortcutsMapToShelfMovement() {
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 115, modifiers: []), .first)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 119, modifiers: []), .last)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 124, modifiers: []), .next)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 121, modifiers: []), .pageNext)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 116, modifiers: []), .pagePrevious)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 123, modifiers: []), .previous)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 126, modifiers: .command), .first)
|
||||
XCTAssertEqual(ClipboardPanelController.navigationShortcutAction(forKeyCode: 125, modifiers: .command), .last)
|
||||
}
|
||||
|
||||
func testNavigationShortcutsRejectUnsupportedModifiers() {
|
||||
XCTAssertNil(ClipboardPanelController.navigationShortcutAction(forKeyCode: 124, modifiers: .command))
|
||||
XCTAssertNil(ClipboardPanelController.navigationShortcutAction(forKeyCode: 126, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.navigationShortcutAction(forKeyCode: 125, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.navigationShortcutAction(forKeyCode: 121, modifiers: .shift))
|
||||
XCTAssertNil(ClipboardPanelController.navigationShortcutAction(forKeyCode: 35, modifiers: []))
|
||||
assertShortcutMappings([
|
||||
(115, [], .first), (119, [], .last), (124, [], .next),
|
||||
(121, [], .pageNext), (116, [], .pagePrevious), (123, [], .previous),
|
||||
(126, .command, .first), (125, .command, .last),
|
||||
(124, .command, nil), (126, [], nil), (125, [], nil),
|
||||
(121, .shift, nil), (35, [], nil)
|
||||
], using: ClipboardPanelController.navigationShortcutAction)
|
||||
}
|
||||
|
||||
func testSelectionShortcutsMapToRangeAndSelectAllActions() {
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 0, modifiers: .command), .selectAll)
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 115, modifiers: .shift), .extendFirst)
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 119, modifiers: .shift), .extendLast)
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 124, modifiers: .shift), .extendNext)
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 121, modifiers: .shift), .extendPageNext)
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 116, modifiers: .shift), .extendPagePrevious)
|
||||
XCTAssertEqual(ClipboardPanelController.selectionShortcutAction(forKeyCode: 123, modifiers: .shift), .extendPrevious)
|
||||
}
|
||||
|
||||
func testSelectionShortcutsRequireExactModifierSets() {
|
||||
XCTAssertNil(ClipboardPanelController.selectionShortcutAction(forKeyCode: 0, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.selectionShortcutAction(forKeyCode: 0, modifiers: [.command, .shift]))
|
||||
XCTAssertNil(ClipboardPanelController.selectionShortcutAction(forKeyCode: 124, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.selectionShortcutAction(forKeyCode: 124, modifiers: [.command, .shift]))
|
||||
assertShortcutMappings([
|
||||
(0, .command, .selectAll),
|
||||
(115, .shift, .extendFirst), (119, .shift, .extendLast),
|
||||
(124, .shift, .extendNext), (121, .shift, .extendPageNext),
|
||||
(116, .shift, .extendPagePrevious), (123, .shift, .extendPrevious),
|
||||
(0, [], nil), (0, [.command, .shift], nil),
|
||||
(124, [], nil), (124, [.command, .shift], nil)
|
||||
], using: ClipboardPanelController.selectionShortcutAction)
|
||||
}
|
||||
|
||||
func testCommandActionShortcutsMapToSelectedClipActions() {
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 8, modifiers: .command), .copy)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 14, modifiers: .command), .edit)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 3, modifiers: .command), .focusSearch)
|
||||
XCTAssertNil(ClipboardPanelController.commandShortcutAction(forKeyCode: 45, modifiers: .command))
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 5, modifiers: .command), .showInClipboard)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 16, modifiers: .command), .preview)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 31, modifiers: .command), .open)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 15, modifiers: .command), .rename)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 17, modifiers: .command), .toggleCapturePause)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 6, modifiers: .command), .undoDelete)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 123, modifiers: .command), .previousCollection)
|
||||
XCTAssertEqual(ClipboardPanelController.commandShortcutAction(forKeyCode: 124, modifiers: .command), .nextCollection)
|
||||
}
|
||||
|
||||
func testCommandActionShortcutsRequireCommandOnlySoSearchTypingIsUntouched() {
|
||||
XCTAssertNil(ClipboardPanelController.commandShortcutAction(forKeyCode: 8, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.commandShortcutAction(forKeyCode: 8, modifiers: [.command, .shift]))
|
||||
XCTAssertNil(ClipboardPanelController.commandShortcutAction(forKeyCode: 9, modifiers: .command))
|
||||
assertShortcutMappings([
|
||||
(8, .command, .copy), (14, .command, .edit), (3, .command, .focusSearch),
|
||||
(45, .command, nil), (5, .command, .showInClipboard),
|
||||
(16, .command, .preview), (31, .command, .open), (15, .command, .rename),
|
||||
(17, .command, .toggleCapturePause), (6, .command, .undoDelete),
|
||||
(123, .command, .previousCollection), (124, .command, .nextCollection),
|
||||
(8, [], nil), (8, [.command, .shift], nil), (9, .command, nil)
|
||||
], using: ClipboardPanelController.commandShortcutAction)
|
||||
}
|
||||
|
||||
func testSettingsShortcutMatchesOnlyItsExactLocalBinding() {
|
||||
@@ -455,67 +319,32 @@ final class ClipboardPanelControllerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testModifiedShortcutsMapToPanelActions() {
|
||||
XCTAssertEqual(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 36, modifiers: .shift), .pastePlainText)
|
||||
XCTAssertEqual(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 1, modifiers: [.command, .shift]), .toggleStack)
|
||||
XCTAssertEqual(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 8, modifiers: [.command, .shift]), .toggleStackCapture)
|
||||
XCTAssertEqual(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 9, modifiers: [.command, .shift]), .pastePlainText)
|
||||
XCTAssertEqual(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 45, modifiers: [.command, .shift]), .newCollection)
|
||||
XCTAssertEqual(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 36, modifiers: [.command, .shift]), .pasteStackNext)
|
||||
assertShortcutMappings([
|
||||
(36, .shift, .pastePlainText),
|
||||
(1, [.command, .shift], .toggleStack),
|
||||
(8, [.command, .shift], .toggleStackCapture),
|
||||
(9, [.command, .shift], .pastePlainText),
|
||||
(45, [.command, .shift], .newCollection),
|
||||
(36, [.command, .shift], .pasteStackNext),
|
||||
(36, [], nil), (8, .shift, nil), (8, .command, nil),
|
||||
(8, [.command, .option, .shift], nil), (31, [.command, .shift], nil)
|
||||
], using: ClipboardPanelController.modifiedShortcutAction)
|
||||
}
|
||||
|
||||
func testModifiedShortcutsRequireCommandShiftOnly() {
|
||||
XCTAssertNil(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 36, modifiers: []))
|
||||
XCTAssertNil(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 8, modifiers: .shift))
|
||||
XCTAssertNil(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 8, modifiers: .command))
|
||||
XCTAssertNil(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 8, modifiers: [.command, .option, .shift]))
|
||||
XCTAssertNil(ClipboardPanelController.modifiedShortcutAction(forKeyCode: 31, modifiers: [.command, .shift]))
|
||||
}
|
||||
|
||||
private func makeController(preferredScreen: NSScreen) -> (ClipboardPanelController, ClipboardStore) {
|
||||
let settings = makeSettings()
|
||||
let encryptionService = ClipboardEncryptionService(keyProvider: { nil })
|
||||
let cacheService = ClipboardCacheService(
|
||||
baseURL: makeTempDirectory(),
|
||||
encryptionService: encryptionService
|
||||
)
|
||||
let store = ClipboardStore(
|
||||
settings: settings,
|
||||
cacheService: cacheService,
|
||||
baseURL: makeTempDirectory(),
|
||||
encryptionService: encryptionService
|
||||
)
|
||||
let controller = ClipboardPanelController(
|
||||
store: store,
|
||||
settings: settings,
|
||||
cacheService: cacheService,
|
||||
preferredScreen: { preferredScreen }
|
||||
)
|
||||
return (controller, store)
|
||||
}
|
||||
|
||||
private func makeSettings() -> SettingsModel {
|
||||
let suite = "com.clipbored.controllertest.\(UUID().uuidString)"
|
||||
defaultsSuites.append(suite)
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
let settings = SettingsModel(defaults: defaults)
|
||||
settings.maxHistoryItems = 10
|
||||
settings.historyRetention = .forever
|
||||
settings.pruneDuplicates = false
|
||||
return settings
|
||||
}
|
||||
|
||||
private func makeTempDirectory() -> URL {
|
||||
let directory = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("clipbored-controllertest")
|
||||
.appendingPathComponent(UUID().uuidString)
|
||||
try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
|
||||
tempURLs.append(directory)
|
||||
return directory
|
||||
}
|
||||
|
||||
private func drainMainQueue() {
|
||||
for _ in 0..<24 {
|
||||
RunLoop.main.run(until: Date().addingTimeInterval(0.01))
|
||||
private func assertShortcutMappings<Value: Equatable>(
|
||||
_ cases: [(keyCode: UInt16, modifiers: NSEvent.ModifierFlags, expected: Value?)],
|
||||
using mapping: (UInt16, NSEvent.ModifierFlags) -> Value?,
|
||||
file: StaticString = #filePath,
|
||||
line: UInt = #line
|
||||
) {
|
||||
for testCase in cases {
|
||||
XCTAssertEqual(
|
||||
mapping(testCase.keyCode, testCase.modifiers),
|
||||
testCase.expected,
|
||||
"keyCode \(testCase.keyCode), modifiers \(testCase.modifiers.rawValue)",
|
||||
file: file,
|
||||
line: line
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,294 +39,6 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(pinnedOnly.map(\.payload), ["four", "one"])
|
||||
}
|
||||
|
||||
func testSelectingCollectionWithEmptySearchUsesIndexedVisibleItems() {
|
||||
let settings = makeSettings()
|
||||
settings.maxHistoryItems = 260
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
|
||||
for index in 0..<220 {
|
||||
store.upsert(makeTextItem("outside indexed category \(index)", createdAt: Date(timeIntervalSince1970: Double(index))))
|
||||
}
|
||||
for index in 0..<30 {
|
||||
var item = makeTextItem("client indexed category \(index)", createdAt: Date(timeIntervalSince1970: Double(1_000 + index)))
|
||||
item.collectionName = "Client Work"
|
||||
store.upsert(item)
|
||||
}
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 250)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.selectCollection(named: "Client Work")
|
||||
|
||||
XCTAssertEqual(viewModel.visibleItems.count, 30)
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsFullScanCount, 0)
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsIndexedLookupCount, 1)
|
||||
XCTAssertEqual(viewModel.visibleItems.first?.payload, "client indexed category 29")
|
||||
}
|
||||
|
||||
func testRepeatedThirtyItemCollectionSelectionsStayOnIndexedFastPath() {
|
||||
let collectionCount = 60
|
||||
let itemsPerCollection = 30
|
||||
let settings = makeSettings()
|
||||
settings.maxHistoryItems = collectionCount * itemsPerCollection
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
let collectionNames = (0..<collectionCount).map { "Client Work \($0)" }
|
||||
|
||||
for (collectionIndex, collectionName) in collectionNames.enumerated() {
|
||||
for itemIndex in 0..<itemsPerCollection {
|
||||
var item = makeTextItem(
|
||||
"\(collectionName) clip \(itemIndex)",
|
||||
createdAt: Date(timeIntervalSince1970: Double((collectionIndex * itemsPerCollection) + itemIndex))
|
||||
)
|
||||
item.collectionName = collectionName
|
||||
store.upsert(item)
|
||||
}
|
||||
}
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: collectionCount * itemsPerCollection)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
let start = CFAbsoluteTimeGetCurrent()
|
||||
for collectionName in collectionNames {
|
||||
viewModel.selectCollection(named: collectionName)
|
||||
XCTAssertEqual(viewModel.visibleItems.count, itemsPerCollection)
|
||||
}
|
||||
let elapsed = CFAbsoluteTimeGetCurrent() - start
|
||||
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsFullScanCount, 0)
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsIndexedLookupCount, collectionCount)
|
||||
XCTAssertLessThan(elapsed, 0.20)
|
||||
}
|
||||
|
||||
func testCommandSelectedCategoriesUseIndexedUnionFastPath() {
|
||||
let settings = makeSettings()
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
|
||||
let text = makeTextItem("category text", createdAt: Date(timeIntervalSince1970: 100))
|
||||
let link = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .url,
|
||||
displayText: "category link",
|
||||
payload: "https://example.com/category",
|
||||
payloadHash: store.hashString("https://example.com/category"),
|
||||
createdAt: Date(timeIntervalSince1970: 200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 200),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
store.upsert(text)
|
||||
store.upsert(link)
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 2)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.selectSortMode(.text, extending: true)
|
||||
viewModel.selectSortMode(.links, extending: true)
|
||||
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["https://example.com/category", "category text"])
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsFullScanCount, 0)
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsIndexedLookupCount, 2)
|
||||
XCTAssertTrue(viewModel.isSortModeCategorySelected(.text))
|
||||
XCTAssertTrue(viewModel.isSortModeCategorySelected(.links))
|
||||
}
|
||||
|
||||
func testCommandSelectedCustomCollectionCombinesWithCategoryFilter() {
|
||||
let settings = makeSettings()
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
|
||||
var client = makeTextItem("client note", createdAt: Date(timeIntervalSince1970: 100))
|
||||
client.collectionName = "Client Work"
|
||||
let link = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .url,
|
||||
displayText: "category link",
|
||||
payload: "https://example.com/category",
|
||||
payloadHash: store.hashString("https://example.com/category"),
|
||||
createdAt: Date(timeIntervalSince1970: 200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 200),
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
store.upsert(client)
|
||||
store.upsert(link)
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 2)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.selectCollection(named: "Client Work")
|
||||
viewModel.selectSortMode(.links, extending: true)
|
||||
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["https://example.com/category", "client note"])
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsFullScanCount, 0)
|
||||
XCTAssertEqual(viewModel.debugVisibleItemsIndexedLookupCount, 2)
|
||||
XCTAssertTrue(viewModel.isCollectionCategorySelected(named: "Client Work"))
|
||||
XCTAssertTrue(viewModel.isSortModeCategorySelected(.links))
|
||||
}
|
||||
|
||||
func testEmptySearchCollectionCountSummaryUsesIndexedCounts() {
|
||||
let settings = makeSettings()
|
||||
settings.maxHistoryItems = 260
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
|
||||
for index in 0..<220 {
|
||||
store.upsert(makeTextItem("outside indexed count \(index)", createdAt: Date(timeIntervalSince1970: Double(index))))
|
||||
}
|
||||
for index in 0..<30 {
|
||||
var item = makeTextItem("client indexed count \(index)", createdAt: Date(timeIntervalSince1970: Double(1_000 + index)))
|
||||
item.collectionName = "Client Work"
|
||||
store.upsert(item)
|
||||
}
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 250)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
let counts = viewModel.collectionCountSummary()
|
||||
|
||||
XCTAssertEqual(counts.count(for: .mostRecent), 250)
|
||||
XCTAssertEqual(counts.count(for: .text), 250)
|
||||
XCTAssertEqual(counts.count(named: "Client Work"), 30)
|
||||
XCTAssertEqual(viewModel.debugCollectionCountFullScanCount, 0)
|
||||
XCTAssertEqual(viewModel.debugCollectionCountIndexedLookupCount, 1)
|
||||
}
|
||||
|
||||
func testSearchMatchesAreEvaluatedOnceAcrossVisibleItemsCountsAndCategoryChanges() {
|
||||
let settings = makeSettings()
|
||||
settings.maxHistoryItems = 120
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
|
||||
for index in 0..<100 {
|
||||
var item = makeTextItem(
|
||||
index.isMultiple(of: 5) ? "shared search needle \(index)" : "unmatched history \(index)",
|
||||
createdAt: Date(timeIntervalSince1970: Double(index))
|
||||
)
|
||||
if index.isMultiple(of: 2) {
|
||||
item.collectionName = "Client Work"
|
||||
}
|
||||
store.upsert(item)
|
||||
}
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 100)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.searchText = "needle"
|
||||
XCTAssertEqual(viewModel.visibleItems.count, 20)
|
||||
XCTAssertEqual(viewModel.debugSearchItemEvaluationCount, 100)
|
||||
XCTAssertEqual(viewModel.debugSearchDocumentBuildCount, 100)
|
||||
|
||||
let counts = viewModel.collectionCountSummary()
|
||||
XCTAssertEqual(counts.count(for: .text), 20)
|
||||
XCTAssertEqual(counts.count(named: "Client Work"), 10)
|
||||
XCTAssertEqual(viewModel.debugSearchItemEvaluationCount, 100)
|
||||
XCTAssertEqual(viewModel.debugSearchMatchCacheHitCount, 1)
|
||||
|
||||
viewModel.selectSortMode(.text, extending: true)
|
||||
viewModel.selectSortMode(.links, extending: true)
|
||||
|
||||
XCTAssertEqual(viewModel.visibleItems.count, 20)
|
||||
XCTAssertEqual(viewModel.debugSearchItemEvaluationCount, 100)
|
||||
XCTAssertEqual(viewModel.debugSearchMatchCacheHitCount, 3)
|
||||
|
||||
viewModel.searchText = "shared"
|
||||
|
||||
XCTAssertEqual(viewModel.visibleItems.count, 20)
|
||||
XCTAssertEqual(viewModel.debugSearchItemEvaluationCount, 200)
|
||||
XCTAssertEqual(viewModel.debugSearchDocumentBuildCount, 100)
|
||||
XCTAssertEqual(viewModel.debugSearchDocumentCacheHitCount, 100)
|
||||
}
|
||||
|
||||
func testEquivalentDiacriticSearchReusesVisibleAndCollectionCountCaches() {
|
||||
let settings = makeSettings()
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
store.upsert(makeTextItem("Résumé draft", createdAt: Date(timeIntervalSince1970: 100)))
|
||||
store.upsert(makeTextItem("Meeting note", createdAt: Date(timeIntervalSince1970: 200)))
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 2)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.searchText = "resume"
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["Résumé draft"])
|
||||
_ = viewModel.collectionCountSummary()
|
||||
XCTAssertEqual(viewModel.debugSearchItemEvaluationCount, 2)
|
||||
XCTAssertEqual(viewModel.debugCollectionCountFullScanCount, 1)
|
||||
|
||||
viewModel.searchText = " RÉSUMÉ "
|
||||
_ = viewModel.collectionCountSummary()
|
||||
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["Résumé draft"])
|
||||
XCTAssertEqual(viewModel.debugSearchItemEvaluationCount, 2)
|
||||
XCTAssertEqual(viewModel.debugCollectionCountFullScanCount, 1)
|
||||
}
|
||||
|
||||
func testCategoryFilterSelectionIsBuiltOncePerMutationAndReusedForChipStateQueries() {
|
||||
let settings = makeSettings()
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
store.upsert(makeTextItem("category state", createdAt: Date(timeIntervalSince1970: 100)))
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 1)
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.selectSortMode(.text, extending: true)
|
||||
XCTAssertEqual(viewModel.debugCategoryFilterSelectionBuildCount, 1)
|
||||
|
||||
for _ in 0..<20 {
|
||||
for mode in ClipboardSortMode.allCases {
|
||||
_ = viewModel.isSortModeCategorySelected(mode)
|
||||
}
|
||||
_ = viewModel.isCollectionCategorySelected(named: "Client Work")
|
||||
_ = viewModel.canShowVisibleItemsInClipboard
|
||||
}
|
||||
|
||||
XCTAssertEqual(viewModel.debugCategoryFilterSelectionBuildCount, 1)
|
||||
}
|
||||
|
||||
func testRepeatedRecomputesDoNotRescanUnchangedStackMembership() {
|
||||
let settings = makeSettings()
|
||||
let cacheService = makeCacheService()
|
||||
let store = makeStore(settings: settings, cacheService: cacheService)
|
||||
store.upsert(makeTextItem("stacked needle", createdAt: Date(timeIntervalSince1970: 100)))
|
||||
store.upsert(makeTextItem("outside note", createdAt: Date(timeIntervalSince1970: 200)))
|
||||
store.flushPersistenceForTesting()
|
||||
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 2)
|
||||
viewModel.selectItem(at: 1)
|
||||
viewModel.toggleSelectedStackMembership()
|
||||
viewModel.debugResetVisibleItemsPerformanceCounters()
|
||||
|
||||
viewModel.searchText = "needle"
|
||||
viewModel.clearSearch()
|
||||
viewModel.selectSortMode(.text)
|
||||
viewModel.selectSortMode(.mostRecent)
|
||||
|
||||
XCTAssertEqual(viewModel.stackCount, 1)
|
||||
XCTAssertEqual(viewModel.debugStackPruneScanCount, 0)
|
||||
}
|
||||
|
||||
func testRepeatedHoverSelectionDoesNotNotifyAnUnchangedSelection() {
|
||||
let settings = makeSettings()
|
||||
@@ -405,139 +117,64 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(completionMainThreadValues, [true, true])
|
||||
}
|
||||
|
||||
func testComputeVisibleItemsFiltersColorClipsAndStructuredType() {
|
||||
func testContentKindsSupportCategoryStructuredAndTextSearch() {
|
||||
let settings = makeSettings()
|
||||
let store = makeStore(settings: settings)
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: makeCacheService())
|
||||
let color = ClipboardItem(
|
||||
id: UUID(),
|
||||
let color = makeItem(
|
||||
kind: .color,
|
||||
displayText: "#0A84FF",
|
||||
payload: "#0A84FF",
|
||||
payloadHash: hash("#0A84FF"),
|
||||
createdAt: Date(timeIntervalSince1970: 100),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 100),
|
||||
useCount: 0,
|
||||
sourceApp: "Design Tool",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
timestamp: 100,
|
||||
sourceApp: "Design Tool"
|
||||
)
|
||||
let text = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .text,
|
||||
displayText: "Color note",
|
||||
payload: "Color note",
|
||||
payloadHash: hash("Color note"),
|
||||
createdAt: Date(timeIntervalSince1970: 200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 200),
|
||||
useCount: 0,
|
||||
sourceApp: "Notes",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [text, color], query: "", sortMode: .colors).map(\.payload),
|
||||
["#0A84FF"]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [text, color], query: "type:swatch", sortMode: .mostRecent).map(\.payload),
|
||||
["#0A84FF"]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [text, color], query: "hex 0a84ff", sortMode: .mostRecent).map(\.payload),
|
||||
["#0A84FF"]
|
||||
)
|
||||
}
|
||||
|
||||
func testComputeVisibleItemsFiltersCodeSnippetsAndKeepsThemInTextView() {
|
||||
let settings = makeSettings()
|
||||
let store = makeStore(settings: settings)
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: makeCacheService())
|
||||
let code = ClipboardItem(
|
||||
id: UUID(),
|
||||
let code = makeItem(
|
||||
kind: .code,
|
||||
displayText: "Swift Snippet",
|
||||
payload: "func greet(name: String) -> String {\n return \"Hi \\(name)\"\n}",
|
||||
payloadHash: hash("swift-snippet"),
|
||||
createdAt: Date(timeIntervalSince1970: 200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 200),
|
||||
useCount: 0,
|
||||
sourceApp: "Xcode",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
timestamp: 300,
|
||||
sourceApp: "Xcode"
|
||||
)
|
||||
let text = ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .text,
|
||||
displayText: "Meeting note",
|
||||
payload: "Meeting note",
|
||||
payloadHash: hash("Meeting note"),
|
||||
createdAt: Date(timeIntervalSince1970: 100),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 100),
|
||||
useCount: 0,
|
||||
sourceApp: "Notes",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [code, text], query: "", sortMode: .code).map(\.kind),
|
||||
[.code]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [code, text], query: "", sortMode: .text).map(\.kind),
|
||||
[.code, .text]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [code, text], query: "type:snippet greet", sortMode: .mostRecent).map(\.kind),
|
||||
[.code]
|
||||
)
|
||||
}
|
||||
|
||||
func testComputeVisibleItemsFiltersVideoClipsAndStructuredType() {
|
||||
let settings = makeSettings()
|
||||
let store = makeStore(settings: settings)
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: makeCacheService())
|
||||
let video = ClipboardItem(
|
||||
id: UUID(),
|
||||
let video = makeItem(
|
||||
kind: .video,
|
||||
displayText: "Video (12 KB)",
|
||||
payload: "/tmp/clip.mp4",
|
||||
payloadHash: hash("clip-video"),
|
||||
createdAt: Date(timeIntervalSince1970: 200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 200),
|
||||
useCount: 0,
|
||||
sourceApp: "QuickTime Player",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
timestamp: 200,
|
||||
sourceApp: "QuickTime Player"
|
||||
)
|
||||
let image = ClipboardItem(
|
||||
id: UUID(),
|
||||
let text = makeItem(
|
||||
kind: .text,
|
||||
displayText: "Meeting note",
|
||||
payload: "Meeting note",
|
||||
timestamp: 100,
|
||||
sourceApp: "Notes"
|
||||
)
|
||||
let image = makeItem(
|
||||
kind: .image,
|
||||
displayText: "Image",
|
||||
payload: "/tmp/image.png",
|
||||
payloadHash: hash("image"),
|
||||
createdAt: Date(timeIntervalSince1970: 100),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 100),
|
||||
useCount: 0,
|
||||
sourceApp: "Preview",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [video, image], query: "", sortMode: .videos).map(\.kind),
|
||||
[.video]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [video, image], query: "type:movie", sortMode: .mostRecent).map(\.kind),
|
||||
[.video]
|
||||
)
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: [video, image], query: "mp4", sortMode: .mostRecent).map(\.kind),
|
||||
[.video]
|
||||
timestamp: 100,
|
||||
sourceApp: "Preview"
|
||||
)
|
||||
let items = [color, code, video, text, image]
|
||||
let cases: [(ClipboardSortMode, String, [ClipboardItemKind])] = [
|
||||
(.colors, "", [.color]),
|
||||
(.mostRecent, "type:swatch", [.color]),
|
||||
(.mostRecent, "hex 0a84ff", [.color]),
|
||||
(.code, "", [.code]),
|
||||
(.text, "", [.code, .text]),
|
||||
(.mostRecent, "type:snippet greet", [.code]),
|
||||
(.videos, "", [.video]),
|
||||
(.mostRecent, "type:movie", [.video]),
|
||||
(.mostRecent, "mp4", [.video])
|
||||
]
|
||||
for (sortMode, query, expectedKinds) in cases {
|
||||
XCTAssertEqual(
|
||||
viewModel.computeVisibleItems(from: items, query: query, sortMode: sortMode).map(\.kind),
|
||||
expectedKinds,
|
||||
"sort=\(sortMode), query=\(query)"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func testSearchMatchesIndependentTokensCaseInsensitively() {
|
||||
@@ -2547,7 +2184,7 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
viewModel.searchText = "second"
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["second queue note"])
|
||||
|
||||
viewModel.clearSearch()
|
||||
viewModel.searchText = ""
|
||||
viewModel.sortMode = .text
|
||||
XCTAssertFalse(viewModel.isStackFilterSelected)
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.payload), ["outside note", "second queue note", "first queue note"])
|
||||
@@ -2566,7 +2203,7 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
|
||||
viewModel.searchText = "draft"
|
||||
XCTAssertEqual(viewModel.visibleItems.map(\.id), [item.id])
|
||||
viewModel.clearSearch()
|
||||
viewModel.searchText = ""
|
||||
|
||||
XCTAssertEqual(viewModel.editableTextForSelected(), "draft meeting note")
|
||||
viewModel.updateSelectedText(to: "final launch note")
|
||||
@@ -2634,8 +2271,8 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
let viewModel = ClipboardPanelViewModel(store: store, settings: settings, cacheService: cacheService)
|
||||
waitForVisibleItems(in: viewModel, count: 2)
|
||||
|
||||
XCTAssertNil(viewModel.editableTextForItem(at: 0))
|
||||
viewModel.selectItem(at: 0)
|
||||
XCTAssertNil(viewModel.editableTextForSelected())
|
||||
viewModel.updateSelectedText(to: "should not apply")
|
||||
XCTAssertEqual(store.items.first?.payload, file.payload)
|
||||
|
||||
@@ -2963,18 +2600,32 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
private func makeTextItem(_ text: String, createdAt: Date) -> ClipboardItem {
|
||||
makeItem(kind: .text, displayText: text, payload: text, timestamp: createdAt.timeIntervalSince1970)
|
||||
}
|
||||
|
||||
private func makeItem(
|
||||
kind: ClipboardItemKind,
|
||||
displayText: String,
|
||||
payload: String,
|
||||
timestamp: TimeInterval,
|
||||
lastUsedTimestamp: TimeInterval? = nil,
|
||||
useCount: Int = 0,
|
||||
sourceApp: String? = nil,
|
||||
isPinned: Bool = false
|
||||
) -> ClipboardItem {
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
kind: .text,
|
||||
displayText: text,
|
||||
payload: text,
|
||||
payloadHash: hash(text),
|
||||
createdAt: createdAt,
|
||||
lastUsedAt: createdAt,
|
||||
useCount: 0,
|
||||
sourceApp: nil,
|
||||
kind: kind,
|
||||
displayText: displayText,
|
||||
payload: payload,
|
||||
payloadHash: hash(payload),
|
||||
createdAt: Date(timeIntervalSince1970: timestamp),
|
||||
lastUsedAt: Date(timeIntervalSince1970: lastUsedTimestamp ?? timestamp),
|
||||
useCount: useCount,
|
||||
sourceApp: sourceApp,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil
|
||||
thumbnailPath: nil,
|
||||
isPinned: isPinned
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3031,88 +2682,56 @@ final class ClipboardPanelViewModelTests: XCTestCase {
|
||||
|
||||
private func makeSampleItems() -> [ClipboardItem] {
|
||||
[
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
makeItem(
|
||||
kind: .text,
|
||||
displayText: "Project notes",
|
||||
payload: "one",
|
||||
payloadHash: hash("one"),
|
||||
createdAt: Date(timeIntervalSince1970: 1000),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 1000),
|
||||
timestamp: 1000,
|
||||
useCount: 2,
|
||||
sourceApp: nil,
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: true
|
||||
),
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
makeItem(
|
||||
kind: .richText,
|
||||
displayText: "Two",
|
||||
payload: "two",
|
||||
payloadHash: hash("two"),
|
||||
createdAt: Date(timeIntervalSince1970: 1100),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 1080),
|
||||
timestamp: 1100,
|
||||
lastUsedTimestamp: 1080,
|
||||
useCount: 4,
|
||||
sourceApp: "Mail",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: false
|
||||
sourceApp: "Mail"
|
||||
),
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
makeItem(
|
||||
kind: .url,
|
||||
displayText: "Apple",
|
||||
payload: "https://apple.com",
|
||||
payloadHash: hash("https://apple.com"),
|
||||
createdAt: Date(timeIntervalSince1970: 1030),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 1050),
|
||||
timestamp: 1030,
|
||||
lastUsedTimestamp: 1050,
|
||||
useCount: 1,
|
||||
sourceApp: "Safari",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: false
|
||||
sourceApp: "Safari"
|
||||
),
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
makeItem(
|
||||
kind: .file,
|
||||
displayText: "report.pdf",
|
||||
payload: "/tmp/report.pdf",
|
||||
payloadHash: hash("/tmp/report.pdf"),
|
||||
createdAt: Date(timeIntervalSince1970: 1060),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 1070),
|
||||
timestamp: 1060,
|
||||
lastUsedTimestamp: 1070,
|
||||
useCount: 3,
|
||||
sourceApp: "Finder",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: false
|
||||
sourceApp: "Finder"
|
||||
),
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
makeItem(
|
||||
kind: .audio,
|
||||
displayText: "Voice memo",
|
||||
payload: "/tmp/voice.sound",
|
||||
payloadHash: hash("/tmp/voice.sound"),
|
||||
createdAt: Date(timeIntervalSince1970: 1040),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 1060),
|
||||
timestamp: 1040,
|
||||
lastUsedTimestamp: 1060,
|
||||
useCount: 2,
|
||||
sourceApp: "Voice Memos",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: false
|
||||
sourceApp: "Voice Memos"
|
||||
),
|
||||
ClipboardItem(
|
||||
id: UUID(),
|
||||
makeItem(
|
||||
kind: .text,
|
||||
displayText: "Four",
|
||||
payload: "four",
|
||||
payloadHash: hash("four"),
|
||||
createdAt: Date(timeIntervalSince1970: 1200),
|
||||
lastUsedAt: Date(timeIntervalSince1970: 1200),
|
||||
useCount: 0,
|
||||
timestamp: 1200,
|
||||
sourceApp: "Notes",
|
||||
imagePath: nil,
|
||||
thumbnailPath: nil,
|
||||
isPinned: true
|
||||
)
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,26 +0,0 @@
|
||||
import XCTest
|
||||
@testable import ClipBored
|
||||
|
||||
final class DiagnosticsServiceTests: XCTestCase {
|
||||
func testCountersCanBeIncrementedAndReset() {
|
||||
let diagnostics = DiagnosticsService.shared
|
||||
diagnostics.reset()
|
||||
|
||||
diagnostics.incrementMonitorTick()
|
||||
diagnostics.incrementPasteboardChange()
|
||||
diagnostics.incrementExtractionAttempt()
|
||||
diagnostics.incrementDatabaseMutation()
|
||||
diagnostics.incrementCachePurge()
|
||||
|
||||
// The snapshot read synchronizes with the serial diagnostics queue.
|
||||
let snapshot = diagnostics.currentSnapshot()
|
||||
XCTAssertEqual(snapshot.monitorTicks, 1)
|
||||
XCTAssertEqual(snapshot.pasteboardChanges, 1)
|
||||
XCTAssertEqual(snapshot.extractionAttempts, 1)
|
||||
XCTAssertEqual(snapshot.databaseMutations, 1)
|
||||
XCTAssertEqual(snapshot.cachePurges, 1)
|
||||
|
||||
diagnostics.reset()
|
||||
XCTAssertEqual(diagnostics.currentSnapshot(), .init(monitorTicks: 0, pasteboardChanges: 0, extractionAttempts: 0, databaseMutations: 0, cachePurges: 0))
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import XCTest
|
||||
@testable import ClipBored
|
||||
|
||||
final class LinkPreviewWindowControllerTests: XCTestCase {
|
||||
func testReusedPreviewIgnoresStaleObservedTitleUntilNewNavigationStarts() throws {
|
||||
let controller = LinkPreviewWindowController()
|
||||
let firstURL = try XCTUnwrap(URL(string: "https://example.com/old"))
|
||||
let secondURL = try XCTUnwrap(URL(string: "https://example.com/new"))
|
||||
|
||||
controller.debugPrepareForPreview(LinkPreviewRequest(url: firstURL, title: "Old request title"))
|
||||
controller.debugAllowObservedPageTitles()
|
||||
controller.debugApplyObservedPageTitle("Old loaded title")
|
||||
|
||||
XCTAssertEqual(controller.debugTitleText, "Old loaded title")
|
||||
|
||||
controller.debugPrepareForPreview(LinkPreviewRequest(url: secondURL, title: "New request title"))
|
||||
controller.debugApplyObservedPageTitle("Old loaded title")
|
||||
|
||||
XCTAssertEqual(controller.debugTitleText, "New request title")
|
||||
XCTAssertEqual(controller.debugAddressText, "https://example.com/new")
|
||||
XCTAssertEqual(controller.debugStatusText, "Loading")
|
||||
|
||||
controller.debugAllowObservedPageTitles()
|
||||
controller.debugApplyObservedPageTitle("New loaded title")
|
||||
|
||||
XCTAssertEqual(controller.debugTitleText, "New loaded title")
|
||||
}
|
||||
|
||||
func testCancelledNavigationDoesNotShowFalseLoadFailure() throws {
|
||||
let controller = LinkPreviewWindowController()
|
||||
let url = try XCTUnwrap(URL(string: "https://example.com/new"))
|
||||
|
||||
controller.debugPrepareForPreview(LinkPreviewRequest(url: url, title: "New request title"))
|
||||
controller.debugApplyNavigationFailure(URLError(.cancelled))
|
||||
|
||||
XCTAssertEqual(controller.debugStatusText, "Loading")
|
||||
|
||||
controller.debugApplyNavigationFailure(URLError(.timedOut))
|
||||
|
||||
XCTAssertEqual(controller.debugStatusText, "Could not load")
|
||||
}
|
||||
|
||||
func testToolbarTooltipsTrackFullVisibleText() throws {
|
||||
let controller = LinkPreviewWindowController()
|
||||
let url = try XCTUnwrap(URL(string: "https://example.com/articles/a-very-long-release-note-title?ref=clipbored"))
|
||||
|
||||
controller.debugPrepareForPreview(LinkPreviewRequest(url: url, title: "Release note with a long title"))
|
||||
|
||||
XCTAssertEqual(controller.debugTitleTooltip, "Release note with a long title")
|
||||
XCTAssertEqual(controller.debugAddressTooltip, url.absoluteString)
|
||||
XCTAssertEqual(controller.debugStatusTooltip, "Loading")
|
||||
|
||||
controller.debugAllowObservedPageTitles()
|
||||
controller.debugApplyObservedPageTitle("Loaded page title that may truncate in the toolbar")
|
||||
|
||||
XCTAssertEqual(controller.debugTitleTooltip, "Loaded page title that may truncate in the toolbar")
|
||||
|
||||
controller.debugApplyNavigationFailure(URLError(.timedOut))
|
||||
|
||||
XCTAssertEqual(controller.debugStatusTooltip, "Could not load")
|
||||
}
|
||||
|
||||
func testOpenInBrowserUsesDisplayedPageURLAndResetsForReusedPreview() throws {
|
||||
var openedURLs: [URL] = []
|
||||
let controller = LinkPreviewWindowController { openedURLs.append($0) }
|
||||
let firstURL = try XCTUnwrap(URL(string: "https://example.com/old"))
|
||||
let navigatedURL = try XCTUnwrap(URL(string: "https://example.com/old/details"))
|
||||
let secondURL = try XCTUnwrap(URL(string: "https://example.com/new"))
|
||||
|
||||
controller.debugPrepareForPreview(LinkPreviewRequest(url: firstURL, title: "Old request title"))
|
||||
controller.debugSetDisplayedPageURL(navigatedURL)
|
||||
controller.debugOpenInBrowser()
|
||||
|
||||
XCTAssertEqual(openedURLs, [navigatedURL])
|
||||
|
||||
controller.debugPrepareForPreview(LinkPreviewRequest(url: secondURL, title: "New request title"))
|
||||
controller.debugOpenInBrowser()
|
||||
|
||||
XCTAssertEqual(openedURLs, [navigatedURL, secondURL])
|
||||
}
|
||||
}
|
||||
@@ -51,25 +51,4 @@ final class OnboardingWindowControllerTests: XCTestCase {
|
||||
XCTAssertTrue(presentation.showDockIcon)
|
||||
}
|
||||
|
||||
func testDisplayedEntryPointSelectionIsNormalizedOnRefresh() {
|
||||
let settings = makeSettings()
|
||||
settings.showMenuBarIcon = false
|
||||
settings.showDockIcon = false
|
||||
|
||||
let controller = OnboardingWindowController(
|
||||
settings: settings,
|
||||
onOpenAccessibility: {},
|
||||
onFinish: {}
|
||||
)
|
||||
|
||||
XCTAssertTrue(controller.debugShowMenuBarIconIsEnabled)
|
||||
XCTAssertFalse(controller.debugShowDockIconIsEnabled)
|
||||
}
|
||||
|
||||
private func makeSettings() -> SettingsModel {
|
||||
let suiteName = "com.clipbored.onboarding.\(UUID().uuidString)"
|
||||
let defaults = UserDefaults(suiteName: suiteName)!
|
||||
defaults.removePersistentDomain(forName: suiteName)
|
||||
return SettingsModel(defaults: defaults)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ final class SensitiveContentDetectorTests: XCTestCase {
|
||||
|
||||
func testDetectsCreditCardWithLuhnCheck() {
|
||||
XCTAssertEqual(SensitiveContentDetector.detect("4242424242424242"), .creditCard)
|
||||
XCTAssertEqual(SensitiveContentDetector.detect("Card: 4242 4242 4242 4242"), .creditCard)
|
||||
XCTAssertNil(SensitiveContentDetector.detect("4242424242424241"))
|
||||
}
|
||||
|
||||
|
||||
117
tests/clipboredtests/SettingsPresentationTests.swift
Normal file
117
tests/clipboredtests/SettingsPresentationTests.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
import AppKit
|
||||
import XCTest
|
||||
@testable import ClipBored
|
||||
|
||||
final class SettingsPresentationTests: XCTestCase {
|
||||
func testPasteAndDataStatusColors() {
|
||||
let pasteCases: [(String, NSColor)] = [
|
||||
("", .secondaryLabelColor),
|
||||
("Pasted", .systemGreen),
|
||||
("Copied. Grant Accessibility access to paste automatically.", .systemOrange),
|
||||
("Could not write item to clipboard.", .systemRed)
|
||||
]
|
||||
for (message, color) in pasteCases {
|
||||
XCTAssertEqual(SettingsWindowController.pasteStatusPresentation(storedStatus: message).textColor, color)
|
||||
}
|
||||
|
||||
let dataCases: [(String, NSColor)] = [
|
||||
("", .secondaryLabelColor),
|
||||
("Exported 3 clips.", .systemGreen),
|
||||
("Imported 3 clips. Skipped 1 clip.", .systemOrange),
|
||||
("The archive couldn't be opened.", .systemRed)
|
||||
]
|
||||
for (message, color) in dataCases {
|
||||
XCTAssertEqual(SettingsWindowController.dataStatusPresentation(storedStatus: message).textColor, color)
|
||||
}
|
||||
}
|
||||
|
||||
func testCaptureStatusColors() {
|
||||
let cases: [(String, NSColor)] = [
|
||||
("", .secondaryLabelColor),
|
||||
("Captured text from Safari.", .systemGreen),
|
||||
("Skipped: Audio items are ignored.", .systemOrange),
|
||||
("At least one content type must stay enabled.", .systemOrange),
|
||||
("Error: Clipboard read failed.", .systemRed)
|
||||
]
|
||||
for (message, color) in cases {
|
||||
XCTAssertEqual(SettingsWindowController.captureStatusPresentation(storedStatus: message).textColor, color)
|
||||
}
|
||||
}
|
||||
|
||||
func testShortcutPermissionAndLifecycleStatusColors() {
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.shortcutStatusPresentation(storedStatus: "").textColor,
|
||||
.systemGreen
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.shortcutStatusPresentation(storedStatus: "Unsupported shortcut").textColor,
|
||||
.systemRed
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.accessibilityPermissionStatusPresentation(
|
||||
storedStatus: "",
|
||||
isTrusted: true
|
||||
).textColor,
|
||||
.systemGreen
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.accessibilityPermissionStatusPresentation(
|
||||
storedStatus: "Permission not granted",
|
||||
isTrusted: true
|
||||
).textColor,
|
||||
.systemOrange
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.launchAtLoginStatusPresentation(storedStatus: "Service unavailable").textColor,
|
||||
.systemRed
|
||||
)
|
||||
}
|
||||
|
||||
func testCloudSyncStatusPrecedence() {
|
||||
let ready = ClipboardCloudSyncStatus(
|
||||
isAvailable: true,
|
||||
archiveURL: nil,
|
||||
lastModifiedAt: nil,
|
||||
message: "iCloud is ready."
|
||||
)
|
||||
let unavailable = ClipboardCloudSyncStatus(
|
||||
isAvailable: false,
|
||||
archiveURL: nil,
|
||||
lastModifiedAt: nil,
|
||||
message: "iCloud is unavailable."
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.cloudSyncStatusPresentation(
|
||||
storedStatus: "",
|
||||
isSyncEnabled: false,
|
||||
cloudStatus: ready
|
||||
).message,
|
||||
"iCloud Sync is off."
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.cloudSyncStatusPresentation(
|
||||
storedStatus: "",
|
||||
isSyncEnabled: true,
|
||||
cloudStatus: unavailable
|
||||
).textColor,
|
||||
.systemOrange
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.cloudSyncStatusPresentation(
|
||||
storedStatus: "Synced 3 clips.",
|
||||
isSyncEnabled: true,
|
||||
cloudStatus: ready
|
||||
).textColor,
|
||||
.systemGreen
|
||||
)
|
||||
XCTAssertEqual(
|
||||
SettingsWindowController.cloudSyncStatusPresentation(
|
||||
storedStatus: "iCloud Sync failed.",
|
||||
isSyncEnabled: true,
|
||||
cloudStatus: ready
|
||||
).textColor,
|
||||
.systemRed
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,13 +36,6 @@ final class ShortcutManagerTests: XCTestCase {
|
||||
manager.stop()
|
||||
}
|
||||
|
||||
func testGlobalRegistrationExcludesLocalSettingsShortcut() {
|
||||
let bindings = ShortcutManager.globalShortcutBindings(openShortcut: AppConfiguration.defaultOpenShortcut)
|
||||
|
||||
XCTAssertEqual(bindings, [AppConfiguration.defaultOpenShortcut, ShortcutManager.stackCaptureShortcut])
|
||||
XCTAssertFalse(bindings.contains(AppConfiguration.defaultSettingsShortcut))
|
||||
}
|
||||
|
||||
func testRejectsConfiguredShortcutConflictWithFixedStackCaptureShortcut() {
|
||||
let manager = makeManager(openShortcut: ShortcutManager.stackCaptureShortcut)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user