Defaults/Tests/DefaultsTests/DefaultsTests.swift

625 lines
17 KiB
Swift

// swiftlint:disable discouraged_optional_boolean
import Foundation
import Combine
import Testing
@testable import Defaults
func createSuite() -> UserDefaults {
UserDefaults(suiteName: UUID().uuidString)!
}
private let suite_ = createSuite()
let fixtureURL = URL(string: "https://sindresorhus.com")!
let fixtureFileURL = URL(string: "file://~/icon.png")!
let fixtureURL2 = URL(string: "https://example.com")!
let fixtureDate = Date()
extension Defaults.Keys {
static let key = Key<Bool>("key", default: false, suite: suite_)
static let url = Key<URL>("url", default: fixtureURL, suite: suite_)
static let file = Key<URL>("fileURL", default: fixtureFileURL, suite: suite_)
static let data = Key<Data>("data", default: Data([]), suite: suite_)
static let date = Key<Date>("date", default: fixtureDate, suite: suite_)
static let uuid = Key<UUID?>("uuid", suite: suite_)
static let defaultDynamicDate = Key<Date>("defaultDynamicOptionalDate", suite: suite_) { Date(timeIntervalSince1970: 0) }
static let defaultDynamicOptionalDate = Key<Date?>("defaultDynamicOptionalDate", suite: suite_) { Date(timeIntervalSince1970: 1) }
}
@Suite(.serialized)
final class DefaultsTests {
init() {
Defaults.removeAll(suite: suite_)
}
deinit {
Defaults.removeAll(suite: suite_)
}
@Test
func testKey() {
let key = Defaults.Key<Bool>("independentKey", default: false, suite: suite_)
#expect(!Defaults[key])
Defaults[key] = true
#expect(Defaults[key])
}
@Test
func testValidKeyName() {
let validKey = Defaults.Key<Bool>("test", default: false, suite: suite_)
let containsDotKey = Defaults.Key<Bool>("test.a", default: false, suite: suite_)
let startsWithAtKey = Defaults.Key<Bool>("@test", default: false, suite: suite_)
#expect(Defaults.isValidKeyPath(name: validKey.name))
#expect(!Defaults.isValidKeyPath(name: containsDotKey.name))
#expect(!Defaults.isValidKeyPath(name: startsWithAtKey.name))
}
@Test
func testOptionalKey() {
let key = Defaults.Key<Bool?>("independentOptionalKey", suite: suite_)
let url = Defaults.Key<URL?>("independentOptionalURLKey", suite: suite_)
#expect(Defaults[key] == nil)
#expect(Defaults[url] == nil)
Defaults[key] = true
Defaults[url] = fixtureURL
#expect(Defaults[key] == true)
#expect(Defaults[url] == fixtureURL)
Defaults[key] = nil
Defaults[url] = nil
#expect(Defaults[key] == nil)
#expect(Defaults[url] == nil)
Defaults[key] = false
Defaults[url] = fixtureURL2
#expect(Defaults[key] == false)
#expect(Defaults[url] == fixtureURL2)
}
@Test
func testInitializeDynamicDateKey() {
_ = Defaults.Key<Date>("independentInitializeDynamicDateKey", suite: suite_) {
Issue.record("Init dynamic key should not trigger getter")
return Date()
}
_ = Defaults.Key<Date?>("independentInitializeDynamicOptionalDateKey", suite: suite_) {
Issue.record("Init dynamic optional key should not trigger getter")
return Date()
}
}
@Test
func testKeyRegistersDefault() {
let keyName = "registersDefault"
#expect(!suite_.bool(forKey: keyName))
_ = Defaults.Key<Bool>(keyName, default: true, suite: suite_)
#expect(suite_.bool(forKey: keyName))
let keyName2 = "registersDefault2"
_ = Defaults.Key<String>(keyName2, default: keyName2, suite: suite_)
#expect(suite_.string(forKey: keyName2) == keyName2)
}
@Test
func testKeyWithUserDefaultSubscript() {
let key = Defaults.Key<Bool>("keyWithUserDeaultSubscript", default: false, suite: suite_)
#expect(!suite_[key])
suite_[key] = true
#expect(suite_[key])
}
@Test
func testKeys() {
#expect(!Defaults[.key])
Defaults[.key] = true
#expect(Defaults[.key])
}
@Test
func testUrlType() {
#expect(Defaults[.url] == fixtureURL)
let newUrl = URL(string: "https://twitter.com")!
Defaults[.url] = newUrl
#expect(Defaults[.url] == newUrl)
}
@Test
func testDataType() {
#expect(Defaults[.data] == Data([]))
let newData = Data([0xFF])
Defaults[.data] = newData
#expect(Defaults[.data] == newData)
}
@Test
func testDateType() {
#expect(Defaults[.date] == fixtureDate)
let newDate = Date()
Defaults[.date] = newDate
#expect(Defaults[.date] == newDate)
}
@Test
func testDynamicDateType() {
#expect(Defaults[.defaultDynamicDate] == Date(timeIntervalSince1970: 0))
let next = Date(timeIntervalSince1970: 1)
Defaults[.defaultDynamicDate] = next
#expect(Defaults[.defaultDynamicDate] == next)
#expect(suite_.object(forKey: Defaults.Key<Date>.defaultDynamicDate.name) as! Date == next)
Defaults.Key<Date>.defaultDynamicDate.reset()
#expect(Defaults[.defaultDynamicDate] == Date(timeIntervalSince1970: 0))
}
@Test
func testDynamicOptionalDateType() {
#expect(Defaults[.defaultDynamicOptionalDate] == Date(timeIntervalSince1970: 1))
let next = Date(timeIntervalSince1970: 2)
Defaults[.defaultDynamicOptionalDate] = next
#expect(Defaults[.defaultDynamicOptionalDate] == next)
#expect(suite_.object(forKey: Defaults.Key<Date>.defaultDynamicOptionalDate.name) as! Date == next)
Defaults[.defaultDynamicOptionalDate] = nil
#expect(Defaults[.defaultDynamicOptionalDate] == Date(timeIntervalSince1970: 1))
#expect(suite_.object(forKey: Defaults.Key<Date>.defaultDynamicOptionalDate.name) == nil)
}
@Test
func testFileURLType() {
#expect(Defaults[.file] == fixtureFileURL)
}
@Test
func testUUIDType() {
let fixture = UUID()
Defaults[.uuid] = fixture
#expect(Defaults[.uuid] == fixture)
}
@Test
func testRemoveAll() {
let key = Defaults.Key<Bool>("removeAll", default: false, suite: suite_)
let key2 = Defaults.Key<Bool>("removeAll2", default: false, suite: suite_)
Defaults[key] = true
Defaults[key2] = true
#expect(Defaults[key])
#expect(Defaults[key2])
Defaults.removeAll(suite: suite_)
#expect(!Defaults[key])
#expect(!Defaults[key2])
}
@Test
func testCustomSuite() {
let customSuite = UserDefaults(suiteName: "com.sindresorhus.customSuite")!
let key = Defaults.Key<Bool>("customSuite", default: false, suite: customSuite)
#expect(!customSuite[key])
#expect(!Defaults[key])
Defaults[key] = true
#expect(customSuite[key])
#expect(Defaults[key])
Defaults.removeAll(suite: customSuite)
}
@Test
func testIsDefaultValue() {
let key = Defaults.Key<Bool>("isDefaultValue", default: false, suite: suite_)
#expect(key.isDefaultValue)
Defaults[key].toggle()
#expect(!key.isDefaultValue)
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testObserveKeyCombine() async throws {
let key = Defaults.Key<Bool>("observeKey", default: false, suite: suite_)
var results: [(Bool, Bool)] = []
try await confirmation(expectedCount: 2) { confirmation in
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.sink { value in
results.append(value)
confirmation()
}
await Task.yield()
Defaults[key] = true
Defaults.reset(key)
try await Task.sleep(for: .milliseconds(100))
publisher.cancel()
}
let expectedValues = [(false, true), (true, false)]
// #expect(results == expectedValues)
#expect(results.count == expectedValues.count)
for (index, expected) in expectedValues.enumerated() {
#expect(results[index].0 == expected.0)
#expect(results[index].1 == expected.1)
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testObserveOptionalKeyCombine() async throws {
let key = Defaults.Key<Bool?>("observeOptionalKey", suite: suite_)
var results: [(Bool?, Bool?)] = []
try await confirmation(expectedCount: 3) { confirmation in
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.sink { value in
results.append(value)
confirmation()
}
await Task.yield()
Defaults[key] = true
Defaults[key] = false
Defaults.reset(key)
try await Task.sleep(for: .milliseconds(100))
publisher.cancel()
}
let expectedValues: [(Bool?, Bool?)] = [(nil, true), (true, false), (false, nil)]
// #expect(results == expectedValues)
#expect(results.count == expectedValues.count)
for (index, expected) in expectedValues.enumerated() {
#expect(results[index].0 == expected.0)
#expect(results[index].1 == expected.1)
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testDynamicOptionalDateTypeCombine() async throws {
let first = Date(timeIntervalSince1970: 0)
let second = Date(timeIntervalSince1970: 1)
let third = Date(timeIntervalSince1970: 2)
let key = Defaults.Key<Date?>("combineDynamicOptionalDateKey", suite: suite_) { first }
var results: [(Date?, Date?)] = []
try await confirmation(expectedCount: 3) { confirmation in
let publisher = Defaults
.publisher(key, options: [])
.map { ($0.oldValue, $0.newValue) }
.sink { value in
results.append(value)
confirmation()
}
await Task.yield()
Defaults[key] = second
Defaults[key] = third
Defaults.reset(key)
try await Task.sleep(for: .milliseconds(100))
publisher.cancel()
}
let expectedValues: [(Date?, Date?)] = [(first, second), (second, third), (third, first)]
// #expect(results == expectedValues)
#expect(results.count == expectedValues.count)
for (index, expected) in expectedValues.enumerated() {
#expect(results[index].0 == expected.0)
#expect(results[index].1 == expected.1)
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testObserveMultipleKeysCombine() async throws {
let key1 = Defaults.Key<String>("observeKey1", default: "x", suite: suite_)
let key2 = Defaults.Key<Bool>("observeKey2", default: true, suite: suite_)
var count = 0
try await confirmation(expectedCount: 2) { confirmation in
let publisher = Defaults.publisher(keys: key1, key2, options: [])
.sink { _ in
count += 1
confirmation()
}
await Task.yield()
Defaults[key1] = "y"
Defaults[key2] = false
try await Task.sleep(for: .milliseconds(100))
publisher.cancel()
}
#expect(count == 2)
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testObserveMultipleOptionalKeysCombine() async throws {
let key1 = Defaults.Key<String?>("observeOptionalKey1", suite: suite_)
let key2 = Defaults.Key<Bool?>("observeOptionalKey2", suite: suite_)
var count = 0
try await confirmation(expectedCount: 2) { confirmation in
let publisher = Defaults.publisher(keys: key1, key2, options: [])
.sink { _ in
count += 1
confirmation()
}
await Task.yield()
Defaults[key1] = "x"
Defaults[key2] = false
try await Task.sleep(for: .milliseconds(100))
publisher.cancel()
}
#expect(count == 2)
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testReceiveValueBeforeSubscriptionCombine() async throws {
let key = Defaults.Key<String>("receiveValueBeforeSubscription", default: "hello", suite: suite_)
try await confirmation(expectedCount: 2) { confirmation in
let publisher = Defaults
.publisher(key)
.map(\.newValue)
.sink { value in
confirmation()
print("Received value: \(value)")
}
// Ensure we're subscribed before changing the value
await Task.yield()
// Change the value
Defaults[key] = "world"
// Keep the subscription alive
try await Task.sleep(for: .milliseconds(100))
publisher.cancel()
}
}
@Test
func testObservePreventPropagationCombine() async throws {
let key1 = Defaults.Key<Bool?>("preventPropagation6", default: nil, suite: suite_)
await confirmation { confirmation in
var wasInside = false
let cancellable = Defaults.publisher(key1, options: []).sink { _ in
#expect(!wasInside)
wasInside = true
Defaults.withoutPropagation {
Defaults[key1] = true
}
confirmation()
}
Defaults[key1] = false
cancellable.cancel()
}
}
@Test
func testObservePreventPropagationMultipleKeysCombine() async throws {
let key1 = Defaults.Key<Bool?>("preventPropagation7", default: nil, suite: suite_)
let key2 = Defaults.Key<Bool?>("preventPropagation8", default: nil, suite: suite_)
await confirmation { confirmation in
var wasInside = false
let cancellable = Defaults.publisher(keys: key1, key2, options: []).sink { _ in
#expect(!wasInside)
wasInside = true
Defaults.withoutPropagation {
Defaults[key1] = true
}
confirmation()
}
Defaults[key2] = false
cancellable.cancel()
}
}
@Test
func testResetKey() {
let defaultFixture1 = "foo1"
let defaultFixture2 = 0
let newFixture1 = "bar1"
let newFixture2 = 1
let key1 = Defaults.Key<String>("key1", default: defaultFixture1, suite: suite_)
let key2 = Defaults.Key<Int>("key2", default: defaultFixture2, suite: suite_)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults.reset(key1)
#expect(Defaults[key1] == defaultFixture1)
#expect(Defaults[key2] == newFixture2)
}
@Test
func testResetMultipleKeys() {
let defaultFxiture1 = "foo1"
let defaultFixture2 = 0
let defaultFixture3 = "foo3"
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String>("akey1", default: defaultFxiture1, suite: suite_)
let key2 = Defaults.Key<Int>("akey2", default: defaultFixture2, suite: suite_)
let key3 = Defaults.Key<String>("akey3", default: defaultFixture3, suite: suite_)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults[key3] = newFixture3
Defaults.reset(key1, key2)
#expect(Defaults[key1] == defaultFxiture1)
#expect(Defaults[key2] == defaultFixture2)
#expect(Defaults[key3] == newFixture3)
}
@Test
func testResetMultipleOptionalKeys() {
let newFixture1 = "bar1"
let newFixture2 = 1
let newFixture3 = "bar3"
let key1 = Defaults.Key<String?>("aoptionalKey1", suite: suite_)
let key2 = Defaults.Key<Int?>("aoptionalKey2", suite: suite_)
let key3 = Defaults.Key<String?>("aoptionalKey3", suite: suite_)
Defaults[key1] = newFixture1
Defaults[key2] = newFixture2
Defaults[key3] = newFixture3
Defaults.reset(key1, key2)
#expect(Defaults[key1] == nil)
#expect(Defaults[key2] == nil)
#expect(Defaults[key3] == newFixture3)
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@Test
func testImmediatelyFinishingPublisherCombine() async throws {
let key = Defaults.Key<Bool>("observeKey", default: false, suite: suite_)
let result: Void? = try await withThrowingTaskGroup(of: Void.self) { group in
let publisher = Defaults
.publisher(key, options: [.initial])
.first()
group.addTask {
for try await _ in publisher.values {
return
}
}
return try await group.next()
}
#expect(result != nil)
}
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, visionOS 1.0, *)
@Test
func testImmediatelyFinishingMultiplePublisherCombine() async throws {
let key1 = Defaults.Key<Bool>("observeKey1", default: false, suite: suite_)
let key2 = Defaults.Key<String>("observeKey2", default: "🦄", suite: suite_)
let result: Void? = try await withThrowingTaskGroup(of: Void.self) { group in
let publisher = Defaults
.publisher(keys: [key1, key2], options: [.initial])
.first()
group.addTask {
for try await _ in publisher.values {
return
}
}
return try await group.next()
}
#expect(result != nil)
}
@Test
func testKeyEquatable() {
// swiftlint:disable:next identical_operands
#expect(Defaults.Key<Bool>("equatableKeyTest", default: false, suite: suite_) == Defaults.Key<Bool>("equatableKeyTest", default: false, suite: suite_))
}
@Test
func testKeyHashable() {
_ = Set([Defaults.Key<Bool>("hashableKeyTest", default: false, suite: suite_)])
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testUpdates() async {
let key = Defaults.Key<Bool>("updatesKey", default: false, suite: suite_)
async let waiter = Defaults.updates(key, initial: false).first { $0 }
try? await Task.sleep(for: .seconds(0.1))
Defaults[key] = true
guard let result = await waiter else {
Issue.record("Failed to get result")
return
}
#expect(result)
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testUpdatesMultipleKeys() async {
let key1 = Defaults.Key<Bool>("updatesMultipleKey1", default: false, suite: suite_)
let key2 = Defaults.Key<Bool>("updatesMultipleKey2", default: false, suite: suite_)
let counter = Counter()
async let waiter: Void = {
for await _ in Defaults.updates([key1, key2], initial: false) {
await counter.increment()
if await counter.count == 2 {
break
}
}
}()
try? await Task.sleep(for: .seconds(0.1))
Defaults[key1] = true
Defaults[key2] = true
await waiter
let count = await counter.count
#expect(count == 2)
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, visionOS 1.0, *)
@Test
func testUpdatesMultipleKeysVariadic() async {
let key1 = Defaults.Key<Bool>("updatesMultipleKeyVariadic1", default: false, suite: suite_)
let key2 = Defaults.Key<Bool>("updatesMultipleKeyVariadic2", default: false, suite: suite_)
let counter = Counter()
async let waiter: Void = {
for await (_, _) in Defaults.updates(key1, key2, initial: false) {
await counter.increment()
if await counter.count == 2 {
break
}
}
}()
try? await Task.sleep(for: .seconds(0.1))
Defaults[key1] = true
Defaults[key2] = true
await waiter
let count = await counter.count
#expect(count == 2)
}
}
actor Counter {
private var _count = 0
var count: Int { _count }
func increment() {
_count += 1
}
}
// swiftlint:enable discouraged_optional_boolean