273 lines
13 KiB
Swift
273 lines
13 KiB
Swift
import ConjureKit
|
|
import Foundation
|
|
import Testing
|
|
|
|
@Test @MainActor
|
|
func suggestionsPreferFrequencyThenRecencyAndKeepUnusedAppsAlphabetical() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
var instant = Date(timeIntervalSinceReferenceDate: 1_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let apps = ["Unused Zebra", "Recent", "Frequent", "Unused Alpha", "Older"].map(historyApplication)
|
|
#expect(history.suggestions(in: apps).map(\.name) == [
|
|
"Frequent", "Older", "Recent", "Unused Alpha", "Unused Zebra",
|
|
])
|
|
|
|
history.recordLaunch(of: apps[4])
|
|
instant += 10
|
|
history.recordLaunch(of: apps[2])
|
|
history.recordLaunch(of: apps[2])
|
|
instant += 10
|
|
history.recordLaunch(of: apps[1])
|
|
#expect(history.suggestions(in: apps).map(\.name) == [
|
|
"Frequent", "Recent", "Older", "Unused Alpha", "Unused Zebra",
|
|
])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func suggestionsRetainUsageAcrossHistoryInstancesAndDoNotReintroduceMissingApps() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 2_000)
|
|
let alpha = historyApplication("Alpha")
|
|
let zebra = historyApplication("Zebra")
|
|
let removed = historyApplication("Removed")
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
history.recordLaunch(of: zebra)
|
|
history.recordLaunch(of: removed)
|
|
history.recordLaunch(of: removed)
|
|
|
|
let reloaded = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(reloaded.suggestions(in: [alpha, zebra]).map(\.name) == ["Zebra", "Alpha"])
|
|
reloaded.recordLaunch(of: alpha)
|
|
#expect(reloaded.suggestions(in: [zebra, alpha]).map(\.name) == ["Alpha", "Zebra"])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func usageTracksApplicationIdentityRatherThanDisplayNameAndKeepsTiesDeterministic() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 3_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let first = Application(url: URL(fileURLWithPath: "/First/Editor.app"), name: "Editor")
|
|
let second = Application(url: URL(fileURLWithPath: "/Second/Editor.app"), name: "Editor")
|
|
#expect(history.suggestions(in: [second, first]).map(\.url) == [first.url, second.url])
|
|
history.recordLaunch(of: second)
|
|
#expect(history.suggestions(in: [first, second]).map(\.url) == [second.url, first.url])
|
|
|
|
let renamed = Application(url: second.url, name: "Renamed Editor")
|
|
#expect(history.suggestions(in: [first, renamed]).map(\.url) == [second.url, first.url])
|
|
history.recordLaunch(of: first)
|
|
#expect(history.suggestions(in: [second, first]).map(\.url) == [first.url, second.url])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func applicationIdentityIgnoresDirectoryURLFormattingInLaunchHistory() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 4_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let path = "/Applications/HistoryTests/Editor.app"
|
|
let plain = Application(url: URL(fileURLWithPath: path, isDirectory: false), name: "Editor")
|
|
let directory = Application(url: URL(fileURLWithPath: path, isDirectory: true), name: "Editor")
|
|
#expect(plain.id == directory.id)
|
|
history.recordLaunch(of: directory)
|
|
#expect(history.suggestions(in: [historyApplication("Alpha"), plain]).first?.id == plain.id)
|
|
history.toggleFavorite(directory)
|
|
#expect(history.isFavorite(plain))
|
|
let reloaded = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(reloaded.isFavorite(plain))
|
|
reloaded.toggleFavorite(plain)
|
|
#expect(!reloaded.isFavorite(directory))
|
|
}
|
|
|
|
@Test @MainActor
|
|
func typedSearchUsesHistoryOnlyForEqualTextRanks() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
var instant = Date(timeIntervalSinceReferenceDate: 5_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let apps = ["Code", "Code Alpha", "Code Recent", "Code Frequent", "Open Code", "Unrelated"].map(historyApplication)
|
|
history.recordLaunch(of: apps[3])
|
|
history.recordLaunch(of: apps[3])
|
|
instant += 10
|
|
history.recordLaunch(of: apps[2])
|
|
for _ in 0..<3 { history.recordLaunch(of: apps[4]) }
|
|
history.recordLaunch(of: apps[5])
|
|
#expect(history.results(for: "code", in: apps).map(\.name) == [
|
|
"Code", "Code Frequent", "Code Recent", "Code Alpha", "Open Code",
|
|
])
|
|
instant += 10
|
|
history.recordLaunch(of: apps[1])
|
|
#expect(history.results(for: "code", in: apps).map(\.name) == [
|
|
"Code", "Code Frequent", "Code Alpha", "Code Recent", "Open Code",
|
|
])
|
|
let fuzzy = ["AyB", "AzB", "xAyB", "AxxxxB"].map(historyApplication)
|
|
history.recordLaunch(of: fuzzy[1])
|
|
for _ in 0..<3 { history.recordLaunch(of: fuzzy[3]) }
|
|
#expect(history.results(for: "ab", in: fuzzy).map(\.name) == [
|
|
"AzB", "AyB", "xAyB", "AxxxxB",
|
|
])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func resetRankingClearsOnlySelectedIdentityAndPersistsAcrossRelaunch() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 6_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let alpha = historyApplication("Editor Alpha")
|
|
let first = Application(url: URL(fileURLWithPath: "/First/Editor.app"), name: "Editor")
|
|
let second = Application(url: URL(fileURLWithPath: "/Second/Editor.app"), name: "Editor")
|
|
let apps = [alpha, first, second]
|
|
history.recordLaunch(of: second)
|
|
history.recordLaunch(of: first)
|
|
history.recordLaunch(of: first)
|
|
#expect(history.suggestions(in: apps).map(\.id) == [first.id, second.id, alpha.id])
|
|
|
|
history.resetRanking(of: first)
|
|
history.resetRanking(of: first)
|
|
#expect(history.suggestions(in: apps).map(\.id) == [second.id, first.id, alpha.id])
|
|
#expect(history.results(for: "editor", in: apps).map(\.id) == [second.id, first.id, alpha.id])
|
|
let reloaded = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(reloaded.suggestions(in: apps).map(\.id) == [second.id, first.id, alpha.id])
|
|
reloaded.recordLaunch(of: first)
|
|
#expect(reloaded.suggestions(in: apps).map(\.id) == [first.id, second.id, alpha.id])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func emptySearchPlacesFavoritesFirstAndPreservesUsageOrderingWithinEachGroup() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
var instant = Date(timeIntervalSinceReferenceDate: 7_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let favorites = ["Favorite Frequent", "Favorite Recent", "Favorite Older", "Favorite Unused Alpha", "Favorite Unused Zebra"]
|
|
.map(historyApplication)
|
|
let others = ["Other Frequent", "Other Recent", "Other Older", "Other Unused Alpha", "Other Unused Zebra"]
|
|
.map(historyApplication)
|
|
for application in favorites { history.toggleFavorite(application) }
|
|
for group in [favorites, others] {
|
|
history.recordLaunch(of: group[0])
|
|
history.recordLaunch(of: group[0])
|
|
history.recordLaunch(of: group[2])
|
|
instant += 10
|
|
history.recordLaunch(of: group[1])
|
|
}
|
|
for _ in 0..<8 { history.recordLaunch(of: others[0]) }
|
|
let expected = (favorites + others).map(\.id)
|
|
let apps = Array((others + favorites).reversed())
|
|
#expect(history.suggestions(in: apps).map(\.id) == expected)
|
|
#expect(history.results(for: "", in: apps).map(\.id) == expected)
|
|
#expect(history.results(for: " \n", in: apps).map(\.id) == expected)
|
|
}
|
|
|
|
@Test @MainActor
|
|
func typedSearchPrefersTextRankBeforeFavoritesAndFavoritesBeforeUsage() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 8_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let apps = ["AB", "AB Alpha", "AB Heavy", "AyB", "AzB", "AxxxxB", "Unrelated"].map(historyApplication)
|
|
for index in [1, 3, 5, 6] { history.toggleFavorite(apps[index]) }
|
|
for _ in 0..<5 {
|
|
history.recordLaunch(of: apps[2])
|
|
history.recordLaunch(of: apps[4])
|
|
}
|
|
#expect(history.results(for: "ab", in: Array(apps.reversed())).map(\.id) == Array(apps.dropLast()).map(\.id))
|
|
}
|
|
|
|
@Test @MainActor
|
|
func favoritesPersistWithoutUsageAndToggleRemovalPersistsAcrossRelaunch() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 9_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let alpha = historyApplication("Alpha")
|
|
let zebra = historyApplication("Zebra")
|
|
let removed = historyApplication("Removed")
|
|
#expect(!history.isFavorite(zebra))
|
|
history.toggleFavorite(zebra)
|
|
history.toggleFavorite(removed)
|
|
#expect(history.isFavorite(zebra))
|
|
|
|
let reloaded = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(reloaded.isFavorite(zebra))
|
|
#expect(reloaded.isFavorite(removed))
|
|
#expect(reloaded.suggestions(in: [alpha, zebra]).map(\.id) == [zebra.id, alpha.id])
|
|
reloaded.toggleFavorite(zebra)
|
|
#expect(!reloaded.isFavorite(zebra))
|
|
let afterRemoval = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(!afterRemoval.isFavorite(zebra))
|
|
#expect(afterRemoval.isFavorite(removed))
|
|
#expect(afterRemoval.suggestions(in: [zebra, alpha]).map(\.id) == [alpha.id, zebra.id])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func resettingUsageRetainsFavoritesAndTheirPersistence() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 10_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let alpha = historyApplication("Alpha")
|
|
let zebra = historyApplication("Zebra")
|
|
let other = historyApplication("Other")
|
|
let apps = [other, zebra, alpha]
|
|
history.toggleFavorite(alpha)
|
|
history.toggleFavorite(zebra)
|
|
history.recordLaunch(of: zebra)
|
|
history.recordLaunch(of: zebra)
|
|
for _ in 0..<5 { history.recordLaunch(of: other) }
|
|
#expect(history.suggestions(in: apps).map(\.id) == [zebra.id, alpha.id, other.id])
|
|
history.resetRanking(of: zebra)
|
|
history.resetRanking(of: zebra)
|
|
#expect(history.isFavorite(zebra))
|
|
#expect(history.suggestions(in: apps).map(\.id) == [alpha.id, zebra.id, other.id])
|
|
|
|
let reloaded = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(reloaded.isFavorite(alpha))
|
|
#expect(reloaded.isFavorite(zebra))
|
|
#expect(reloaded.suggestions(in: apps).map(\.id) == [alpha.id, zebra.id, other.id])
|
|
reloaded.recordLaunch(of: zebra)
|
|
#expect(reloaded.isFavorite(zebra))
|
|
#expect(reloaded.suggestions(in: apps).map(\.id) == [zebra.id, alpha.id, other.id])
|
|
}
|
|
|
|
@Test @MainActor
|
|
func favoritesTrackApplicationURLsRatherThanDisplayNames() {
|
|
let suite = "ConjureHistoryTests.\(UUID().uuidString)"
|
|
let defaults = UserDefaults(suiteName: suite)!
|
|
defer { defaults.removePersistentDomain(forName: suite) }
|
|
let instant = Date(timeIntervalSinceReferenceDate: 11_000)
|
|
let history = LaunchHistory(defaults: defaults, now: { instant })
|
|
let first = Application(url: URL(fileURLWithPath: "/First/Editor.app"), name: "Editor")
|
|
let second = Application(url: URL(fileURLWithPath: "/Second/Editor.app"), name: "Editor")
|
|
for _ in 0..<5 { history.recordLaunch(of: first) }
|
|
history.toggleFavorite(second)
|
|
#expect(!history.isFavorite(first))
|
|
#expect(history.isFavorite(second))
|
|
#expect(history.results(for: "editor", in: [first, second]).map(\.id) == [second.id, first.id])
|
|
|
|
let renamed = Application(url: second.url, name: "Renamed Editor")
|
|
let reloaded = LaunchHistory(defaults: UserDefaults(suiteName: suite)!, now: { instant })
|
|
#expect(!reloaded.isFavorite(first))
|
|
#expect(reloaded.isFavorite(renamed))
|
|
#expect(reloaded.suggestions(in: [first, renamed]).map(\.id) == [second.id, first.id])
|
|
reloaded.toggleFavorite(renamed)
|
|
#expect(!reloaded.isFavorite(second))
|
|
#expect(reloaded.suggestions(in: [second, first]).map(\.id) == [first.id, second.id])
|
|
}
|
|
|
|
private func historyApplication(_ name: String) -> Application {
|
|
Application(url: URL(fileURLWithPath: "/Applications/HistoryTests/\(name).app"), name: name)
|
|
}
|