@@ -458,6 +458,83 @@ - (void) testGuifontSystemMonospace {
458458 [self waitForVimClose ];
459459}
460460
461+ // / Test that dark mode settings work and the corresponding Vim bindings are functional.
462+ // /
463+ // / Note that `v:os_appearance` and OSAppearanceChanged respond to the view's appearance
464+ // / rather than the OS setting. When using manual light/dark or "use background" settings,
465+ // / they do not reflect the current OS dark mode setting.
466+ - (void ) testDarkMode {
467+ NSUserDefaults *ud = NSUserDefaults .standardUserDefaults ;
468+
469+ MMAppController *app = MMAppController.sharedInstance ;
470+
471+ [app openNewWindow: NewWindowClean activate: YES ];
472+ [self waitForVimOpenAndMessages ];
473+
474+ MMVimView *vimView = [[[app keyVimController ] windowController ] vimView ];
475+
476+ // We just use the system appearance to determine the initial state. Otherwise
477+ // we have to change the system appearance to light mode first which we don't
478+ // have permission to do.
479+ const BOOL systemUsingDarkMode = [[ud stringForKey: @" AppleInterfaceStyle" ] isEqualToString: @" Dark" ];
480+ const NSAppearance *systemAppearance = systemUsingDarkMode ?
481+ [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua ] : [NSAppearance appearanceNamed: NSAppearanceNameAqua ];
482+
483+ // Default setting uses system appearance
484+ XCTAssertEqualObjects (vimView.effectiveAppearance , systemAppearance);
485+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" v:os_appearance" ], systemUsingDarkMode ? @" 1" : @" 0" );
486+
487+ // Cache original settings / set up setting overrides
488+ NSDictionary <NSString *, id > *defaults = [ud volatileDomainForName: NSArgumentDomain ];
489+ NSMutableDictionary <NSString *, id > *newDefaults = [defaults mutableCopy ];
490+
491+ // Manual Light / Dark mode setting
492+ newDefaults[MMAppearanceModeSelectionKey] = [NSNumber numberWithInt: MMAppearanceModeSelectionLight];
493+ [ud setVolatileDomain: newDefaults forName: NSArgumentDomain ];
494+ [app refreshAllAppearances ];
495+ XCTAssertEqualObjects (vimView.effectiveAppearance , [NSAppearance appearanceNamed: NSAppearanceNameAqua ]);
496+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" v:os_appearance" ], @" 0" );
497+
498+ // Set up a listener for OSAppearanceChanged event to make sure it's called
499+ // when the view appearance changes.
500+ [self sendStringToVim: @" :let g:os_appearance_changed_called=0\n " withMods: 0 ];
501+ [self sendStringToVim: @" :autocmd OSAppearanceChanged * let g:os_appearance_changed_called+=1\n " withMods: 0 ];
502+ [self waitForEventHandlingAndVimProcess ];
503+
504+ newDefaults[MMAppearanceModeSelectionKey] = [NSNumber numberWithInt: MMAppearanceModeSelectionDark];
505+ [ud setVolatileDomain: newDefaults forName: NSArgumentDomain ];
506+ [app refreshAllAppearances ];
507+ XCTAssertEqualObjects (vimView.effectiveAppearance , [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua ]);
508+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" v:os_appearance" ], @" 1" );
509+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" g:os_appearance_changed_called" ], @" 1" );
510+
511+ // "Use background" setting
512+ [self sendStringToVim: @" :set background=dark\n " withMods: 0 ];
513+ [self waitForEventHandlingAndVimProcess ];
514+
515+ newDefaults[MMAppearanceModeSelectionKey] = [NSNumber numberWithInt: MMAppearanceModeSelectionBackgroundOption];
516+ [NSUserDefaults .standardUserDefaults setVolatileDomain: newDefaults forName: NSArgumentDomain ];
517+ [app refreshAllAppearances ];
518+ XCTAssertEqualObjects (vimView.effectiveAppearance , [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua ]);
519+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" v:os_appearance" ], @" 1" );
520+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" g:os_appearance_changed_called" ], @" 1" ); // we stayed in dark mode, so OSAppearnceChanged didn't trigger
521+
522+ [self sendStringToVim: @" :set background=light\n " withMods: 0 ];
523+ [self waitForEventHandlingAndVimProcess ];
524+ XCTAssertEqualObjects (vimView.effectiveAppearance , [NSAppearance appearanceNamed: NSAppearanceNameAqua ]);
525+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" v:os_appearance" ], @" 0" );
526+ XCTAssertEqualObjects ([[app keyVimController ] evaluateVimExpression: @" g:os_appearance_changed_called" ], @" 2" );
527+
528+ // Restore original settings and make sure it's reset
529+ [NSUserDefaults .standardUserDefaults setVolatileDomain: defaults forName: NSArgumentDomain ];
530+ [app refreshAllAppearances ];
531+ XCTAssertEqualObjects (vimView.effectiveAppearance , systemAppearance);
532+
533+ // Clean up
534+ [[app keyVimController ] sendMessage: VimShouldCloseMsgID data: nil ];
535+ [self waitForVimClose ];
536+ }
537+
461538// / Test that document icon is shown in title bar when enabled.
462539- (void ) testTitlebarDocumentIcon {
463540 MMAppController *app = MMAppController.sharedInstance ;
0 commit comments