From e320f15f132b69b0a2d6219e712e22996d42584d Mon Sep 17 00:00:00 2001 From: Abhishek Banthia <8280282+n0shake@users.noreply.github.com> Date: Mon, 28 Mar 2022 20:11:43 -0400 Subject: [PATCH 1/4] Tests. --- Clocker/Clocker.xcodeproj/project.pbxproj | 4 ++ .../ReviewControllerTests.swift | 55 +++++++++++++++++++ .../Rate Controller/ReviewController.swift | 11 ++-- 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 Clocker/ClockerUnitTests/ReviewControllerTests.swift diff --git a/Clocker/Clocker.xcodeproj/project.pbxproj b/Clocker/Clocker.xcodeproj/project.pbxproj index 9205298..28c44d8 100755 --- a/Clocker/Clocker.xcodeproj/project.pbxproj +++ b/Clocker/Clocker.xcodeproj/project.pbxproj @@ -125,6 +125,7 @@ 35C36FA12259ED6D002FA5C6 /* EventCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */; }; 35C36FA22259ED6D002FA5C6 /* RemindersHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */; }; 35C36FA42259EEC2002FA5C6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */; }; + 35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */; }; 35DFBCEF26A8468900D6648B /* ConfigExport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35DFBCEE26A8468900D6648B /* ConfigExport.swift */; }; 35E65125268EDD2E00E3E1E3 /* Toasty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35E65124268EDD2E00E3E1E3 /* Toasty.swift */; }; 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */; }; @@ -365,6 +366,7 @@ 35C36F9E2259ED6D002FA5C6 /* EventCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventCenter.swift; sourceTree = ""; }; 35C36F9F2259ED6D002FA5C6 /* RemindersHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemindersHandler.swift; sourceTree = ""; }; 35C36FA32259EEC2002FA5C6 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = SOURCE_ROOT; }; + 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReviewControllerTests.swift; sourceTree = ""; }; 35DFBCEE26A8468900D6648B /* ConfigExport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigExport.swift; sourceTree = ""; }; 35E65124268EDD2E00E3E1E3 /* Toasty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toasty.swift; sourceTree = ""; }; 9A0385BA269E3434003B5E72 /* StandardMenubarHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StandardMenubarHandlerTests.swift; sourceTree = ""; }; @@ -887,6 +889,7 @@ 35584D1327EF8EB5006E3EAD /* ThemerTests.swift */, 35584D1727F0B019006E3EAD /* DateFormatterManagerTests.swift */, 35584D1927F0B64E006E3EAD /* AppDelegateTests.swift */, + 35D23E3627F27E2E00C6DD55 /* ReviewControllerTests.swift */, ); path = ClockerUnitTests; sourceTree = ""; @@ -1273,6 +1276,7 @@ buildActionMask = 2147483647; files = ( 35584D1427EF8EB5006E3EAD /* ThemerTests.swift in Sources */, + 35D23E3727F27E2E00C6DD55 /* ReviewControllerTests.swift in Sources */, 35584D1A27F0B64E006E3EAD /* AppDelegateTests.swift in Sources */, 9A0385BB269E3434003B5E72 /* StandardMenubarHandlerTests.swift in Sources */, 35584D1827F0B019006E3EAD /* DateFormatterManagerTests.swift in Sources */, diff --git a/Clocker/ClockerUnitTests/ReviewControllerTests.swift b/Clocker/ClockerUnitTests/ReviewControllerTests.swift new file mode 100644 index 0000000..0054828 --- /dev/null +++ b/Clocker/ClockerUnitTests/ReviewControllerTests.swift @@ -0,0 +1,55 @@ +// Copyright © 2015 Abhishek Banthia + +import XCTest +@testable import Clocker + +class ReviewControllerTests: XCTestCase { + func testDebuggingMode() throws { + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker") else { + return + } + ReviewController.applicationDidLaunch(mockDefaults) + + // Call it again to ensure Keys.install + ReviewController.applicationDidLaunch(mockDefaults) + + ReviewController.setPreviewMode(true) + XCTAssertTrue(ReviewController.canPrompt()) + } + + func testPromptNotDisplayedInFirstWeekSinceInstall() { + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker") else { + return + } + // Set key install time + ReviewController.applicationDidLaunch(mockDefaults) + // Explicitly set preview mode to false + ReviewController.setPreviewMode(false) + + XCTAssertFalse(ReviewController.canPrompt()) + } + + func testPrompDisplayedAfterFirstWeekOfInstall() { + let dateChunk = TimeChunk(seconds: 0, + minutes: 0, + hours: 0, + days: -7, + weeks: 0, + months: 0, + years: 0) + let oldDate = Date().subtract(dateChunk) + + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker") else { + return + } + mockDefaults.set(oldDate, forKey: "install") + + // Explicitly set preview mode to false + ReviewController.setPreviewMode(false) + + XCTAssertNil(mockDefaults.object(forKey:"last-prompt")) + XCTAssertNil(mockDefaults.object(forKey:"last-version")) + XCTAssertTrue(ReviewController.canPrompt()) + } + +} diff --git a/Clocker/Panel/Rate Controller/ReviewController.swift b/Clocker/Panel/Rate Controller/ReviewController.swift index fdfa2d4..34964ea 100644 --- a/Clocker/Panel/Rate Controller/ReviewController.swift +++ b/Clocker/Panel/Rate Controller/ReviewController.swift @@ -14,15 +14,16 @@ final class ReviewController { static let install = "install" } - class func applicationDidLaunch(_ defaults: UserDefaults = UserDefaults.standard) { + class func applicationDidLaunch(_ defaults: UserDefaults) { if ProcessInfo.processInfo.arguments.contains(CLUITestingLaunchArgument) { debugging = true } storage = defaults - guard defaults.object(forKey: Keys.install) == nil else { return } - defaults.set(Date(), forKey: Keys.install) + if defaults.object(forKey: Keys.install) == nil { + defaults.set(Date(), forKey: Keys.install) + } } class func setPreviewMode(_ value: Bool) { @@ -35,7 +36,9 @@ final class ReviewController { } class func canPrompt() -> Bool { - guard debugging == false else { return true } + if debugging == true { + return true + } let day: TimeInterval = -1 * 60 * 60 * 24 let minInstall: TimeInterval = day * 7 From 5c64797cfc1cd0c02621dc037f4a82ed4dd724a0 Mon Sep 17 00:00:00 2001 From: Abhishek Banthia <8280282+n0shake@users.noreply.github.com> Date: Mon, 28 Mar 2022 20:21:36 -0400 Subject: [PATCH 2/4] Update ReviewControllerTests.swift --- .../ReviewControllerTests.swift | 52 +++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) diff --git a/Clocker/ClockerUnitTests/ReviewControllerTests.swift b/Clocker/ClockerUnitTests/ReviewControllerTests.swift index 0054828..fc1755e 100644 --- a/Clocker/ClockerUnitTests/ReviewControllerTests.swift +++ b/Clocker/ClockerUnitTests/ReviewControllerTests.swift @@ -18,7 +18,7 @@ class ReviewControllerTests: XCTestCase { } func testPromptNotDisplayedInFirstWeekSinceInstall() { - guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker") else { + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker1") else { return } // Set key install time @@ -33,16 +33,17 @@ class ReviewControllerTests: XCTestCase { let dateChunk = TimeChunk(seconds: 0, minutes: 0, hours: 0, - days: -7, + days: 8, weeks: 0, months: 0, years: 0) let oldDate = Date().subtract(dateChunk) - guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker") else { + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker2") else { return } mockDefaults.set(oldDate, forKey: "install") + ReviewController.applicationDidLaunch(mockDefaults) // Explicitly set preview mode to false ReviewController.setPreviewMode(false) @@ -51,5 +52,50 @@ class ReviewControllerTests: XCTestCase { XCTAssertNil(mockDefaults.object(forKey:"last-version")) XCTAssertTrue(ReviewController.canPrompt()) } + + func testPrompDisplayedAfterThreeMonths() { + let dateChunk = TimeChunk(seconds: 0, + minutes: 0, + hours: 0, + days: 98, + weeks: 0, + months: 0, + years: 0) + let minInstall = Date().subtract(dateChunk) + + let promptChunk = TimeChunk(seconds: 0, + minutes: 0, + hours: 0, + days: 91, + weeks: 0, + months: 0, + years: 0) + let lastPromptDate = Date().subtract(promptChunk) + + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker3") else { + return + } + mockDefaults.set(minInstall, forKey: "install") + mockDefaults.set("test-version", forKey: "last-version") + mockDefaults.set(lastPromptDate, forKey: "last-prompt") + ReviewController.applicationDidLaunch(mockDefaults) + + // Explicitly set preview mode to false + ReviewController.setPreviewMode(false) + + XCTAssertNotNil(mockDefaults.object(forKey:"last-prompt")) + XCTAssertNotNil(mockDefaults.object(forKey:"last-version")) + XCTAssertTrue(ReviewController.canPrompt()) + } + + func testPrompted() { + guard let mockDefaults = UserDefaults(suiteName: "com.test.Clocker4") else { + return + } + ReviewController.applicationDidLaunch(mockDefaults) + ReviewController.prompt() + XCTAssertNotNil(mockDefaults.object(forKey:"last-prompt")) + XCTAssertNotNil(mockDefaults.object(forKey:"last-version")) + } } From 958562ee4cbdd091950369345fa979445f9b15c1 Mon Sep 17 00:00:00 2001 From: Abhishek Banthia <8280282+n0shake@users.noreply.github.com> Date: Mon, 28 Mar 2022 21:20:06 -0400 Subject: [PATCH 3/4] Fix theming tests. --- Clocker/ClockerUnitTests/ThemerTests.swift | 12 ++++++------ Clocker/Overall App/Themer.swift | 14 +++++++++----- .../Panel/Rate Controller/ReviewController.swift | 5 ++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/Clocker/ClockerUnitTests/ThemerTests.swift b/Clocker/ClockerUnitTests/ThemerTests.swift index cf23798..28c786f 100644 --- a/Clocker/ClockerUnitTests/ThemerTests.swift +++ b/Clocker/ClockerUnitTests/ThemerTests.swift @@ -101,7 +101,7 @@ class ThemerTests: XCTestCase { let expectedSunsetImageName = "sunset.fill" let expectedRemoveImageName = "xmark" let expectedExtraOptionsImage = "ExtraWhite" - let expectedMenubarOnboardingImage = "Light Menubar" + let expectedMenubarOnboardingImage = "Dark Menubar" let expectedExtraOptionsHighlightedImage = "ExtraWhiteHighlighted" let expectedSharingImage = "square.and.arrow.up.on.square.fill" let expectedCurrentLocationImage = "location.fill" @@ -153,13 +153,13 @@ class ThemerTests: XCTestCase { func testSystemTheme() throws { let currentSystemTheme = - UserDefaults.standard.string(forKey: "AppleUserInterfaceStyle")?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light + UserDefaults.standard.string(forKey: "AppleInterfaceStyle")?.lowercased().contains("dark") ?? false ? Themer.Theme.dark : Themer.Theme.light let subject = Themer(index: 2) // 2 is for system theme let expectedSliderKnobColor = currentSystemTheme == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) let expectedSliderRightColor = currentSystemTheme == .dark ? NSColor.white : NSColor.gray - let expectedBackgroundColor = currentSystemTheme == .dark ? NSColor(deviceRed: 42.0 / 255.0, green: 42.0 / 255.0, blue: 42.0 / 255.0, alpha: 1.0) : NSColor.white + let expectedBackgroundColor = currentSystemTheme == .dark ? NSColor.windowBackgroundColor : NSColor.white let expectedTextColor = NSColor.textColor - let expectedTextBackgroundColor = currentSystemTheme == .dark ? NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) : NSColor(deviceRed: 241.0 / 255.0, green: 241.0 / 255.0, blue: 241.0 / 255.0, alpha: 1.0) + let expectedTextBackgroundColor = currentSystemTheme == .light ? NSColor(deviceRed: 42.0 / 255.0, green: 55.0 / 255.0, blue: 62.0 / 255.0, alpha: 1.0) : NSColor.controlBackgroundColor let expectedShutdownImageName = "ellipsis.circle" let expectedPreferenceImageName = "plus" @@ -220,7 +220,7 @@ class ThemerTests: XCTestCase { func testSolarizedLightTheme() throws { let subject = Themer(index: 3) // 3 is for solarized light theme - let expectedSliderKnobColor = NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) + let expectedSliderKnobColor = NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) let expectedSliderRightColor = NSColor.gray let expectedBackgroundColor = NSColor(deviceRed: 253.0 / 255.0, green: 246.0 / 255.0, blue: 227.0 / 255.0, alpha: 1.0) let expectedTextColor = NSColor.black @@ -299,7 +299,7 @@ class ThemerTests: XCTestCase { let expectedSunsetImageName = "sunset.fill" let expectedRemoveImageName = "xmark" let expectedExtraOptionsImage = "ExtraWhite" - let expectedMenubarOnboardingImage = "Light Menubar" + let expectedMenubarOnboardingImage = "Dark Menubar" let expectedExtraOptionsHighlightedImage = "ExtraWhiteHighlighted" let expectedSharingImage = "square.and.arrow.up.on.square.fill" let expectedCurrentLocationImage = "location.fill" diff --git a/Clocker/Overall App/Themer.swift b/Clocker/Overall App/Themer.swift index a0f90dc..0613deb 100644 --- a/Clocker/Overall App/Themer.swift +++ b/Clocker/Overall App/Themer.swift @@ -95,12 +95,12 @@ extension Themer { func sliderKnobColor() -> NSColor { switch themeIndex { - case .light: + case .light, .solarizedLight: return NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) + case .dark, .solarizedDark: + return NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) case .system: return retrieveCurrentSystem() == .light ? NSColor(deviceRed: 255.0, green: 255.0, blue: 255, alpha: 0.9) : NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) - default: - return NSColor(deviceRed: 0.0, green: 0.0, blue: 0, alpha: 0.9) } } @@ -108,6 +108,8 @@ extension Themer { switch themeIndex { case .dark: return NSColor.white + case .system: + return retrieveCurrentSystem() == .dark ? NSColor.white : NSColor.gray default: return NSColor.gray } @@ -258,8 +260,10 @@ extension Themer { switch themeIndex { case .system: return NSImage(named: NSImage.Name("Dynamic Menubar"))! - default: - return retrieveCurrentSystem() == .dark ? NSImage(named: NSImage.Name("Dark Menubar"))! : NSImage(named: NSImage.Name("Light Menubar"))! + case .light, .solarizedLight: + return NSImage(named: NSImage.Name("Light Menubar"))! + case .dark, .solarizedDark: + return NSImage(named: NSImage.Name("Dark Menubar"))! } } diff --git a/Clocker/Panel/Rate Controller/ReviewController.swift b/Clocker/Panel/Rate Controller/ReviewController.swift index 34964ea..063c20f 100644 --- a/Clocker/Panel/Rate Controller/ReviewController.swift +++ b/Clocker/Panel/Rate Controller/ReviewController.swift @@ -5,7 +5,6 @@ import StoreKit final class ReviewController { private static var storage = UserDefaults.standard - private static let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "N/A" private static var debugging = false private enum Keys { @@ -32,7 +31,7 @@ final class ReviewController { class func prompted() { storage.set(Date(), forKey: Keys.lastPrompt) - storage.set(version, forKey: Keys.lastVersion) + storage.set(Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String, forKey: Keys.lastVersion) } class func canPrompt() -> Bool { @@ -57,7 +56,7 @@ final class ReviewController { let minInterval: TimeInterval = day * 90 // never prompt w/in the same version - return lastVersion != version + return lastVersion != (Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String) // limit all types of prompts to at least 1mo intervals && lastPrompt.timeIntervalSinceNow < minInterval } From 95a36966d68cc8724dfbb9e00de4b6e082071b49 Mon Sep 17 00:00:00 2001 From: Abhishek Banthia <8280282+n0shake@users.noreply.github.com> Date: Mon, 28 Mar 2022 21:33:46 -0400 Subject: [PATCH 4/4] Update StatusItemHandler.swift --- Clocker/Preferences/Menu Bar/StatusItemHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Clocker/Preferences/Menu Bar/StatusItemHandler.swift b/Clocker/Preferences/Menu Bar/StatusItemHandler.swift index 91f1213..7215ac4 100644 --- a/Clocker/Preferences/Menu Bar/StatusItemHandler.swift +++ b/Clocker/Preferences/Menu Bar/StatusItemHandler.swift @@ -207,7 +207,7 @@ class StatusItemHandler: NSObject { return false } - return timezonesSupportingSeconds.isEmpty + return timezonesSupportingSeconds.isEmpty == false } private func calculateFireDate() -> Date? {