2026-06-30 01:12:19 -07:00
import AppKit
import XCTest
@ testable import ClipBored
final class ClipboardPanelViewTests : XCTestCase {
private var tempURLs : [ URL ] = [ ]
private struct PanelFixture {
let window : NSWindow
let view : ClipboardPanelView
let viewModel : ClipboardPanelViewModel
let settings : SettingsModel
let store : ClipboardStore
let cacheService : ClipboardCacheService
2026-06-30 08:56:45 -07:00
let previewProbe : PreviewProbe
}
private final class PreviewProbe {
var requestCount = 0
2026-06-30 01:12:19 -07:00
}
override func tearDown ( ) {
tempURLs . forEach { try ? FileManager . default . removeItem ( at : $0 ) }
tempURLs . removeAll ( )
super . tearDown ( )
}
func testSearchFieldEditingWhenSearchFieldIsFirstResponder ( ) {
let ( window , view ) = makePanelWithPanelView ( )
window . makeFirstResponder ( view )
XCTAssertFalse ( view . isSearchFieldEditing )
view . focusSearchField ( )
XCTAssertTrue ( view . isSearchFieldEditing )
}
func testSearchFieldEditingWhenFieldEditorIsFirstResponder ( ) {
let ( window , view ) = makePanelWithPanelView ( )
view . focusSearchField ( )
guard let editor = window . fieldEditor ( false , for : nil ) else {
return XCTFail ( " Expected a search field editor " )
}
window . makeFirstResponder ( editor )
XCTAssertTrue ( view . isSearchFieldEditing )
}
func testCapturedTextItemCreatesVisibleCardDocument ( ) {
let fixture = makePanelFixture ( )
let item = makeTextItem ( " Bruh it said copied text but it does not appear. " , store : fixture . store )
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . id ) , [ item . id ] )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 1 )
XCTAssertEqual ( fixture . view . debugResultCountText , " 1 clip " )
XCTAssertTrue ( fixture . view . debugDocumentViewIsCardStack )
XCTAssertGreaterThanOrEqual ( fixture . view . debugDocumentViewFrame . width , 292 )
XCTAssertGreaterThanOrEqual ( fixture . view . debugDocumentViewFrame . height , 244 )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " text-preview " ] )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCardRailOverflowFadeVisibility , [ false , false ] )
2026-06-30 01:12:19 -07:00
}
2026-06-30 10:44:11 -07:00
func testSingleLineTextCardsDoNotDuplicateTitleInBody ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Client follow-up note " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardTextPreviewTitles , [ " Client follow-up note " ] )
XCTAssertEqual ( fixture . view . debugCardTextPreviewBodies , [ " " ] )
}
func testMultiLineTextCardsShowRemainderBelowTitle ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Address: \n 399 The Embarcadero \n San Francisco " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardTextPreviewTitles , [ " Address: " ] )
XCTAssertEqual ( fixture . view . debugCardTextPreviewBodies , [ " 399 The Embarcadero San Francisco " ] )
}
2026-06-30 03:11:40 -07:00
func testCompactCardsFitTwoItemsOnNarrowDockShelf ( ) {
let fixture = makePanelFixture ( )
fixture . window . setFrame ( NSRect ( x : 0 , y : 0 , width : 620 , height : 520 ) , display : true )
fixture . store . upsert ( makeTextItem ( " Compact first " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Compact second " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardDensity , " compact " )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 2 )
XCTAssertEqual ( fixture . view . debugCardSizes . count , 2 )
XCTAssertEqual ( fixture . view . debugCardSizes . first ? . width ? ? 0 , 264 , accuracy : 0.5 )
XCTAssertEqual ( fixture . view . debugCardSizes . first ? . height ? ? 0 , 220 , accuracy : 0.5 )
XCTAssertLessThanOrEqual (
fixture . view . debugDocumentViewFrame . width ,
fixture . view . debugCardRailVisibleRect . width + 1
)
}
2026-06-30 02:38:48 -07:00
func testCardsShowQuickPasteNumberBadgesForFirstNineItems ( ) {
let fixture = makePanelFixture ( )
for index in 0. . < 10 {
fixture . store . upsert ( makeTextItem ( " Quick paste badge \( index ) " , store : fixture . store ) )
drainMainQueue ( )
}
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 10 )
XCTAssertEqual ( fixture . view . debugQuickPasteBadgeTexts , [ " 1 " , " 2 " , " 3 " , " 4 " , " 5 " , " 6 " , " 7 " , " 8 " , " 9 " ] )
}
2026-06-30 01:12:19 -07:00
func testFooterShowsCaptureStatusInsteadOfShortcutInstructions ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Footer status item " , store : fixture . store ) )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugStatusText , " Capture running " )
XCTAssertEqual ( fixture . view . debugStatusTone , " ready " )
XCTAssertFalse ( fixture . view . debugStatusText . contains ( " Enter paste " ) )
}
2026-06-30 10:14:25 -07:00
func testCardFooterHidesMissingSourceInsteadOfShowingUnknown ( ) {
let fixture = makePanelFixture ( )
var item = makeTextItem ( " No source noise " , store : fixture . store )
item . sourceApp = nil
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFirstCardFooterSourceIsHidden )
XCTAssertEqual ( fixture . view . debugFirstCardFooterSourceText , " " )
XCTAssertEqual ( fixture . view . debugFirstCardFooterDetailText , " 15 characters " )
}
func testCardFooterShowsSourceAndUsageWhenUsed ( ) {
let fixture = makePanelFixture ( )
var item = makeTextItem ( " Frequently pasted " , store : fixture . store )
item . useCount = 3
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertFalse ( fixture . view . debugFirstCardFooterSourceIsHidden )
XCTAssertEqual ( fixture . view . debugFirstCardFooterSourceText , " Ghostty - Used 3 times " )
}
2026-06-30 01:56:40 -07:00
func testEditedTextStatusUsesActionTone ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Editable footer item " , store : fixture . store ) )
drainMainQueue ( )
fixture . viewModel . updateSelectedText ( to : " Edited footer item " )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugStatusText , " Updated text clip " )
XCTAssertEqual ( fixture . view . debugStatusTone , " action " )
}
2026-06-30 01:12:19 -07:00
func testSkippedCaptureStatusUsesWarningTone ( ) {
let fixture = makePanelFixture ( )
fixture . settings . setCaptureStatus ( message : " Skipped: Audio items are ignored in capture settings. " )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugStatusText , " Skipped: Audio items are ignored in capture settings. " )
XCTAssertEqual ( fixture . view . debugStatusTone , " warning " )
}
func testPanelShellRendersAsSquareDockedSurface ( ) {
let ( _ , view ) = makePanelWithPanelView ( )
drainMainQueue ( )
view . layoutSubtreeIfNeeded ( )
view . displayIfNeeded ( )
let rep = try ! XCTUnwrap ( view . bitmapImageRepForCachingDisplay ( in : view . bounds ) )
rep . size = view . bounds . size
view . cacheDisplay ( in : view . bounds , to : rep )
let width = rep . pixelsWide
let height = rep . pixelsHigh
func alphaAt ( _ x : Int , _ y : Int ) -> CGFloat {
rep . colorAt ( x : x , y : y ) ? . alphaComponent ? ? 0
}
XCTAssertEqual ( view . debugPanelCornerRadius , 0 )
XCTAssertGreaterThan ( alphaAt ( 8 , 8 ) , 0.9 )
XCTAssertGreaterThan ( alphaAt ( width - 9 , 8 ) , 0.9 )
XCTAssertGreaterThan ( alphaAt ( 8 , height - 9 ) , 0.9 )
XCTAssertGreaterThan ( alphaAt ( width - 9 , height - 9 ) , 0.9 )
}
func testOpeningTransitionDefersCardRailReloadUntilAnimationCompletes ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Existing clip " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 1 )
fixture . view . beginOpeningTransition ( )
XCTAssertTrue ( fixture . view . debugIsDeferringVisualReloads )
fixture . store . upsert ( makeTextItem ( " New clip during open " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 1 )
XCTAssertEqual ( fixture . view . debugResultCountText , " 2 clips " )
fixture . view . finishOpeningTransition ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertFalse ( fixture . view . debugIsDeferringVisualReloads )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 2 )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels . first , " Text: New clip during open " )
}
func testCollectionRailUsesPasteStyleLabelsAndTracksSelection ( ) {
let fixture = makePanelFixture ( )
XCTAssertEqual (
fixture . view . debugCollectionTitles ,
[ " Clipboard " , " Frequent " , " Text " , " Links " , " Images " , " Audio " , " Files " , " Pinned " ]
)
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Clipboard " )
fixture . viewModel . sortMode = . links
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Links " )
}
2026-06-30 08:50:21 -07:00
func testCollectionRailChipsAreKeyboardFocusableAndVoiceOverDescriptive ( ) {
let fixture = makePanelFixture ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCollectionChipAcceptsFirstResponder , Array ( repeating : true , count : ClipboardSortMode . allCases . count ) )
2026-06-30 10:39:33 -07:00
XCTAssertEqual ( fixture . view . debugCollectionCountLabelHiddenStates , Array ( repeating : true , count : ClipboardSortMode . allCases . count ) )
2026-06-30 08:50:21 -07:00
XCTAssertEqual ( fixture . view . debugCollectionChipAccessibilityLabels . first , " Clipboard, selected, 0 clips " )
XCTAssertTrue ( fixture . view . debugFocusCollectionChip ( . links ) )
fixture . view . debugPressFocusedResponderWithSpace ( )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Links " )
XCTAssertTrue ( fixture . view . debugCollectionChipAccessibilityLabels . contains ( " Links, selected, 0 clips " ) )
}
2026-06-30 09:26:28 -07:00
func testCollectionRailChipsSupportShelfNavigationKeys ( ) {
let fixture = makePanelFixture ( )
var clientItem = makeTextItem ( " Client collection item " , store : fixture . store )
clientItem . collectionName = " Client Work "
fixture . store . upsert ( clientItem )
fixture . store . upsert ( makeTextItem ( " Stack queue item " , store : fixture . store ) )
drainMainQueue ( )
fixture . viewModel . selectItem ( at : 0 )
fixture . viewModel . toggleSelectedStackMembership ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFocusCollectionChip ( . links ) )
fixture . view . debugPressFocusedResponderKeyCode ( 124 )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Images " )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCollectionTitles , [ " Images " ] )
fixture . view . debugPressFocusedResponderKeyCode ( 123 )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Links " )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCollectionTitles , [ " Links " ] )
fixture . view . debugPressFocusedResponderKeyCode ( 119 )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Stack " )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCollectionTitles , [ " Stack " ] )
fixture . view . debugPressFocusedResponderKeyCode ( 123 )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Client Work " )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCollectionTitles , [ " Client Work " ] )
fixture . view . debugPressFocusedResponderKeyCode ( 115 )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Clipboard " )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCollectionTitles , [ " Clipboard " ] )
}
2026-06-30 09:40:04 -07:00
func testTypingFromFocusedCollectionChipStartsSearch ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Alpha note " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Quantum reference " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFocusCollectionChip ( . links ) )
fixture . view . debugTypeFocusedResponder ( " q " , keyCode : 12 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . isSearchFieldEditing )
XCTAssertEqual ( fixture . view . debugSearchFieldText , " q " )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Quantum reference " ] )
}
2026-06-30 09:44:45 -07:00
func testKeyboardCancelClearsSearchFromFocusedCollectionChip ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Alpha note " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Quantum reference " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . view . debugSetSearchFieldText ( " quantum " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Quantum reference " ] )
XCTAssertTrue ( fixture . view . debugFocusCollectionChip ( . text ) )
XCTAssertTrue ( fixture . view . clearSearchForKeyboardCancel ( ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . isSearchFieldEditing )
XCTAssertEqual ( fixture . view . debugSearchFieldText , " " )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Quantum reference " , " Alpha note " ] )
}
2026-06-30 03:51:06 -07:00
func testCollectionRailAddButtonCreatesEmptyCollection ( ) {
2026-06-30 01:34:05 -07:00
let fixture = makePanelFixture ( )
XCTAssertTrue ( fixture . view . debugCollectionRailContainsAddButton )
XCTAssertTrue ( fixture . view . debugAddCollectionButtonIsEnabled )
fixture . view . debugSetCollectionNameProvider { " Research Stack " }
fixture . view . debugPressAddCollectionButton ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
2026-06-30 03:51:06 -07:00
XCTAssertEqual ( fixture . viewModel . statusMessage , " Created Research Stack " )
2026-06-30 01:34:05 -07:00
XCTAssertEqual ( fixture . view . debugCustomCollectionTitles , [ " Research Stack " ] )
2026-06-30 03:51:06 -07:00
XCTAssertEqual ( fixture . view . debugCustomCollectionCounts , [ 0 ] )
2026-06-30 10:39:33 -07:00
XCTAssertEqual ( fixture . view . debugCustomCollectionCountLabelHiddenStates , [ true ] )
2026-06-30 03:51:06 -07:00
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Research Stack " )
XCTAssertEqual ( fixture . view . debugVisibleCardCount , 0 )
XCTAssertEqual ( fixture . view . debugEmptyStateText ? . title , " No clips in Research Stack " )
XCTAssertEqual ( fixture . view . debugEmptyStateText ? . detail , " Drag clips here or use Collect to add them. " )
}
2026-06-30 01:34:05 -07:00
2026-06-30 03:51:06 -07:00
func testCollectionFilteredCardsUseStoredCollectionHeaderColor ( ) {
let fixture = makePanelFixture ( )
fixture . viewModel . createCollection ( named : " Research Stack " , colorHex : " #0A9EB8 " )
var item = makeTextItem ( " Collect this note " , store : fixture . store )
item . collectionName = " Research Stack "
fixture . store . upsert ( item )
2026-06-30 01:34:05 -07:00
drainMainQueue ( )
2026-06-30 03:36:36 -07:00
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
2026-06-30 01:34:05 -07:00
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Collect this note " ] )
2026-06-30 03:36:36 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardHeaderTitle , " Research Stack " )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderSubtitle , " Text - Just now " )
2026-06-30 03:51:06 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardHeaderColorHex , " #0A9EB8 " )
XCTAssertEqual ( fixture . view . debugCustomCollectionColorHexes [ " Research Stack " ] , " #0A9EB8 " )
2026-06-30 03:36:36 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardFooterDetailText , " 17 characters " )
2026-06-30 01:34:05 -07:00
}
2026-06-30 10:22:09 -07:00
func testCardHeaderUsesReadableRelativeAgeText ( ) {
let fixture = makePanelFixture ( )
var item = makeTextItem ( " Readable age " , store : fixture . store )
item . createdAt = Date ( ) . addingTimeInterval ( - 3 * 60 )
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderSubtitle , " 3 minutes ago " )
fixture . viewModel . createCollection ( named : " Age Stack " , colorHex : " #FF3B30 " , selectAfterCreate : false )
fixture . viewModel . assignSelected ( to : " Age Stack " )
fixture . viewModel . selectCollection ( named : " Age Stack " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderSubtitle , " Text - 3 minutes ago " )
}
2026-06-30 04:01:57 -07:00
func testCollectionChipsExposeManagementMenuActions ( ) {
let fixture = makePanelFixture ( )
fixture . viewModel . createCollection ( named : " Research Stack " , colorHex : " #0A9EB8 " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCustomCollectionMenuTitles ( named : " Research Stack " ) , [ " Edit Collection... " , " - " , " Delete Collection " ] )
}
func testCollectionChipManagementRenamesAndDeletesCollections ( ) {
let fixture = makePanelFixture ( )
fixture . viewModel . createCollection ( named : " Research Stack " , colorHex : " #0A9EB8 " )
var item = makeTextItem ( " Collect this note " , store : fixture . store )
item . collectionName = " Research Stack "
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . view . debugEditCollection ( named : " Research Stack " , to : " Product Research " , colorHex : " #3366FF " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCustomCollectionTitles , [ " Product Research " ] )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Product Research " )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderTitle , " Product Research " )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderColorHex , " #3366FF " )
fixture . view . debugDeleteCollection ( named : " Product Research " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCustomCollectionTitles , [ ] )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ ] )
XCTAssertEqual ( fixture . store . items . map ( \ . payload ) , [ ] )
}
2026-06-30 01:12:19 -07:00
func testSelectedCardActionsRespectSelectedKind ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Plain text " , store : fixture . store ) )
drainMainQueue ( )
2026-06-30 10:07:36 -07:00
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
2026-06-30 01:12:19 -07:00
2026-06-30 10:34:37 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionLabels , [ " Paste " , " Copy " , " Pin " , " Collect " , " Edit " , " Preview " , " Delete " ] )
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionRailWidth , 210 )
2026-06-30 01:12:19 -07:00
XCTAssertFalse ( fixture . view . debugFirstCardFooterDetailIsHidden )
2026-06-30 10:02:22 -07:00
XCTAssertFalse ( fixture . view . debugFirstCardHeaderBadgeIsHidden )
2026-06-30 10:07:36 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardHeaderBadgeFrame . maxX , 320 , accuracy : 0.5 )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderBadgeFrame . maxY , 244 , accuracy : 0.5 )
2026-06-30 01:12:19 -07:00
fixture . store . upsert ( makeItem ( kind : . file , text : " /tmp/report.txt " , store : fixture . store ) )
drainMainQueue ( )
2026-06-30 10:07:36 -07:00
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
2026-06-30 01:12:19 -07:00
fixture . viewModel . selectFirstItem ( )
2026-06-30 10:07:36 -07:00
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
2026-06-30 01:12:19 -07:00
XCTAssertEqual ( fixture . viewModel . visibleItems . first ? . kind , . file )
2026-06-30 10:34:37 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionLabels , [ " Paste " , " Copy " , " Paste Plain Text " , " Collect " , " Preview " , " Open " , " Reveal " , " More " ] )
2026-06-30 10:02:22 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionRailWidth , 238 )
2026-06-30 01:12:19 -07:00
XCTAssertFalse ( fixture . view . debugFirstCardFooterDetailIsHidden )
2026-06-30 10:02:22 -07:00
XCTAssertFalse ( fixture . view . debugFirstCardHeaderBadgeIsHidden )
2026-06-30 10:07:36 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardHeaderBadgeFrame . maxX , 320 , accuracy : 0.5 )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderBadgeFrame . maxY , 244 , accuracy : 0.5 )
2026-06-30 01:12:19 -07:00
}
2026-06-30 09:54:59 -07:00
func testCompactFileCardActionsFitInsideShelfWithOverflowMenu ( ) {
let fixture = makePanelFixture ( )
fixture . window . setFrame ( NSRect ( x : 0 , y : 0 , width : 620 , height : 520 ) , display : true )
fixture . store . upsert ( makeItem ( kind : . file , text : " /tmp/report.txt " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardDensity , " compact " )
2026-06-30 10:34:37 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionLabels , [ " Paste " , " Copy " , " Paste Plain Text " , " Collect " , " Preview " , " Open " , " More " ] )
2026-06-30 10:02:22 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionRailWidth , 196 )
XCTAssertLessThanOrEqual ( fixture . view . debugFirstCardVisibleActionRailWidth , 197 )
XCTAssertFalse ( fixture . view . debugFirstCardHeaderBadgeIsHidden )
2026-06-30 10:07:36 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardHeaderBadgeFrame . maxX , 264 , accuracy : 0.5 )
XCTAssertEqual ( fixture . view . debugFirstCardHeaderBadgeFrame . maxY , 220 , accuracy : 0.5 )
2026-06-30 09:54:59 -07:00
XCTAssertEqual (
fixture . view . debugFirstCardMenuTitles ,
[ " Paste " , " Copy " , " Paste Plain Text " , " Copy Plain Text " , " Rename... " , " Add to Stack " , " Quick Look " , " Pin " , " Add to Collection " , " Capture Rules " , " - " , " Open " , " Reveal in Finder " , " - " , " Delete " ]
)
}
2026-06-30 08:56:45 -07:00
func testCardsAreKeyboardFocusableAndReturnPastesFocusedCard ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Older text card " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Newest text card " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAcceptsFirstResponder , [ true , true ] )
XCTAssertTrue ( fixture . view . debugFocusCard ( at : 1 ) )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedItem ? . payload , " Older text card " )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ 1 ] )
XCTAssertEqual ( fixture . view . debugCardBorderWidths [ 1 ] , 2 )
XCTAssertEqual ( fixture . view . debugCardAccessibilityValues [ 1 ] , " Selected " )
2026-06-30 09:33:00 -07:00
XCTAssertEqual ( fixture . view . debugCardAccessibilityHelps [ 1 ] , " Press Return to paste. Press Space for Quick Look. " )
2026-06-30 08:56:45 -07:00
fixture . view . debugPressFocusedResponderWithReturn ( )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . statusMessage , " Copied " )
}
2026-06-30 09:33:00 -07:00
func testFocusedTextCardSpaceOpensQuickLookInsteadOfPasting ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Preview this text without pasting " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFocusCard ( at : 0 ) )
drainMainQueue ( )
fixture . view . debugPressFocusedResponderWithSpace ( )
drainMainQueue ( )
XCTAssertEqual ( fixture . previewProbe . requestCount , 1 )
XCTAssertEqual ( fixture . viewModel . selectedItem ? . payload , " Preview this text without pasting " )
XCTAssertEqual ( fixture . viewModel . statusMessage , " " )
}
2026-06-30 09:40:04 -07:00
func testTypingFromFocusedCardStartsSearch ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Alpha note " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Quantum card " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFocusCard ( at : 0 ) )
fixture . view . debugTypeFocusedResponder ( " q " , keyCode : 12 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . isSearchFieldEditing )
XCTAssertEqual ( fixture . view . debugSearchFieldText , " q " )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Quantum card " ] )
}
2026-06-30 09:44:45 -07:00
func testKeyboardCancelClearsSearchFromFocusedCard ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Alpha note " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Quantum card " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . view . debugSetSearchFieldText ( " quantum " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Quantum card " ] )
XCTAssertTrue ( fixture . view . debugFocusCard ( at : 0 ) )
XCTAssertTrue ( fixture . view . clearSearchForKeyboardCancel ( ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . isSearchFieldEditing )
XCTAssertEqual ( fixture . view . debugSearchFieldText , " " )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Quantum card " , " Alpha note " ] )
XCTAssertFalse ( fixture . view . clearSearchForKeyboardCancel ( ) )
}
2026-06-30 08:56:45 -07:00
func testFocusedPreviewableCardSpaceOpensQuickLook ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeItem ( kind : . url , text : " https://example.com/read " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFocusCard ( at : 0 ) )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityHelps . first , " Press Return to paste. Press Space for Quick Look. " )
fixture . view . debugPressFocusedResponderWithSpace ( )
drainMainQueue ( )
XCTAssertEqual ( fixture . previewProbe . requestCount , 1 )
XCTAssertEqual ( fixture . viewModel . selectedItem ? . payload , " https://example.com/read " )
}
2026-06-30 09:19:18 -07:00
func testFocusedCardsSupportShelfNavigationKeys ( ) {
let fixture = makePanelFixture ( )
fixture . window . setFrame ( NSRect ( x : 0 , y : 0 , width : 620 , height : 520 ) , display : true )
for index in 0. . < 8 {
fixture . store . upsert ( makeTextItem ( " Keyboard navigation item \( index ) " , store : fixture . store ) )
drainMainQueue ( )
}
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . viewModel . selectFirstItem ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
let pageStep = fixture . view . debugVisibleCardPageStep
XCTAssertGreaterThan ( pageStep , 1 )
XCTAssertTrue ( fixture . view . debugFocusCard ( at : 0 ) )
fixture . view . debugPressFocusedResponderKeyCode ( 124 )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedIndex , 1 )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ 1 ] )
fixture . view . debugPressFocusedResponderKeyCode ( 121 )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedIndex , min ( 7 , 1 + pageStep ) )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ fixture . viewModel . selectedIndex ] )
fixture . view . debugPressFocusedResponderKeyCode ( 119 )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedIndex , 7 )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ 7 ] )
fixture . view . debugPressFocusedResponderKeyCode ( 116 )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedIndex , max ( 0 , 7 - pageStep ) )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ fixture . viewModel . selectedIndex ] )
fixture . view . debugPressFocusedResponderKeyCode ( 115 )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedIndex , 0 )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ 0 ] )
fixture . view . debugPressFocusedResponderKeyCode ( 123 )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . selectedIndex , 0 )
XCTAssertEqual ( fixture . view . debugKeyboardFocusedCardIndexes , [ 0 ] )
}
2026-06-30 01:12:19 -07:00
func testCardHeaderUsesKindSymbolBadgeWhenSourceIconIsUnavailable ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeItem ( kind : . url , text : " https://example.com " , store : fixture . store ) )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugCardHeaderBadgeSymbols , [ " link " ] )
}
func testCollectionRailShowsLiveCounts ( ) {
let fixture = makePanelFixture ( )
var pinned = makeTextItem ( " Pinned note " , store : fixture . store )
pinned . isPinned = true
let rich = makeItem ( kind : . richText , text : " Rich note " , store : fixture . store )
let link = makeItem ( kind : . url , text : " https://example.com/releases " , store : fixture . store )
let image = makeItem ( kind : . image , text : " image payload " , store : fixture . store )
let audio = makeItem ( kind : . audio , text : " audio payload " , store : fixture . store )
let file = makeItem ( kind : . file , text : " /tmp/report.pdf " , store : fixture . store )
[ pinned , rich , link , image , audio , file ] . forEach {
fixture . store . upsert ( $0 )
drainMainQueue ( )
}
XCTAssertEqual ( fixture . viewModel . visibleItems . count , 6 )
XCTAssertEqual ( ClipboardSortMode . allCases . map { fixture . viewModel . collectionCount ( for : $0 ) } , [ 6 , 6 , 2 , 1 , 1 , 1 , 1 , 1 ] )
XCTAssertEqual ( fixture . view . debugCollectionCounts , [ 6 , 6 , 2 , 1 , 1 , 1 , 1 , 1 ] )
2026-06-30 10:39:33 -07:00
XCTAssertEqual ( fixture . view . debugCollectionCountLabelHiddenStates , Array ( repeating : false , count : ClipboardSortMode . allCases . count ) )
2026-06-30 01:12:19 -07:00
}
func testCollectionRailShowsAssignedCollections ( ) {
let fixture = makePanelFixture ( )
var link = makeItem ( kind : . url , text : " https://example.com/read " , store : fixture . store )
link . collectionName = " Useful Links "
var note = makeTextItem ( " Meeting note " , store : fixture . store )
note . collectionName = " Important Notes "
var file = makeItem ( kind : . file , text : " /tmp/client-brief.pdf " , store : fixture . store )
file . collectionName = " Client Work "
fixture . store . upsert ( link )
fixture . store . upsert ( note )
fixture . store . upsert ( file )
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugCustomCollectionTitles , [ " Useful Links " , " Important Notes " , " Client Work " ] )
XCTAssertEqual ( fixture . view . debugCustomCollectionCounts , [ 1 , 1 , 1 ] )
2026-06-30 10:39:33 -07:00
XCTAssertEqual ( fixture . view . debugCustomCollectionCountLabelHiddenStates , [ false , false , false ] )
2026-06-30 01:12:19 -07:00
fixture . viewModel . selectCollection ( named : " Useful Links " )
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " https://example.com/read " ] )
}
2026-06-30 03:02:29 -07:00
func testCardsCanDropOntoCollectionChipsToOrganize ( ) {
let fixture = makePanelFixture ( )
var existing = makeTextItem ( " Existing client note " , store : fixture . store )
existing . collectionName = " Client Work "
fixture . store . upsert ( existing )
let dropped = makeTextItem ( " Drop this note " , store : fixture . store )
fixture . store . upsert ( dropped )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCustomCollectionTitles , [ " Client Work " ] )
XCTAssertEqual ( fixture . view . debugCustomCollectionDropTargets , [ " Client Work " ] )
XCTAssertEqual ( fixture . viewModel . visibleItems . first ? . id , dropped . id )
fixture . view . debugDropFirstCard ( onCollectionNamed : " Client Work " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . viewModel . statusMessage , " Added to Client Work " )
XCTAssertEqual ( fixture . view . debugCustomCollectionCounts , [ 2 ] )
fixture . viewModel . selectCollection ( named : " Client Work " )
drainMainQueue ( )
XCTAssertEqual ( Set ( fixture . viewModel . visibleItems . map ( \ . payload ) ) , [ " Existing client note " , " Drop this note " ] )
}
2026-06-30 01:12:19 -07:00
func testCollectionRailUsesScrollableDocumentForCrowdedCustomCollections ( ) {
let fixture = makePanelFixture ( )
2026-06-30 09:02:19 -07:00
fixture . window . setFrame ( NSRect ( x : 0 , y : 0 , width : 620 , height : 520 ) , display : true )
2026-06-30 01:12:19 -07:00
let names = [
" Client Work " ,
" Research Archive " ,
" Launch Planning " ,
" Design QA " ,
" Product References " ,
" Reading Stack " ,
" Invoices " ,
" Hiring Pipeline "
]
for name in names {
var item = makeTextItem ( " Collection item \( name ) " , store : fixture . store )
item . collectionName = name
fixture . store . upsert ( item )
}
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertGreaterThan ( fixture . view . debugCollectionRailVisibleWidth , 0 )
XCTAssertGreaterThan (
fixture . view . debugCollectionRailDocumentWidth ,
fixture . view . debugCollectionRailVisibleWidth + 1
)
XCTAssertTrue ( fixture . view . debugCustomCollectionTitles . contains ( " Client Work " ) )
XCTAssertTrue ( fixture . view . debugCustomCollectionTitles . contains ( " Product References " ) )
2026-06-30 09:02:19 -07:00
XCTAssertEqual ( fixture . view . debugCollectionRailVisibleRect . minX , 0 , accuracy : 0.5 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCollectionRailOverflowFadeVisibility , [ false , true ] )
2026-06-30 09:02:19 -07:00
fixture . view . debugScrollCollectionRailVertically ( deltaY : - 220 )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertGreaterThan ( fixture . view . debugCollectionRailVisibleRect . minX , 0 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCollectionRailOverflowFadeVisibility , [ true , true ] )
2026-06-30 09:02:19 -07:00
fixture . view . debugScrollCollectionRailVertically ( deltaY : 10_000 )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCollectionRailVisibleRect . minX , 0 , accuracy : 0.5 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCollectionRailOverflowFadeVisibility , [ false , true ] )
2026-06-30 01:12:19 -07:00
}
2026-06-30 01:29:36 -07:00
func testSelectionScrollsCardRailToKeepSelectedCardVisible ( ) {
let fixture = makePanelFixture ( )
fixture . window . setFrame ( NSRect ( x : 0 , y : 0 , width : 620 , height : 520 ) , display : true )
for index in 0. . < 8 {
fixture . store . upsert ( makeTextItem ( " Scrollable clipboard item \( index ) " , store : fixture . store ) )
drainMainQueue ( )
}
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . viewModel . selectFirstItem ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertLessThanOrEqual ( fixture . view . debugCardRailVisibleRect . minX , 1 )
fixture . viewModel . selectItem ( at : fixture . viewModel . visibleItems . count - 1 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
let visibleRect = fixture . view . debugCardRailVisibleRect
let selectedFrame = fixture . view . debugSelectedCardFrameInDocument
XCTAssertGreaterThan ( visibleRect . minX , 0 )
XCTAssertLessThanOrEqual ( selectedFrame . minX , visibleRect . maxX )
XCTAssertGreaterThanOrEqual ( visibleRect . maxX + 1 , selectedFrame . maxX )
fixture . viewModel . selectItem ( at : 0 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertLessThanOrEqual ( fixture . view . debugCardRailVisibleRect . minX , 1 )
}
2026-06-30 09:02:19 -07:00
func testVerticalWheelPansHorizontalCardRailAndClamps ( ) {
let fixture = makePanelFixture ( )
fixture . window . setFrame ( NSRect ( x : 0 , y : 0 , width : 620 , height : 520 ) , display : true )
for index in 0. . < 8 {
fixture . store . upsert ( makeTextItem ( " Wheel scroll item \( index ) " , store : fixture . store ) )
drainMainQueue ( )
}
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . viewModel . selectItem ( at : 0 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertGreaterThan (
fixture . view . debugCardRailDocumentWidth ,
fixture . view . debugCardRailVisibleRect . width + 1
)
XCTAssertEqual ( fixture . view . debugCardRailVisibleRect . minX , 0 , accuracy : 0.5 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCardRailOverflowFadeVisibility , [ false , true ] )
2026-06-30 09:02:19 -07:00
fixture . view . debugScrollCardRailVertically ( deltaY : - 240 )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertGreaterThan ( fixture . view . debugCardRailVisibleRect . minX , 0 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCardRailOverflowFadeVisibility , [ true , true ] )
2026-06-30 09:02:19 -07:00
fixture . view . debugScrollCardRailVertically ( deltaY : - 10_000 )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
let maxOffset = fixture . view . debugCardRailDocumentWidth - fixture . view . debugCardRailVisibleRect . width
XCTAssertEqual ( fixture . view . debugCardRailVisibleRect . minX , maxOffset , accuracy : 1 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCardRailOverflowFadeVisibility , [ true , false ] )
2026-06-30 09:02:19 -07:00
fixture . view . debugScrollCardRailVertically ( deltaY : 10_000 )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardRailVisibleRect . minX , 0 , accuracy : 1 )
2026-06-30 09:06:49 -07:00
XCTAssertEqual ( fixture . view . debugCardRailOverflowFadeVisibility , [ false , true ] )
2026-06-30 09:02:19 -07:00
}
2026-06-30 01:12:19 -07:00
func testFilteredEmptyStateNamesCurrentCollection ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Only text exists " , store : fixture . store ) )
drainMainQueue ( )
fixture . viewModel . sortMode = . images
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugEmptyStateText ? . title , " No images yet " )
XCTAssertEqual ( fixture . view . debugEmptyStateText ? . detail , " Image clips are saved when the clipboard contains image data. " )
}
2026-06-30 10:48:14 -07:00
func testPinnedEmptyStatePointsToPinAction ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Only text exists " , store : fixture . store ) )
drainMainQueue ( )
fixture . viewModel . sortMode = . pinned
drainMainQueue ( )
XCTAssertEqual ( fixture . view . debugEmptyStateText ? . title , " No pinned clips " )
XCTAssertEqual ( fixture . view . debugEmptyStateText ? . detail , " Use the Pin action on a card to keep important clips here. " )
}
2026-06-30 01:12:19 -07:00
func testCardsExposeContextMenuActions ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Context menu text " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual (
fixture . view . debugFirstCardMenuTitles ,
2026-06-30 09:33:00 -07:00
[ " Paste " , " Copy " , " Rename... " , " Add to Stack " , " Edit " , " Quick Look " , " Pin " , " Add to Collection " , " Capture Rules " , " - " , " Open " , " Reveal in Finder " , " - " , " Delete " ]
2026-06-30 01:12:19 -07:00
)
XCTAssertEqual (
fixture . view . debugFirstCardCollectionMenuTitles ,
[ " Useful Links " , " Important Notes " , " Code Snippets " , " Read Later " , " - " , " New Collection... " ]
)
2026-06-30 03:17:40 -07:00
XCTAssertEqual (
fixture . view . debugFirstCardCollectActionMenuTitles ,
[ " Useful Links " , " Important Notes " , " Code Snippets " , " Read Later " , " - " , " New Collection... " ]
)
2026-06-30 02:49:35 -07:00
XCTAssertEqual (
fixture . view . debugFirstCardCaptureRuleMenuTitles ,
[ " Ignore Ghostty " , " Ignore Text Items " ]
)
2026-06-30 01:12:19 -07:00
}
2026-06-30 03:28:50 -07:00
func testFilteredCardsExposeShowInClipboardContextMenuAction ( ) {
let fixture = makePanelFixture ( )
var release = makeTextItem ( " Release needle " , store : fixture . store )
release . createdAt = Date ( timeIntervalSince1970 : 100 )
release . lastUsedAt = release . createdAt
var meeting = makeTextItem ( " Meeting note " , store : fixture . store )
meeting . createdAt = Date ( timeIntervalSince1970 : 200 )
meeting . lastUsedAt = meeting . createdAt
fixture . store . upsert ( release )
fixture . store . upsert ( meeting )
drainMainQueue ( )
fixture . viewModel . searchText = " release "
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual (
fixture . view . debugFirstCardMenuTitles ,
2026-06-30 09:33:00 -07:00
[ " Paste " , " Copy " , " Show in Clipboard " , " Rename... " , " Add to Stack " , " Edit " , " Quick Look " , " Pin " , " Add to Collection " , " Capture Rules " , " - " , " Open " , " Reveal in Finder " , " - " , " Delete " ]
2026-06-30 03:28:50 -07:00
)
fixture . view . debugShowFirstCardInClipboard ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugSearchFieldText , " " )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " Meeting note " , " Release needle " ] )
XCTAssertEqual ( fixture . viewModel . selectedItem ? . payload , " Release needle " )
}
2026-06-30 01:48:35 -07:00
func testPreviewableCardsExposeQuickLookContextMenuAction ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeItem ( kind : . file , text : " /tmp/report.txt " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual (
fixture . view . debugFirstCardMenuTitles ,
2026-06-30 04:10:12 -07:00
[ " Paste " , " Copy " , " Paste Plain Text " , " Copy Plain Text " , " Rename... " , " Add to Stack " , " Quick Look " , " Pin " , " Add to Collection " , " Capture Rules " , " - " , " Open " , " Reveal in Finder " , " - " , " Delete " ]
2026-06-30 02:49:35 -07:00
)
XCTAssertEqual (
fixture . view . debugFirstCardCaptureRuleMenuTitles ,
[ " Ignore Ghostty " , " Ignore File Items " ]
2026-06-30 01:48:35 -07:00
)
}
2026-06-30 02:11:50 -07:00
func testStackedCardsExposeStackManagementActions ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Stackable text " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
fixture . viewModel . toggleSelectedStackMembership ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual (
fixture . view . debugFirstCardMenuTitles ,
2026-06-30 09:33:00 -07:00
[ " Paste " , " Copy " , " Rename... " , " Remove from Stack " , " Paste Stack Next " , " Copy Stack Next " , " Clear Stack " , " Edit " , " Quick Look " , " Pin " , " Add to Collection " , " Capture Rules " , " - " , " Open " , " Reveal in Finder " , " - " , " Delete " ]
2026-06-30 02:11:50 -07:00
)
2026-06-30 10:34:37 -07:00
XCTAssertEqual ( fixture . view . debugFirstCardVisibleActionLabels , [ " Paste " , " Copy " , " Pin " , " Collect " , " Edit " , " Preview " , " Delete " ] )
XCTAssertEqual ( fixture . view . debugStackCornerLabels , [ " Remove from Stack " ] )
2026-06-30 02:11:50 -07:00
}
2026-06-30 10:30:07 -07:00
func testStackCornerButtonTogglesAndPersistsForQueuedCards ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " Older stack item " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Newest stack item " , store : fixture . store ) )
drainMainQueue ( )
fixture . viewModel . selectItem ( at : 0 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugStackCornerLabels , [ " Add to Stack " , " Add to Stack " ] )
XCTAssertEqual ( fixture . view . debugStackCornerHiddenStates , [ false , true ] )
2026-06-30 10:34:37 -07:00
XCTAssertFalse ( fixture . view . debugFirstCardVisibleActionLabels . contains ( " Add to Stack " ) )
2026-06-30 10:30:07 -07:00
XCTAssertGreaterThan ( fixture . view . debugFirstCardStackCornerFrame . maxX , 290 )
fixture . view . debugPressFirstCardStackCornerButton ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugStatusText , " Added to Stack " )
XCTAssertEqual ( fixture . view . debugStackCornerLabels , [ " Remove from Stack " , " Add to Stack " ] )
XCTAssertEqual ( fixture . view . debugStackCornerHiddenStates , [ false , true ] )
XCTAssertTrue ( fixture . view . debugStackChipIsVisible )
XCTAssertEqual ( fixture . view . debugStackChipCount , 1 )
fixture . viewModel . selectItem ( at : 1 )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugStackCornerHiddenStates , [ false , false ] )
}
2026-06-30 02:19:03 -07:00
func testStackChipAppearsFiltersAndClearsWithStack ( ) {
let fixture = makePanelFixture ( )
fixture . store . upsert ( makeTextItem ( " First stack chip item " , store : fixture . store ) )
fixture . store . upsert ( makeTextItem ( " Second stack chip item " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertFalse ( fixture . view . debugStackChipIsVisible )
fixture . viewModel . selectItem ( at : 1 )
fixture . viewModel . toggleSelectedStackMembership ( )
fixture . viewModel . selectItem ( at : 0 )
fixture . viewModel . toggleSelectedStackMembership ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugStackChipIsVisible )
XCTAssertEqual ( fixture . view . debugStackChipCount , 2 )
XCTAssertFalse ( fixture . view . debugStackChipIsSelected )
fixture . view . debugPressStackChip ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugSelectedCollectionTitle , " Stack " )
XCTAssertTrue ( fixture . view . debugStackChipIsSelected )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . payload ) , [ " First stack chip item " , " Second stack chip item " ] )
fixture . viewModel . clearStack ( )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertFalse ( fixture . view . debugStackChipIsVisible )
XCTAssertEqual ( fixture . view . debugStackChipCount , 0 )
}
2026-06-30 01:12:19 -07:00
func testCollectionMenuOffersExistingCustomCollections ( ) {
let fixture = makePanelFixture ( )
var existing = makeTextItem ( " Existing client note " , store : fixture . store )
existing . collectionName = " Client Work "
fixture . store . upsert ( existing )
fixture . store . upsert ( makeTextItem ( " Unsorted card " , store : fixture . store ) )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual (
fixture . view . debugFirstCardCollectionMenuTitles ,
[ " Useful Links " , " Important Notes " , " Code Snippets " , " Read Later " , " Client Work " , " - " , " New Collection... " ]
)
2026-06-30 03:17:40 -07:00
XCTAssertEqual (
fixture . view . debugFirstCardCollectActionMenuTitles ,
[ " Useful Links " , " Important Notes " , " Code Snippets " , " Read Later " , " Client Work " , " - " , " New Collection... " ]
)
2026-06-30 01:12:19 -07:00
}
func testBottomSafeInsetIsAppliedToPanelContent ( ) {
let fixture = makePanelFixture ( )
fixture . view . setBottomSafeInset ( 108 )
XCTAssertEqual ( fixture . view . debugContentInsets . bottom , 108 )
}
func testInternalLookingTextDoesNotBecomePrimaryCardTitle ( ) {
let fixture = makePanelFixture ( )
let item = makeTextItem ( " clipbored-flow-test- \( UUID ( ) . uuidString ) " , store : fixture . store )
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Text: Copied text " ] )
}
func testLinkCardsUseReadableTitleAndAddressPreview ( ) {
let fixture = makePanelFixture ( )
let item = makeItem (
kind : . url ,
displayText : " Release notes " ,
payload : " https://www.example.com/releases/v1?utm_source=copy " ,
store : fixture . store
)
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Link: Release notes " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Release notes|example.com/releases/v1|example.com " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " link-preview " ] )
}
func testLinkCardsUseMediaPreviewWhenThumbnailExists ( ) throws {
let fixture = makePanelFixture ( )
let id = UUID ( )
let paths = try XCTUnwrap ( fixture . cacheService . cacheImage ( sampleImage ( ) , id : id ) )
let item = ClipboardItem (
id : id ,
kind : . url ,
displayText : " Lookbook " ,
payload : " https://example.com/lookbook " ,
payloadHash : fixture . store . hashString ( " https://example.com/lookbook " ) ,
createdAt : Date ( ) ,
lastUsedAt : Date ( ) ,
useCount : 0 ,
sourceApp : " Safari " ,
imagePath : paths . full ,
thumbnailPath : paths . thumb
)
fixture . store . upsert ( item )
fixture . viewModel . sortMode = . links
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Link: Lookbook " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Lookbook|example.com/lookbook|example.com " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " link-media-preview " ] )
}
func testRichTextCardsUseDisplayTextInsteadOfManagedPayloadPath ( ) {
let fixture = makePanelFixture ( )
let item = makeItem (
kind : . richText ,
displayText : " Styled note " ,
payload : " /tmp/clipbored-managed-rich-text.rtf " ,
store : fixture . store
)
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Rich Text: Styled note " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Styled note|Styled note|11 characters " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " rich-text-preview " ] )
}
func testFileCardsUseFilenameLocationAndType ( ) {
let fixture = makePanelFixture ( )
let fileURL = FileManager . default . homeDirectoryForCurrentUser
. appendingPathComponent ( " Documents " )
. appendingPathComponent ( " Project Plan.pdf " )
let item = makeItem ( kind : . file , displayText : " File " , payload : fileURL . path , store : fixture . store )
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " File: Project Plan.pdf " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Project Plan.pdf|~/Documents|PDF " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " file-preview " ] )
}
2026-06-30 04:10:12 -07:00
func testRenamedClipsUseCustomTitleInCardsAndSearch ( ) {
let fixture = makePanelFixture ( )
let fileURL = FileManager . default . homeDirectoryForCurrentUser
. appendingPathComponent ( " Documents " )
. appendingPathComponent ( " Project Plan.pdf " )
let item = makeItem ( kind : . file , displayText : " File " , payload : fileURL . path , store : fixture . store )
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertTrue ( fixture . view . debugFirstCardMenuTitles . contains ( " Rename... " ) )
fixture . view . debugRenameFirstCard ( to : " Client Launch Brief " )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " File: Client Launch Brief " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Client Launch Brief|~/Documents|PDF " ] )
XCTAssertEqual ( fixture . view . debugStatusText , " Renamed clip " )
XCTAssertEqual ( fixture . view . debugStatusTone , " action " )
fixture . viewModel . searchText = " launch "
drainMainQueue ( )
XCTAssertEqual ( fixture . viewModel . visibleItems . map ( \ . id ) , [ item . id ] )
}
2026-06-30 01:12:19 -07:00
func testMultipleFileCardsUseCountAndSharedLocation ( ) throws {
let fixture = makePanelFixture ( )
let directory = makeTempDirectory ( )
let firstURL = directory . appendingPathComponent ( " Brief.pdf " )
let secondURL = directory . appendingPathComponent ( " Invoice.csv " )
try Data ( " brief " . utf8 ) . write ( to : firstURL )
try Data ( " invoice " . utf8 ) . write ( to : secondURL )
let item = makeItem (
kind : . file ,
displayText : " 2 files " ,
payload : FilePayload . payload ( from : [ firstURL , secondURL ] ) ,
store : fixture . store
)
fixture . store . upsert ( item )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " File: 2 files " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " 2 files| \( directory . path ) |2 files " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " file-preview " ] )
}
func testExistingFileCardsUseFullBleedMediaPreviewLayout ( ) throws {
let fixture = makePanelFixture ( )
let imageURL = makeTempDirectory ( ) . appendingPathComponent ( " Campaign Reference.png " )
let imageData = try XCTUnwrap ( sampleImage ( ) . pngData ( ) )
try imageData . write ( to : imageURL )
let item = makeItem ( kind : . file , displayText : " File " , payload : imageURL . path , store : fixture . store )
fixture . store . upsert ( item )
fixture . viewModel . sortMode = . files
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " File: Campaign Reference.png " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " file-media-preview " ] )
}
func testPdfAndImageCardsUseSpecificPreviewText ( ) {
let fixture = makePanelFixture ( )
let pdfURL = FileManager . default . homeDirectoryForCurrentUser
. appendingPathComponent ( " Downloads " )
. appendingPathComponent ( " Reference Guide.pdf " )
let pdf = makeItem (
kind : . pdf ,
displayText : " PDF " ,
payload : pdfURL . path ,
store : fixture . store ,
ocrText : " Quarterly metrics \n Second page "
)
fixture . store . upsert ( pdf )
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " PDF: Reference Guide.pdf " ] )
XCTAssertEqual (
fixture . view . debugCardPreviewSummaries ,
[ " Reference Guide.pdf|Quarterly metrics Second page|PDF " ]
)
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " file-preview " ] )
let image = makeItem (
kind : . image ,
displayText : " Image " ,
payload : " " ,
store : fixture . store ,
ocrText : " Receipt total $42 "
)
fixture . store . upsert ( image )
fixture . viewModel . sortMode = . images
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Image: Receipt total $42 " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Receipt total $42|Receipt total $42|OCR text " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " text-fallback-preview " ] )
}
func testImageCardsUseMediaPreviewWhenThumbnailExists ( ) {
let fixture = makePanelFixture ( )
let item = makeCachedImageItem ( store : fixture . store , cacheService : fixture . cacheService )
fixture . store . upsert ( item )
fixture . viewModel . sortMode = . images
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Image: Campaign portrait " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " media-preview " ] )
}
func testAudioCardsUseSpecificPreviewText ( ) {
let fixture = makePanelFixture ( )
let item = makeItem (
kind : . audio ,
displayText : " Audio (14 KB) " ,
payload : " /tmp/clipbored-audio.sound " ,
store : fixture . store
)
fixture . store . upsert ( item )
fixture . viewModel . sortMode = . audio
drainMainQueue ( )
fixture . window . contentView ? . layoutSubtreeIfNeeded ( )
XCTAssertEqual ( fixture . view . debugCardAccessibilityLabels , [ " Audio: Audio (14 KB) " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewSummaries , [ " Audio (14 KB)|Sound clip|Audio " ] )
XCTAssertEqual ( fixture . view . debugCardPreviewStyles , [ " audio-preview " ] )
}
private func makePanelWithPanelView ( ) -> ( NSWindow , ClipboardPanelView ) {
let fixture = makePanelFixture ( )
return ( fixture . window , fixture . view )
}
private func makePanelFixture ( ) -> PanelFixture {
let settings = makeSettings ( )
let cacheService = ClipboardCacheService ( )
let store = makeStore ( settings : settings , cacheService : cacheService )
let viewModel = ClipboardPanelViewModel ( store : store , settings : settings , cacheService : cacheService )
2026-06-30 08:56:45 -07:00
let previewProbe = PreviewProbe ( )
2026-06-30 01:12:19 -07:00
let view = ClipboardPanelView (
viewModel : viewModel ,
onClose : { } ,
2026-06-30 08:56:45 -07:00
onSettings : { } ,
onPreview : { previewProbe . requestCount += 1 }
2026-06-30 01:12:19 -07:00
)
let window = NSPanel (
contentRect : NSRect ( x : 0 , y : 0 , width : 1200 , height : 520 ) ,
styleMask : [ . titled , . closable ] ,
backing : . buffered ,
defer : false
)
window . contentView = view
window . makeKeyAndOrderFront ( nil )
return PanelFixture (
window : window ,
view : view ,
viewModel : viewModel ,
settings : settings ,
store : store ,
2026-06-30 08:56:45 -07:00
cacheService : cacheService ,
previewProbe : previewProbe
2026-06-30 01:12:19 -07:00
)
}
private func makeSettings ( ) -> SettingsModel {
let settings = SettingsModel ( defaults : UserDefaults ( suiteName : " com.clipbored.viewtest. \( UUID ( ) . uuidString ) " ) ! )
settings . maxHistoryItems = 10
settings . pruneDuplicates = false
return settings
}
private func makeStore ( settings : SettingsModel , cacheService : ClipboardCacheService ) -> ClipboardStore {
ClipboardStore ( settings : settings , cacheService : cacheService , baseURL : makeTempDirectory ( ) )
}
private func makeTempDirectory ( ) -> URL {
let directory = FileManager . default . temporaryDirectory
. appendingPathComponent ( " clipbored-viewtest " )
. appendingPathComponent ( UUID ( ) . uuidString )
try ? FileManager . default . createDirectory ( at : directory , withIntermediateDirectories : true )
tempURLs . append ( directory )
return directory
}
private func makeTextItem ( _ text : String , store : ClipboardStore ) -> ClipboardItem {
makeItem ( kind : . text , text : text , store : store )
}
private func makeItem ( kind : ClipboardItemKind , text : String , store : ClipboardStore ) -> ClipboardItem {
makeItem ( kind : kind , displayText : text , payload : text , store : store )
}
private func makeItem (
kind : ClipboardItemKind ,
displayText : String ,
payload : String ,
store : ClipboardStore ,
ocrText : String ? = nil
) -> ClipboardItem {
ClipboardItem (
id : UUID ( ) ,
kind : kind ,
displayText : displayText ,
payload : payload ,
payloadHash : store . hashString ( payload ) ,
createdAt : Date ( ) ,
lastUsedAt : Date ( ) ,
useCount : 0 ,
sourceApp : " Ghostty " ,
imagePath : nil ,
thumbnailPath : nil ,
ocrText : ocrText
)
}
private func makeCachedImageItem ( store : ClipboardStore , cacheService : ClipboardCacheService ) -> ClipboardItem {
let id = UUID ( )
let paths = cacheService . cacheImage ( sampleImage ( ) , id : id )
return ClipboardItem (
id : id ,
kind : . image ,
displayText : " Campaign portrait " ,
payload : paths ? . full ? ? " " ,
payloadHash : store . hashString ( " campaign-portrait " ) ,
createdAt : Date ( ) ,
lastUsedAt : Date ( ) ,
useCount : 0 ,
sourceApp : " Photos " ,
imagePath : paths ? . full ,
thumbnailPath : paths ? . thumb ,
ocrText : nil
)
}
private func sampleImage ( ) -> NSImage {
let image = NSImage ( size : NSSize ( width : 180 , height : 140 ) )
image . lockFocus ( )
NSColor ( calibratedRed : 1.0 , green : 0.76 , blue : 0.20 , alpha : 1 ) . setFill ( )
NSRect ( x : 0 , y : 0 , width : 180 , height : 140 ) . fill ( )
NSColor ( calibratedRed : 0.92 , green : 0.20 , blue : 0.26 , alpha : 1 ) . setFill ( )
NSBezierPath ( ovalIn : NSRect ( x : 34 , y : 24 , width : 82 , height : 82 ) ) . fill ( )
NSColor ( calibratedRed : 0.05 , green : 0.42 , blue : 0.86 , alpha : 1 ) . setFill ( )
NSBezierPath ( roundedRect : NSRect ( x : 92 , y : 45 , width : 58 , height : 48 ) , xRadius : 12 , yRadius : 12 ) . fill ( )
image . unlockFocus ( )
return image
}
private func drainMainQueue ( ) {
for _ in 0. . < 20 {
RunLoop . main . run ( until : Date ( ) . addingTimeInterval ( 0.01 ) )
}
}
}