diff --git a/Calendr.xcodeproj/project.pbxproj b/Calendr.xcodeproj/project.pbxproj index 4506564d..ba4672ae 100644 --- a/Calendr.xcodeproj/project.pbxproj +++ b/Calendr.xcodeproj/project.pbxproj @@ -1133,7 +1133,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.6.6; + MARKETING_VERSION = 1.6.7; PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h"; @@ -1157,7 +1157,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.6.6; + MARKETING_VERSION = 1.6.7; PRODUCT_BUNDLE_IDENTIFIER = br.paker.Calendr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Calendr/Config/Calendr-Bridging-Header.h"; diff --git a/Calendr/Assets/Accessibility.swift b/Calendr/Assets/Accessibility.swift index e6628258..b86bf36e 100644 --- a/Calendr/Assets/Accessibility.swift +++ b/Calendr/Assets/Accessibility.swift @@ -57,6 +57,8 @@ enum Accessibility { enum General { static let view = "settings_general_view" + static let dateFormatDropdown = "settings_general_date_format_dropdown" + static let dateFormatInput = "settings_general_date_format_input" } enum Calendars { diff --git a/Calendr/Assets/Generated/Strings.generated.swift b/Calendr/Assets/Generated/Strings.generated.swift index 13c1c1e6..02c9f3b3 100644 --- a/Calendr/Assets/Generated/Strings.generated.swift +++ b/Calendr/Assets/Generated/Strings.generated.swift @@ -107,6 +107,8 @@ internal enum Strings { internal enum MenuBar { /// Date format internal static let dateFormat = Strings.tr("Localizable", "settings.menu_bar.date_format", fallback: "Date format") + /// Custom + internal static let dateFormatCustom = Strings.tr("Localizable", "settings.menu_bar.date_format_custom", fallback: "Custom") /// Shorten if 'notch' is present internal static let nextEventDetectNotch = Strings.tr("Localizable", "settings.menu_bar.next_event_detect_notch", fallback: "Shorten if 'notch' is present") /// Width @@ -117,10 +119,6 @@ internal enum Strings { internal static let showIcon = Strings.tr("Localizable", "settings.menu_bar.show_icon", fallback: "Show icon") /// Show next event internal static let showNextEvent = Strings.tr("Localizable", "settings.menu_bar.show_next_event", fallback: "Show next event") - internal enum DateFormat { - /// Configurable in System Preferences - internal static let info = Strings.tr("Localizable", "settings.menu_bar.date_format.info", fallback: "Configurable in System Preferences") - } } internal enum Tab { /// About diff --git a/Calendr/Assets/de.lproj/Localizable.strings b/Calendr/Assets/de.lproj/Localizable.strings index a18578c9..bf3e68d2 100644 --- a/Calendr/Assets/de.lproj/Localizable.strings +++ b/Calendr/Assets/de.lproj/Localizable.strings @@ -16,7 +16,7 @@ "settings.menu_bar.show_icon" = "Zeige Icon"; "settings.menu_bar.show_date" = "Zeige Datum"; "settings.menu_bar.date_format" = "Datumsformat"; -"settings.menu_bar.date_format.info" = "In Systemeinstellungen konfigurieren"; +"settings.menu_bar.date_format_custom" = "Benutzerdefiniert"; "settings.menu_bar.show_next_event" = "Zeige nächsten Termin"; "settings.menu_bar.next_event_length" = "Breite"; "settings.menu_bar.next_event_detect_notch" = "Kürzen, wenn 'notch' vorhanden ist"; diff --git a/Calendr/Assets/en.lproj/Localizable.strings b/Calendr/Assets/en.lproj/Localizable.strings index 6809337a..576a4dd1 100644 --- a/Calendr/Assets/en.lproj/Localizable.strings +++ b/Calendr/Assets/en.lproj/Localizable.strings @@ -16,7 +16,7 @@ "settings.menu_bar.show_icon" = "Show icon"; "settings.menu_bar.show_date" = "Show date"; "settings.menu_bar.date_format" = "Date format"; -"settings.menu_bar.date_format.info" = "Configurable in System Preferences"; +"settings.menu_bar.date_format_custom" = "Custom"; "settings.menu_bar.show_next_event" = "Show next event"; "settings.menu_bar.next_event_length" = "Width"; "settings.menu_bar.next_event_detect_notch" = "Shorten if 'notch' is present"; diff --git a/Calendr/Assets/es.lproj/Localizable.strings b/Calendr/Assets/es.lproj/Localizable.strings index 4f98347e..f68dc1db 100644 --- a/Calendr/Assets/es.lproj/Localizable.strings +++ b/Calendr/Assets/es.lproj/Localizable.strings @@ -16,7 +16,7 @@ "settings.menu_bar.show_icon" = "Mostrar icono"; "settings.menu_bar.show_date" = "Mostrar fecha"; "settings.menu_bar.date_format" = "Formato de fecha"; -"settings.menu_bar.date_format.info" = "Configurable en Preferencias del Sistema"; +"settings.menu_bar.date_format_custom" = "Personalizado"; "settings.menu_bar.show_next_event" = "Mostrar próximo evento"; "settings.menu_bar.next_event_length" = "Ancho"; "settings.menu_bar.next_event_detect_notch" = "Acortar si 'notch' está presente"; diff --git a/Calendr/Assets/fr.lproj/Localizable.strings b/Calendr/Assets/fr.lproj/Localizable.strings index b0eec656..40ee51e3 100644 --- a/Calendr/Assets/fr.lproj/Localizable.strings +++ b/Calendr/Assets/fr.lproj/Localizable.strings @@ -16,7 +16,7 @@ "settings.menu_bar.show_icon" = "Afficher l'icône"; "settings.menu_bar.show_date" = "Afficher la date"; "settings.menu_bar.date_format" = "Format de date"; -"settings.menu_bar.date_format.info" = "Configurable dans les Préférences Système"; +"settings.menu_bar.date_format_custom" = "Personnalisé"; "settings.menu_bar.show_next_event" = "Afficher prochain événement"; "settings.menu_bar.next_event_length" = "Largeur"; "settings.menu_bar.next_event_detect_notch" = "Raccourcir si 'notch' est présent"; diff --git a/Calendr/Assets/pt.lproj/Localizable.strings b/Calendr/Assets/pt.lproj/Localizable.strings index 6802d694..cfc3d32e 100644 --- a/Calendr/Assets/pt.lproj/Localizable.strings +++ b/Calendr/Assets/pt.lproj/Localizable.strings @@ -16,7 +16,7 @@ "settings.menu_bar.show_icon" = "Mostrar ícone"; "settings.menu_bar.show_date" = "Mostrar data"; "settings.menu_bar.date_format" = "Formato de data"; -"settings.menu_bar.date_format.info" = "Configurável nas Preferências do Sistema"; +"settings.menu_bar.date_format_custom" = "Personalizado"; "settings.menu_bar.show_next_event" = "Mostrar próximo evento"; "settings.menu_bar.next_event_length" = "Largura"; "settings.menu_bar.next_event_detect_notch" = "Encurtar se 'notch' estiver presente"; diff --git a/Calendr/Main/AppDelegate.swift b/Calendr/Main/AppDelegate.swift index 15ba0ae5..c58d9c2f 100644 --- a/Calendr/Main/AppDelegate.swift +++ b/Calendr/Main/AppDelegate.swift @@ -38,5 +38,31 @@ class AppDelegate: NSObject, NSApplicationDelegate { userDefaults: .standard, notificationCenter: .default ) + + setUpKeyboard() + } + + // 🔨 This will not be visible, but it allows us to use basic commands in text fields + private func setUpKeyboard() { + + let mainMenu = NSMenu(title: "MainMenu") + let menuItem = mainMenu.addItem(withTitle: "", action: nil, keyEquivalent: "") + + let submenu = NSMenu() + submenu.addItem(withTitle: "Close Window", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w") + submenu.addItem(withTitle: "Undo", action: #selector(EditMenuActions.undo(_:)), keyEquivalent: "z") + submenu.addItem(withTitle: "Redo", action: #selector(EditMenuActions.redo(_:)), keyEquivalent: "Z") + submenu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x") + submenu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c") + submenu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v") + submenu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a") + + mainMenu.setSubmenu(submenu, for: menuItem) + NSApp.mainMenu = mainMenu } } + +@objc private protocol EditMenuActions { + func redo(_ sender: AnyObject) + func undo(_ sender: AnyObject) +} diff --git a/Calendr/Main/MainViewController.swift b/Calendr/Main/MainViewController.swift index 6f415678..5ecb9705 100644 --- a/Calendr/Main/MainViewController.swift +++ b/Calendr/Main/MainViewController.swift @@ -519,7 +519,8 @@ class MainViewController: NSViewController { NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event -> NSEvent? in - if event.keyCode == 53, let vc = self?.presentedViewControllers?.last { + if let vc = self?.presentedViewControllers?.last { + guard event.keyCode == 53 else { return event } self?.dismiss(vc) return nil } diff --git a/Calendr/MenuBar/StatusItemViewModel.swift b/Calendr/MenuBar/StatusItemViewModel.swift index 7181e8e6..2b511d8b 100644 --- a/Calendr/MenuBar/StatusItemViewModel.swift +++ b/Calendr/MenuBar/StatusItemViewModel.swift @@ -24,10 +24,20 @@ class StatusItemViewModel { .notification(NSLocale.currentLocaleDidChangeNotification) .void() - let dateFormatterObservable = settings.statusItemDateStyle + let dateFormatterObservable = Observable + .combineLatest(settings.statusItemDateStyle, settings.statusItemDateFormat) .repeat(when: localeChangeObservable) - .map { dateStyle in - DateFormatter(calendar: dateProvider.calendar).with(style: dateStyle) + .map { style, format in + + let formatter = DateFormatter(calendar: dateProvider.calendar) + + if style.isCustom { + formatter.dateFormat = format + } else { + formatter.dateStyle = style + } + + return formatter } let shouldCompact = Observable @@ -74,7 +84,8 @@ class StatusItemViewModel { if title.length > 0 { title.append(NSAttributedString(string: " ")) } - title.append(NSAttributedString(string: dateFormatter.string(from: date))) + let text = dateFormatter.string(from: date) + title.append(NSAttributedString(string: text.isEmpty ? "???" : text)) } return title diff --git a/Calendr/Settings/GeneralSettingsViewController.swift b/Calendr/Settings/GeneralSettingsViewController.swift index f6a3ff03..5846cf7f 100644 --- a/Calendr/Settings/GeneralSettingsViewController.swift +++ b/Calendr/Settings/GeneralSettingsViewController.swift @@ -24,6 +24,7 @@ class GeneralSettingsViewController: NSViewController { private let fadePastEventsRadio = Radio(title: Strings.Settings.Events.Finished.fade) private let hidePastEventsRadio = Radio(title: Strings.Settings.Events.Finished.hide) private let dateFormatDropdown = Dropdown() + private let dateFormatTextField = NSTextField() private let nextEventLengthSlider = NSSlider.make(minValue: 10, maxValue: 30) private let transparencySlider = NSSlider.make(minValue: 0, maxValue: 5) @@ -46,6 +47,9 @@ class GeneralSettingsViewController: NSViewController { view.setAccessibilityElement(true) view.setAccessibilityIdentifier(Accessibility.Settings.General.view) + + dateFormatDropdown.setAccessibilityIdentifier(Accessibility.Settings.General.dateFormatDropdown) + dateFormatTextField.setAccessibilityIdentifier(Accessibility.Settings.General.dateFormatInput) } override func loadView() { @@ -89,13 +93,12 @@ class GeneralSettingsViewController: NSViewController { ]) .with(orientation: .vertical) + dateFormatTextField.placeholderString = viewModel.dateFormatPlaceholder + let dateFormat = NSStackView(views: [ Label(text: "\(Strings.Settings.MenuBar.dateFormat):"), dateFormatDropdown, - Label( - text: " \(Strings.Settings.MenuBar.DateFormat.info)", - font: .systemFont(ofSize: 10, weight: .light) - ) + dateFormatTextField ]) .with(orientation: .vertical) @@ -240,7 +243,7 @@ class GeneralSettingsViewController: NSViewController { DateStyle(rawValue: UInt(dropdown.indexOfSelectedItem + 1)) ?? .none }, setter: { (dropdown: NSPopUpButton, style: DateStyle) in - dropdown.selectItem(at: Int(style.rawValue) - 1) + dropdown.selectItem(at: (style.isCustom ? dropdown.numberOfItems : Int(style.rawValue)) - 1) } ) @@ -250,7 +253,7 @@ class GeneralSettingsViewController: NSViewController { .disposed(by: disposeBag) Observable.combineLatest( - viewModel.dateFormatOptions, viewModel.statusItemDateStyle + viewModel.dateStyleOptions, viewModel.statusItemDateStyle ) .bind { [dateFormatDropdown] options, dateStyle in dateFormatDropdown.removeAllItems() @@ -258,6 +261,26 @@ class GeneralSettingsViewController: NSViewController { dateFormatStyle.onNext(dateStyle) } .disposed(by: disposeBag) + + viewModel.isDateFormatInputVisible + .map(!) + .bind(to: dateFormatTextField.rx.isHidden) + .disposed(by: disposeBag) + + viewModel.isDateFormatInputVisible + .map(true) + .bind(to: view.rx.needsLayout) + .disposed(by: disposeBag) + + dateFormatTextField.rx.text + .skip(1) + .skipNil() + .bind(to: viewModel.statusItemDateFormatObserver) + .disposed(by: disposeBag) + + viewModel.statusItemDateFormat + .bind(to: dateFormatTextField.rx.text) + .disposed(by: disposeBag) } private func bind(control: NSButton, observable: Observable, observer: AnyObserver) { diff --git a/Calendr/Settings/Prefs+UserDefaults.swift b/Calendr/Settings/Prefs+UserDefaults.swift index 0eb117cf..ec2f0fd0 100644 --- a/Calendr/Settings/Prefs+UserDefaults.swift +++ b/Calendr/Settings/Prefs+UserDefaults.swift @@ -12,6 +12,7 @@ enum Prefs { static let statusItemIconEnabled = "status_item_icon_enabled" static let statusItemDateEnabled = "status_item_date_enabled" static let statusItemDateStyle = "status_item_date_style" + static let statusItemDateFormat = "status_item_date_format" static let showEventStatusItem = "show_event_status_item" static let eventStatusItemLength = "event_status_item_length" static let eventStatusItemDetectNotch = "event_status_item_detect_notch" @@ -44,6 +45,11 @@ extension UserDefaults { get { UInt(integer(forKey: Prefs.statusItemDateStyle)) } set { set(newValue, forKey: Prefs.statusItemDateStyle) } } + + @objc dynamic var statusItemDateFormat: String { + get { string(forKey: Prefs.statusItemDateFormat) ?? "" } + set { set(newValue, forKey: Prefs.statusItemDateFormat) } + } @objc dynamic var showEventStatusItem: Bool { get { bool(forKey: Prefs.showEventStatusItem) } diff --git a/Calendr/Settings/SettingsViewModel.swift b/Calendr/Settings/SettingsViewModel.swift index 318a1b99..088badb6 100644 --- a/Calendr/Settings/SettingsViewModel.swift +++ b/Calendr/Settings/SettingsViewModel.swift @@ -9,6 +9,12 @@ import Cocoa import RxSwift typealias DateStyle = DateFormatter.Style + +extension DateStyle { + static let options: [Self] = [.short, .medium, .long, .full] + var isCustom: Bool { !Self.options.contains(self) } +} + typealias PopoverMaterial = NSVisualEffectView.Material extension PopoverMaterial { @@ -29,6 +35,7 @@ protocol StatusItemSettings { var showStatusItemIcon: Observable { get } var showStatusItemDate: Observable { get } var statusItemDateStyle: Observable { get } + var statusItemDateFormat: Observable { get } var eventStatusItemDetectNotch: Observable { get } } @@ -59,6 +66,7 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings let toggleStatusItemIcon: AnyObserver let toggleStatusItemDate: AnyObserver let statusItemDateStyleObserver: AnyObserver + let statusItemDateFormatObserver: AnyObserver let toggleEventStatusItem: AnyObserver let eventStatusItemLengthObserver: AnyObserver let toggleEventStatusItemDetectNotch: AnyObserver @@ -70,9 +78,13 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings let calendarScalingObserver: AnyObserver // Observables - var showStatusItemIcon: Observable - var showStatusItemDate: Observable - var statusItemDateStyle: Observable + let showStatusItemIcon: Observable + let showStatusItemDate: Observable + let statusItemDateStyle: Observable + let dateStyleOptions: Observable<[String]> + let statusItemDateFormat: Observable + let dateFormatPlaceholder = "E d MMM YYYY" + let isDateFormatInputVisible: Observable let showEventStatusItem: Observable let eventStatusItemLength: Observable let eventStatusItemDetectNotch: Observable @@ -84,8 +96,6 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings let popoverMaterial: Observable let calendarScaling: Observable - let dateFormatOptions: Observable<[String]> - init( dateProvider: DateProviding, userDefaults: UserDefaults, @@ -95,7 +105,8 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings userDefaults.register(defaults: [ Prefs.statusItemIconEnabled: true, Prefs.statusItemDateEnabled: true, - Prefs.statusItemDateStyle: 1, + Prefs.statusItemDateStyle: DateStyle.short.rawValue, + Prefs.statusItemDateFormat: dateFormatPlaceholder, Prefs.showEventStatusItem: false, Prefs.eventStatusItemLength: 18, Prefs.eventStatusItemDetectNotch: false, @@ -110,6 +121,7 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings toggleStatusItemIcon = userDefaults.rx.observer(for: \.statusItemIconEnabled) toggleStatusItemDate = userDefaults.rx.observer(for: \.statusItemDateEnabled) statusItemDateStyleObserver = userDefaults.rx.observer(for: \.statusItemDateStyle).mapObserver(\.rawValue) + statusItemDateFormatObserver = userDefaults.rx.observer(for: \.statusItemDateFormat) toggleEventStatusItem = userDefaults.rx.observer(for: \.showEventStatusItem) eventStatusItemLengthObserver = userDefaults.rx.observer(for: \.eventStatusItemLength) toggleEventStatusItemDetectNotch = userDefaults.rx.observer(for: \.eventStatusItemDetectNotch) @@ -131,6 +143,7 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings showStatusItemIcon = statusItemIconAndDate.map(\.0) showStatusItemDate = statusItemIconAndDate.map(\.1) statusItemDateStyle = userDefaults.rx.observe(\.statusItemDateStyle).map { DateStyle(rawValue: $0) ?? .none } + statusItemDateFormat = userDefaults.rx.observe(\.statusItemDateFormat) showEventStatusItem = userDefaults.rx.observe(\.showEventStatusItem) eventStatusItemLength = userDefaults.rx.observe(\.eventStatusItemLength) eventStatusItemDetectNotch = userDefaults.rx.observe(\.eventStatusItemDetectNotch) @@ -141,22 +154,29 @@ class SettingsViewModel: StatusItemSettings, NextEventSettings, CalendarSettings popoverTransparency = userDefaults.rx.observe(\.transparencyLevel) calendarScaling = userDefaults.rx.observe(\.calendarScaling) - dateFormatOptions = notificationCenter.rx.notification(NSLocale.currentLocaleDidChangeNotification) + dateStyleOptions = notificationCenter.rx.notification(NSLocale.currentLocaleDidChangeNotification) .void() .startWith(()) .map { let dateFormatter = DateFormatter(calendar: dateProvider.calendar) var options: [String] = [] - for i: UInt in 1...4 { - dateFormatter.dateStyle = DateStyle(rawValue: i) ?? .none + for i: UInt in DateStyle.options.map(\.rawValue) { + dateFormatter.dateStyle = .init(rawValue: i) ?? .none options.append(dateFormatter.string(from: dateProvider.now)) } + + options.append("\(Strings.Settings.MenuBar.dateFormatCustom)...") return options } .share(replay: 1) + isDateFormatInputVisible = statusItemDateStyle + .map(\.isCustom) + .distinctUntilChanged() + .share(replay: 1) + popoverMaterial = popoverTransparency.map(PopoverMaterial.init(transparency:)) } } diff --git a/CalendrTests/Mocks/MockStatusItemSettings.swift b/CalendrTests/Mocks/MockStatusItemSettings.swift index 1320a96b..ff7fc866 100644 --- a/CalendrTests/Mocks/MockStatusItemSettings.swift +++ b/CalendrTests/Mocks/MockStatusItemSettings.swift @@ -13,17 +13,20 @@ class MockStatusItemSettings: StatusItemSettings { var toggleIcon: AnyObserver var toggleDate: AnyObserver var dateStyleObserver: AnyObserver + var dateFormatObserver: AnyObserver var eventStatusItemDetectNotchObserver: AnyObserver var showStatusItemIcon: Observable var showStatusItemDate: Observable var statusItemDateStyle: Observable + var statusItemDateFormat: Observable var eventStatusItemDetectNotch: Observable init() { (showStatusItemIcon, toggleIcon) = BehaviorSubject.pipe(value: true) (showStatusItemDate, toggleDate) = BehaviorSubject.pipe(value: true) (statusItemDateStyle, dateStyleObserver) = BehaviorSubject.pipe(value: .short) + (statusItemDateFormat, dateFormatObserver) = BehaviorSubject.pipe(value: "") (eventStatusItemDetectNotch, eventStatusItemDetectNotchObserver) = BehaviorSubject.pipe(value: true) } } diff --git a/CalendrTests/SettingsViewModelTests.swift b/CalendrTests/SettingsViewModelTests.swift index 4b59e9f8..604788ed 100644 --- a/CalendrTests/SettingsViewModelTests.swift +++ b/CalendrTests/SettingsViewModelTests.swift @@ -158,7 +158,7 @@ class SettingsViewModelTests: XCTestCase { var options: [String]? - viewModel.dateFormatOptions + viewModel.dateStyleOptions .bind { options = $0 } .disposed(by: disposeBag) @@ -166,7 +166,8 @@ class SettingsViewModelTests: XCTestCase { "1/1/21", "Jan 1, 2021", "January 1, 2021", - "Friday, January 1, 2021" + "Friday, January 1, 2021", + "Custom..." ]) } @@ -176,7 +177,7 @@ class SettingsViewModelTests: XCTestCase { var options: [String]? - viewModel.dateFormatOptions + viewModel.dateStyleOptions .bind { options = $0 } .disposed(by: disposeBag) @@ -191,7 +192,8 @@ class SettingsViewModelTests: XCTestCase { func testDateStyleSelected() { - userDefaults.statusItemDateStyle = 2 + userDefaults.statusItemDateStyle = 1 + XCTAssertEqual(userDefaultsStatusItemDateStyle, 1) var statusItemDateStyle: DateStyle? @@ -202,10 +204,36 @@ class SettingsViewModelTests: XCTestCase { viewModel.statusItemDateStyleObserver.onNext(.medium) XCTAssertEqual(statusItemDateStyle, .medium) - XCTAssertEqual(userDefaultsStatusItemDateStyle, 2) } + func testCustomDateStyleSelected() throws { + + userDefaults.statusItemDateStyle = 0 + XCTAssertEqual(userDefaultsStatusItemDateStyle, 0) + + var isDateFormatInputVisible: Bool? + + viewModel.isDateFormatInputVisible + .bind { isDateFormatInputVisible = $0 } + .disposed(by: disposeBag) + + XCTAssertEqual(isDateFormatInputVisible, true) + + viewModel.statusItemDateStyleObserver.onNext(.short) + XCTAssertEqual(isDateFormatInputVisible, false) + + // this should fail, but it doesn't, so we test it ¯\_(ツ)_/¯ + viewModel.statusItemDateStyleObserver.onNext(try XCTUnwrap(.init(rawValue: 5))) + XCTAssertEqual(isDateFormatInputVisible, true) + + viewModel.statusItemDateStyleObserver.onNext(.full) + XCTAssertEqual(isDateFormatInputVisible, false) + + viewModel.statusItemDateStyleObserver.onNext(.none) + XCTAssertEqual(isDateFormatInputVisible, true) + } + func testToggleShowEventStatusItem() { userDefaults.showEventStatusItem = true diff --git a/CalendrTests/StatusItemViewModelTests.swift b/CalendrTests/StatusItemViewModelTests.swift index 5a4f1770..361234d6 100644 --- a/CalendrTests/StatusItemViewModelTests.swift +++ b/CalendrTests/StatusItemViewModelTests.swift @@ -31,7 +31,7 @@ class StatusItemViewModelTests: XCTestCase { var lastAttributed: NSAttributedString? var lastValue: String? { // remove attachments - lastAttributed?.string.replacingOccurrences(of: "[^ \\w,/-]", with: "", options: .regularExpression) + lastAttributed?.string.replacingOccurrences(of: "[^ ?\\w,/-]", with: "", options: .regularExpression) } override func setUp() { @@ -130,5 +130,14 @@ class StatusItemViewModelTests: XCTestCase { settings.dateStyleObserver.onNext(.full) XCTAssertEqual(lastValue, "Friday, January 1, 2021") + + settings.dateStyleObserver.onNext(.none) + XCTAssertEqual(lastValue, "???") + + settings.dateFormatObserver.onNext("E d MMM YY") + XCTAssertEqual(lastValue, "Fri 1 Jan 21") + + settings.dateStyleObserver.onNext(.short) + XCTAssertEqual(lastValue, "1/1/21") } } diff --git a/CalendrUITests/MainViewTests.swift b/CalendrUITests/MainViewTests.swift index 72a9b2bd..d3452650 100644 --- a/CalendrUITests/MainViewTests.swift +++ b/CalendrUITests/MainViewTests.swift @@ -42,7 +42,7 @@ class MainViewTests: UITestCase { func testEventStatusItemClicked_shouldDisplayEventDetails() { - MenuBar.event.click() + MenuBar.event.wait(.eventTimeout).click() XCTAssertTrue(EventDetails.view.didAppear) diff --git a/CalendrUITests/SettingsTests.swift b/CalendrUITests/SettingsTests.swift index 33020afd..64509a07 100644 --- a/CalendrUITests/SettingsTests.swift +++ b/CalendrUITests/SettingsTests.swift @@ -175,13 +175,13 @@ class SettingsTests: UITestCase { let checkbox = Settings.General.view.checkBoxes .element(matching: NSPredicate(format: "title = %@", "Show next event")) - XCTAssertTrue(MenuBar.event.exists) + XCTAssertTrue(MenuBar.event.waitForExistence(timeout: .eventTimeout)) checkbox.click() - XCTAssertFalse(MenuBar.event.exists) + XCTAssertFalse(MenuBar.event.waitForExistence(timeout: .eventTimeout)) checkbox.click() - XCTAssertTrue(MenuBar.event.exists) + XCTAssertTrue(MenuBar.event.waitForExistence(timeout: .eventTimeout)) } func testSettingsGeneral_changeDateFormat() { @@ -191,7 +191,8 @@ class SettingsTests: UITestCase { XCTAssert(Settings.view.didAppear) - let dropdown = Settings.General.view.popUpButtons.element + let dropdown = Settings.General.dateFormatDropdown + let input = Settings.General.dateFormatInput XCTAssertEqual(dropdown.text, "Friday, 1 January 2021") XCTAssertTrue(MenuBar.main.title.hasSuffix("Friday, 1 January 2021")) @@ -201,6 +202,20 @@ class SettingsTests: UITestCase { XCTAssertEqual(dropdown.text, "01/01/2021") XCTAssertTrue(MenuBar.main.title.hasSuffix("01/01/2021")) + + dropdown.click() + dropdown.menuItems.allElementsBoundByIndex.last?.click() + + XCTAssertEqual(dropdown.text, "Custom...") + XCTAssertTrue(MenuBar.main.title.hasSuffix("Fri 1 Jan 2020")) + + input.rightClick() + input.click() + input.typeKey(.delete, modifierFlags: []) + XCTAssertTrue(MenuBar.main.title.hasSuffix("???")) + + input.typeText("E") + XCTAssertTrue(MenuBar.main.title.hasSuffix("Fri")) } func testSettingsGeneral_toggleShowWeekNumbers() throws { diff --git a/CalendrUITests/UITestCase+Queries.swift b/CalendrUITests/UITestCase+Queries.swift index 5e7db9e9..4288f419 100644 --- a/CalendrUITests/UITestCase+Queries.swift +++ b/CalendrUITests/UITestCase+Queries.swift @@ -27,8 +27,8 @@ extension UITestCase { } enum MenuBar { - static var main: XCUIElement { app.statusItems[Accessibility.MenuBar.main].wait(0.1) } - static var event: XCUIElement { app.statusItems[Accessibility.MenuBar.event].wait(1.5) } + static var main: XCUIElement { app.statusItems[Accessibility.MenuBar.main].wait(.shortTimeout) } + static var event: XCUIElement { app.statusItems[Accessibility.MenuBar.event] } } enum Calendar { @@ -49,7 +49,7 @@ extension UITestCase { } enum EventList { - static var view: XCUIElement { Main.view.otherElements[Accessibility.EventList.view].wait(0.1) } + static var view: XCUIElement { Main.view.otherElements[Accessibility.EventList.view].wait(.shortTimeout) } static var events: [XCUIElement] { view.otherElements.matching(identifier: Accessibility.EventList.event).array } } @@ -76,6 +76,8 @@ extension UITestCase { enum General { static var view: XCUIElement { Settings.view.otherElements[Accessibility.Settings.General.view] } + static var dateFormatDropdown: XCUIElement { view.popUpButtons[Accessibility.Settings.General.dateFormatDropdown] } + static var dateFormatInput: XCUIElement { view.textFields[Accessibility.Settings.General.dateFormatInput] } } enum Calendars { @@ -91,6 +93,11 @@ extension UITestCase { // MARK: - Helpers +extension TimeInterval { + static let shortTimeout: Self = 0.1 + static let eventTimeout: Self = 1.5 +} + extension XCUIElement { var didAppear: Bool { waitForExistence(timeout: 1) && !frame.isEmpty }