From 46dfe486455b0a1e13f7f1d7753dbd0cbf4cdd2b Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Thu, 18 Jan 2024 20:00:21 +0700 Subject: [PATCH] Remove v5 migration functionality (#159) --- .../Migration/Migration+Defaults.swift | 46 - .../Migration/v5/Migration+Extensions.swift | 235 ---- .../Migration/v5/Migration+Protocol.swift | 61 - .../Migration/v5/Migration+UserDefaults.swift | 48 - .../DefaultsMigrationTests.swift | 1167 ----------------- migration.md | 405 ------ readme.md | 11 - 7 files changed, 1973 deletions(-) delete mode 100644 Sources/Defaults/Migration/Migration+Defaults.swift delete mode 100644 Sources/Defaults/Migration/v5/Migration+Extensions.swift delete mode 100644 Sources/Defaults/Migration/v5/Migration+Protocol.swift delete mode 100644 Sources/Defaults/Migration/v5/Migration+UserDefaults.swift delete mode 100644 Tests/DefaultsTests/DefaultsMigrationTests.swift delete mode 100644 migration.md diff --git a/Sources/Defaults/Migration/Migration+Defaults.swift b/Sources/Defaults/Migration/Migration+Defaults.swift deleted file mode 100644 index d87ebdb..0000000 --- a/Sources/Defaults/Migration/Migration+Defaults.swift +++ /dev/null @@ -1,46 +0,0 @@ -import Foundation - -extension Defaults { - public enum Version: Int { - case v5 = 5 - } - - /** - Migrate the given key's value from JSON string to `Value`. - - ```swift - extension Defaults.Keys { - static let array = Key?>("array") - } - - Defaults.migrate(.array, to: .v5) - ``` - */ - public static func migrate(_ keys: Key..., to version: Version) { - migrate(keys, to: version) - } - - public static func migrate(_ keys: Key..., to version: Version) { - migrate(keys, to: version) - } - - public static func migrate(_ keys: [Key], to version: Version) { - switch version { - case .v5: - for key in keys { - let suite = key.suite - suite.migrateCodableToNative(forKey: key.name, of: Value.self) - } - } - } - - public static func migrate(_ keys: [Key], to version: Version) { - switch version { - case .v5: - for key in keys { - let suite = key.suite - suite.migrateCodableToNative(forKey: key.name, of: Value.self) - } - } - } -} diff --git a/Sources/Defaults/Migration/v5/Migration+Extensions.swift b/Sources/Defaults/Migration/v5/Migration+Extensions.swift deleted file mode 100644 index d9243d4..0000000 --- a/Sources/Defaults/Migration/v5/Migration+Extensions.swift +++ /dev/null @@ -1,235 +0,0 @@ -import Foundation -import CoreGraphics - -extension Defaults { - public typealias NativeType = _DefaultsNativeType - public typealias CodableType = _DefaultsCodableType -} - -extension Data: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Data: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Date: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Date: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Bool: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Bool: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Int: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Int: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension UInt: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension UInt: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Double: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Double: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Float: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Float: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension String: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension String: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension CGFloat: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension CGFloat: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Int8: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Int8: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension UInt8: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension UInt8: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Int16: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Int16: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension UInt16: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension UInt16: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Int32: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Int32: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension UInt32: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension UInt32: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Int64: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension Int64: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension UInt64: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension UInt64: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension URL: Defaults.NativeType { - public typealias CodableForm = Self -} - -extension URL: Defaults.CodableType { - public typealias NativeForm = Self - - public func toNative() -> Self { self } -} - -extension Optional: Defaults.NativeType where Wrapped: Defaults.NativeType { - public typealias CodableForm = Wrapped.CodableForm -} - -extension Defaults.CollectionSerializable where Self: Defaults.NativeType, Element: Defaults.NativeType { - public typealias CodableForm = [Element.CodableForm] -} - -extension Defaults.SetAlgebraSerializable where Self: Defaults.NativeType, Element: Defaults.NativeType { - public typealias CodableForm = [Element.CodableForm] -} - -extension Defaults.CodableType where Self: RawRepresentable, NativeForm: RawRepresentable { - public func toNative() -> NativeForm { - NativeForm(rawValue: rawValue)! - } -} - -extension Set: Defaults.NativeType where Element: Defaults.NativeType { - public typealias CodableForm = [Element.CodableForm] -} - -extension Array: Defaults.NativeType where Element: Defaults.NativeType { - public typealias CodableForm = [Element.CodableForm] -} - -extension Array: Defaults.CodableType where Element: Defaults.CodableType { - public typealias NativeForm = [Element.NativeForm] - - public func toNative() -> NativeForm { - map { $0.toNative() } - } -} - -extension Dictionary: Defaults.NativeType where Key: LosslessStringConvertible & Hashable, Value: Defaults.NativeType { - public typealias CodableForm = [String: Value.CodableForm] -} - -extension Dictionary: Defaults.CodableType where Key == String, Value: Defaults.CodableType { - public typealias NativeForm = [String: Value.NativeForm] - - public func toNative() -> NativeForm { - reduce(into: NativeForm()) { memo, tuple in - memo[tuple.key] = tuple.value.toNative() - } - } -} diff --git a/Sources/Defaults/Migration/v5/Migration+Protocol.swift b/Sources/Defaults/Migration/v5/Migration+Protocol.swift deleted file mode 100644 index a68c422..0000000 --- a/Sources/Defaults/Migration/v5/Migration+Protocol.swift +++ /dev/null @@ -1,61 +0,0 @@ -import Foundation - -/** -Only exists for migration. - -Represents the type after migration and its protocol should conform to `Defaults.Serializable`. - -It should have an associated type name `CodableForm` where its protocol conform to `Codable`. - -So we can convert the JSON string into a `NativeType` like this: - -```swift -guard - let jsonData = string?.data(using: .utf8), - let codable = try? JSONDecoder().decode(NativeType.CodableForm.self, from: jsonData) -else { - return nil -} - -return codable.toNative() -``` -*/ -public protocol _DefaultsNativeType: Defaults.Serializable { - associatedtype CodableForm: Defaults.CodableType -} - -/** -Only exists for migration. - -Represents the type before migration an its protocol should conform to `Codable`. - -The main purposed of `CodableType` is trying to infer the `Codable` type to do `JSONDecoder().decode`. It should have an associated type name `NativeForm` which is the type we want it to store in `UserDefaults`. nd it also have a `toNative()` function to convert itself into `NativeForm`. - -```swift -struct User { - username: String - password: String -} - -struct CodableUser: Codable { - username: String - password: String -} - -extension User: Defaults.NativeType { - typealias CodableForm = CodableUser -} - -extension CodableUser: Defaults.CodableType { - typealias NativeForm = User - - func toNative() -> NativeForm { - User(username: self.username, password: self.password) - } -} -``` -*/ -public protocol _DefaultsCodableType: Codable { - associatedtype NativeForm: Defaults.NativeType - func toNative() -> NativeForm -} diff --git a/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift b/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift deleted file mode 100644 index feb7c2b..0000000 --- a/Sources/Defaults/Migration/v5/Migration+UserDefaults.swift +++ /dev/null @@ -1,48 +0,0 @@ -import Foundation - -extension UserDefaults { - func migrateCodableToNative(forKey key: String, of type: Value.Type) { - guard - let jsonData = string(forKey: key)?.data(using: .utf8), - let codable = try? JSONDecoder().decode(Value.self, from: jsonData) - else { - return - } - - _set(key, to: codable) - } - - /** - Get a JSON string in `UserDefaults` and decode it into its `NativeForm`. - - How does it work? - - 1. If `Value` is `[String]`, `Value.CodableForm` will covert into `[String].CodableForm`. - - ```swift - JSONDecoder().decode([String].CodableForm.self, from: jsonData) - ``` - - 2. If `Array` conforms to `NativeType`, its `CodableForm` is `[Element.CodableForm]` and `Element` is `String`. - - ```swift - JSONDecoder().decode([String.CodableForm].self, from: jsonData) - ``` - - 3. `String`'s `CodableForm` is `self`, because `String` is `Codable`. - - ```swift - JSONDecoder().decode([String].self, from: jsonData) - ``` - */ - func migrateCodableToNative(forKey key: String, of type: Value.Type) { - guard - let jsonData = string(forKey: key)?.data(using: .utf8), - let codable = try? JSONDecoder().decode(Value.CodableForm.self, from: jsonData) - else { - return - } - - _set(key, to: codable.toNative()) - } -} diff --git a/Tests/DefaultsTests/DefaultsMigrationTests.swift b/Tests/DefaultsTests/DefaultsMigrationTests.swift deleted file mode 100644 index 5f30efa..0000000 --- a/Tests/DefaultsTests/DefaultsMigrationTests.swift +++ /dev/null @@ -1,1167 +0,0 @@ -import Defaults -import Foundation -import XCTest - -// Create an unique ID to test whether `LosslessStringConvertible` works. -private struct UniqueID: LosslessStringConvertible, Hashable { - var id: Int64 - - var description: String { - "\(id)" - } - - init(id: Int64) { - self.id = id - } - - init?(_ description: String) { - self.init(id: Int64(description) ?? 0) - } -} - -private struct TimeZone: Hashable { - var id: String - var name: String -} - -extension TimeZone: Defaults.NativeType { - /** - Associated `CodableForm` to `CodableTimeZone`. - */ - typealias CodableForm = CodableTimeZone - - static let bridge = TimeZoneBridge() -} - -private struct CodableTimeZone { - var id: String - var name: String -} - -extension CodableTimeZone: Defaults.CodableType { - /** - Convert from `Codable` to `Native`. - */ - func toNative() -> TimeZone { - TimeZone(id: id, name: name) - } -} - -private struct TimeZoneBridge: Defaults.Bridge { - typealias Value = TimeZone - typealias Serializable = [String: Any] - - func serialize(_ value: TimeZone?) -> Serializable? { - guard let value else { - return nil - } - - return ["id": value.id, "name": value.name] - } - - func deserialize(_ object: Serializable?) -> TimeZone? { - guard - let object, - let id = object["id"] as? String, - let name = object["name"] as? String - else { - return nil - } - - return TimeZone(id: id, name: name) - } -} - -private struct ChosenTimeZone: Codable, Hashable { - var id: String - var name: String -} - -extension ChosenTimeZone: Defaults.Serializable { - static let bridge = ChosenTimeZoneBridge() -} - -private struct ChosenTimeZoneBridge: Defaults.Bridge { - typealias Value = ChosenTimeZone - typealias Serializable = [String: Any] - - func serialize(_ value: Value?) -> Serializable? { - guard let value else { - return nil - } - - return ["id": value.id, "name": value.name] - } - - func deserialize(_ object: Serializable?) -> Value? { - guard - let object, - let id = object["id"] as? String, - let name = object["name"] as? String - else { - return nil - } - - return ChosenTimeZone(id: id, name: name) - } -} - -private protocol BagForm { - associatedtype Element - var items: [Element] { get set } -} - -extension BagForm { - var startIndex: Int { - items.startIndex - } - - var endIndex: Int { - items.endIndex - } - - mutating func insert(element: Element, at: Int) { - items.insert(element, at: at) - } - - func index(after index: Int) -> Int { - items.index(after: index) - } - - subscript(position: Int) -> Element { - get { items[position] } - set { items[position] = newValue } - } -} - -private struct MyBag: BagForm, Defaults.CollectionSerializable, Defaults.NativeType { - var items: [Element] - - init(_ elements: [Element]) { - self.items = elements - } -} - -private struct CodableBag: BagForm, Defaults.CollectionSerializable, Codable { - var items: [Element] - - init(_ elements: [Element]) { - self.items = elements - } -} - -private protocol SetForm: SetAlgebra where Element: Hashable { - var store: Set { get set } -} - -extension SetForm { - func contains(_ member: Element) -> Bool { - store.contains(member) - } - - func union(_ other: Self) -> Self { - Self(store.union(other.store)) - } - - func intersection(_ other: Self) -> Self { - var setForm = Self() - setForm.store = store.intersection(other.store) - return setForm - } - - func symmetricDifference(_ other: Self) -> Self { - var setForm = Self() - setForm.store = store.symmetricDifference(other.store) - return setForm - } - - @discardableResult - mutating func insert(_ newMember: Element) -> (inserted: Bool, memberAfterInsert: Element) { - store.insert(newMember) - } - - mutating func remove(_ member: Element) -> Element? { - store.remove(member) - } - - mutating func update(with newMember: Element) -> Element? { - store.update(with: newMember) - } - - mutating func formUnion(_ other: Self) { - store.formUnion(other.store) - } - - mutating func formSymmetricDifference(_ other: Self) { - store.formSymmetricDifference(other.store) - } - - mutating func formIntersection(_ other: Self) { - store.formIntersection(other.store) - } - - func toArray() -> [Element] { - Array(store) - } -} - -private struct MySet: SetForm, Defaults.SetAlgebraSerializable, Defaults.NativeType { - var store: Set - - init() { - self.store = [] - } - - init(_ elements: [Element]) { - self.store = Set(elements) - } -} - -private struct CodableSet: SetForm, Defaults.SetAlgebraSerializable, Codable { - var store: Set - - init() { - self.store = [] - } - - init(_ elements: [Element]) { - self.store = Set(elements) - } -} - -private enum EnumForm: String { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} - -extension EnumForm: Defaults.NativeType { - typealias CodableForm = CodableEnumForm -} - -private enum CodableEnumForm: String { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} - -extension CodableEnumForm: Defaults.CodableType { - typealias NativeForm = EnumForm -} - -private func setCodable(forKey keyName: String, data: some Codable) { - guard - let text = try? JSONEncoder().encode(data), - let string = String(data: text, encoding: .utf8) - else { - XCTAssert(false) - return - } - - UserDefaults.standard.set(string, forKey: keyName) -} - -extension Defaults.Keys { - fileprivate static let nativeArray = Key<[String]?>("arrayToNativeStaticArrayKey") -} - -final class DefaultsMigrationTests: XCTestCase { - override func setUp() { - super.setUp() - Defaults.removeAll() - } - - override func tearDown() { - super.tearDown() - Defaults.removeAll() - } - - func testDataToNativeData() { - let answer = "Hello World!" - let keyName = "dataToNativeData" - let data = answer.data(using: .utf8) - setCodable(forKey: keyName, data: data) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(answer, String(data: Defaults[key]!, encoding: .utf8)) - let newName = " Hank Chen" - Defaults[key]?.append(newName.data(using: .utf8)!) - XCTAssertEqual(answer + newName, String(data: Defaults[key]!, encoding: .utf8)) - } - - func testArrayDataToNativeCollectionData() { - let answer = "Hello World!" - let keyName = "arrayDataToNativeCollectionData" - let data = answer.data(using: .utf8) - setCodable(forKey: keyName, data: [data]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(answer, String(data: Defaults[key]!.first!, encoding: .utf8)) - let newName = " Hank Chen" - Defaults[key]?[0].append(newName.data(using: .utf8)!) - XCTAssertEqual(answer + newName, String(data: Defaults[key]!.first!, encoding: .utf8)) - } - - func testArrayDataToCodableCollectionData() { - let answer = "Hello World!" - let keyName = "arrayDataToCodableCollectionData" - let data = answer.data(using: .utf8) - setCodable(forKey: keyName, data: CodableBag([data])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(answer, String(data: Defaults[key]!.first!, encoding: .utf8)) - let newName = " Hank Chen" - Defaults[key]?[0].append(newName.data(using: .utf8)!) - XCTAssertEqual(answer + newName, String(data: Defaults[key]!.first!, encoding: .utf8)) - } - - func testArrayDataToNativeSetAlgebraData() { - let answer = "Hello World!" - let keyName = "arrayDataToNativeSetAlgebraData" - let data = answer.data(using: .utf8) - setCodable(forKey: keyName, data: CodableSet([data])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(answer, String(data: Defaults[key]!.store.first!, encoding: .utf8)) - let newName = " Hank Chen" - Defaults[key]?.store.insert(newName.data(using: .utf8)!) - XCTAssertEqual(Set([answer.data(using: .utf8)!, newName.data(using: .utf8)!]), Defaults[key]?.store) - } - - func testDateToNativeDate() { - let date = Date() - let keyName = "dateToNativeDate" - setCodable(forKey: keyName, data: date) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(date, Defaults[key]) - let newDate = Date() - Defaults[key] = newDate - XCTAssertEqual(newDate, Defaults[key]) - } - - func testDateToNativeCollectionDate() { - let date = Date() - let keyName = "dateToNativeCollectionDate" - setCodable(forKey: keyName, data: [date]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(date, Defaults[key]!.first) - let newDate = Date() - Defaults[key]?[0] = newDate - XCTAssertEqual(newDate, Defaults[key]!.first) - } - - func testDateToCodableCollectionDate() { - let date = Date() - let keyName = "dateToCodableCollectionDate" - setCodable(forKey: keyName, data: CodableBag([date])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(date, Defaults[key]!.first) - let newDate = Date() - Defaults[key]?[0] = newDate - XCTAssertEqual(newDate, Defaults[key]!.first) - } - - func testBoolToNativeBool() { - let bool = false - let keyName = "boolToNativeBool" - setCodable(forKey: keyName, data: bool) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], bool) - let newBool = true - Defaults[key] = newBool - XCTAssertEqual(Defaults[key], newBool) - } - - func testBoolToNativeCollectionBool() { - let bool = false - let keyName = "boolToNativeCollectionBool" - setCodable(forKey: keyName, data: [bool]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], bool) - let newBool = true - Defaults[key]?[0] = newBool - XCTAssertEqual(Defaults[key]?[0], newBool) - } - - func testBoolToCodableCollectionBool() { - let bool = false - let keyName = "boolToCodableCollectionBool" - setCodable(forKey: keyName, data: CodableBag([bool])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], bool) - let newBool = true - Defaults[key]?[0] = newBool - XCTAssertEqual(Defaults[key]?[0], newBool) - } - - func testIntToNativeInt() { - let int = Int.min - let keyName = "intToNativeInt" - setCodable(forKey: keyName, data: int) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], int) - let newInt = Int.max - Defaults[key] = newInt - XCTAssertEqual(Defaults[key], newInt) - } - - func testIntToNativeCollectionInt() { - let int = Int.min - let keyName = "intToNativeCollectionInt" - setCodable(forKey: keyName, data: [int]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int) - let newInt = Int.max - Defaults[key]?[0] = newInt - XCTAssertEqual(Defaults[key]?[0], newInt) - } - - func testIntToCodableCollectionInt() { - let int = Int.min - let keyName = "intToCodableCollectionInt" - setCodable(forKey: keyName, data: CodableBag([int])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int) - let newInt = Int.max - Defaults[key]?[0] = newInt - XCTAssertEqual(Defaults[key]?[0], newInt) - } - - func testUIntToNativeUInt() { - let uInt = UInt.min - let keyName = "uIntToNativeUInt" - setCodable(forKey: keyName, data: uInt) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], uInt) - let newUInt = UInt.max - Defaults[key] = newUInt - XCTAssertEqual(Defaults[key], newUInt) - } - - func testUIntToNativeCollectionUInt() { - let uInt = UInt.min - let keyName = "uIntToNativeCollectionUInt" - setCodable(forKey: keyName, data: [uInt]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt) - let newUInt = UInt.max - Defaults[key]?[0] = newUInt - XCTAssertEqual(Defaults[key]?[0], newUInt) - } - - func testUIntToCodableCollectionUInt() { - let uInt = UInt.min - let keyName = "uIntToCodableCollectionUInt" - setCodable(forKey: keyName, data: CodableBag([uInt])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt) - let newUInt = UInt.max - Defaults[key]?[0] = newUInt - XCTAssertEqual(Defaults[key]?[0], newUInt) - } - - func testDoubleToNativeDouble() { - let double = Double.zero - let keyName = "doubleToNativeDouble" - setCodable(forKey: keyName, data: double) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], double) - let newDouble = Double.infinity - Defaults[key] = newDouble - XCTAssertEqual(Defaults[key], newDouble) - } - - func testDoubleToNativeCollectionDouble() { - let double = Double.zero - let keyName = "doubleToNativeCollectionDouble" - setCodable(forKey: keyName, data: [double]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], double) - let newDouble = Double.infinity - Defaults[key]?[0] = newDouble - XCTAssertEqual(Defaults[key]?[0], newDouble) - } - - func testDoubleToCodableCollectionDouble() { - let double = Double.zero - let keyName = "doubleToCodableCollectionDouble" - setCodable(forKey: keyName, data: CodableBag([double])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], double) - let newDouble = Double.infinity - Defaults[key]?[0] = newDouble - XCTAssertEqual(Defaults[key]?[0], newDouble) - } - - func testFloatToNativeFloat() { - let float = Float.zero - let keyName = "floatToNativeFloat" - setCodable(forKey: keyName, data: float) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], float) - let newFloat = Float.infinity - Defaults[key] = newFloat - XCTAssertEqual(Defaults[key], newFloat) - } - - func testFloatToNativeCollectionFloat() { - let float = Float.zero - let keyName = "floatToNativeCollectionFloat" - setCodable(forKey: keyName, data: [float]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], float) - let newFloat = Float.infinity - Defaults[key]?[0] = newFloat - XCTAssertEqual(Defaults[key]?[0], newFloat) - } - - func testFloatToCodableCollectionFloat() { - let float = Float.zero - let keyName = "floatToCodableCollectionFloat" - setCodable(forKey: keyName, data: CodableBag([float])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], float) - let newFloat = Float.infinity - Defaults[key]?[0] = newFloat - XCTAssertEqual(Defaults[key]?[0], newFloat) - } - - func testCGFloatToNativeCGFloat() { - let cgFloat = CGFloat.zero - let keyName = "cgFloatToNativeCGFloat" - setCodable(forKey: keyName, data: cgFloat) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], cgFloat) - let newCGFloat = CGFloat.infinity - Defaults[key] = newCGFloat - XCTAssertEqual(Defaults[key], newCGFloat) - } - - func testCGFloatToNativeCollectionCGFloat() { - let cgFloat = CGFloat.zero - let keyName = "cgFloatToNativeCollectionCGFloat" - setCodable(forKey: keyName, data: [cgFloat]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], cgFloat) - let newCGFloat = CGFloat.infinity - Defaults[key]?[0] = newCGFloat - XCTAssertEqual(Defaults[key]?[0], newCGFloat) - } - - func testCGFloatToCodableCollectionCGFloat() { - let cgFloat = CGFloat.zero - let keyName = "cgFloatToCodableCollectionCGFloat" - setCodable(forKey: keyName, data: CodableBag([cgFloat])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], cgFloat) - let newCGFloat = CGFloat.infinity - Defaults[key]?[0] = newCGFloat - XCTAssertEqual(Defaults[key]?[0], newCGFloat) - } - - func testInt8ToNativeInt8() { - let int8 = Int8.min - let keyName = "int8ToNativeInt8" - setCodable(forKey: keyName, data: int8) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], int8) - let newInt8 = Int8.max - Defaults[key] = newInt8 - XCTAssertEqual(Defaults[key], newInt8) - } - - func testInt8ToNativeCollectionInt8() { - let int8 = Int8.min - let keyName = "int8ToNativeCollectionInt8" - setCodable(forKey: keyName, data: [int8]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int8) - let newInt8 = Int8.max - Defaults[key]?[0] = newInt8 - XCTAssertEqual(Defaults[key]?[0], newInt8) - } - - func testInt8ToCodableCollectionInt8() { - let int8 = Int8.min - let keyName = "int8ToCodableCollectionInt8" - setCodable(forKey: keyName, data: CodableBag([int8])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int8) - let newInt8 = Int8.max - Defaults[key]?[0] = newInt8 - XCTAssertEqual(Defaults[key]?[0], newInt8) - } - - func testUInt8ToNativeUInt8() { - let uInt8 = UInt8.min - let keyName = "uInt8ToNativeUInt8" - setCodable(forKey: keyName, data: uInt8) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], uInt8) - let newUInt8 = UInt8.max - Defaults[key] = newUInt8 - XCTAssertEqual(Defaults[key], newUInt8) - } - - func testUInt8ToNativeCollectionUInt8() { - let uInt8 = UInt8.min - let keyName = "uInt8ToNativeCollectionUInt8" - setCodable(forKey: keyName, data: [uInt8]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt8) - let newUInt8 = UInt8.max - Defaults[key]?[0] = newUInt8 - XCTAssertEqual(Defaults[key]?[0], newUInt8) - } - - func testUInt8ToCodableCollectionUInt8() { - let uInt8 = UInt8.min - let keyName = "uInt8ToCodableCollectionUInt8" - setCodable(forKey: keyName, data: CodableBag([uInt8])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt8) - let newUInt8 = UInt8.max - Defaults[key]?[0] = newUInt8 - XCTAssertEqual(Defaults[key]?[0], newUInt8) - } - - func testInt16ToNativeInt16() { - let int16 = Int16.min - let keyName = "int16ToNativeInt16" - setCodable(forKey: keyName, data: int16) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], int16) - let newInt16 = Int16.max - Defaults[key] = newInt16 - XCTAssertEqual(Defaults[key], newInt16) - } - - func testInt16ToNativeCollectionInt16() { - let int16 = Int16.min - let keyName = "int16ToNativeCollectionInt16" - setCodable(forKey: keyName, data: [int16]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int16) - let newInt16 = Int16.max - Defaults[key]?[0] = newInt16 - XCTAssertEqual(Defaults[key]?[0], newInt16) - } - - func testInt16ToCodableCollectionInt16() { - let int16 = Int16.min - let keyName = "int16ToCodableCollectionInt16" - setCodable(forKey: keyName, data: CodableBag([int16])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int16) - let newInt16 = Int16.max - Defaults[key]?[0] = newInt16 - XCTAssertEqual(Defaults[key]?[0], newInt16) - } - - func testUInt16ToNativeUInt16() { - let uInt16 = UInt16.min - let keyName = "uInt16ToNativeUInt16" - setCodable(forKey: keyName, data: uInt16) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], uInt16) - let newUInt16 = UInt16.max - Defaults[key] = newUInt16 - XCTAssertEqual(Defaults[key], newUInt16) - } - - func testUInt16ToNativeCollectionUInt16() { - let uInt16 = UInt16.min - let keyName = "uInt16ToNativeCollectionUInt16" - setCodable(forKey: keyName, data: [uInt16]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt16) - let newUInt16 = UInt16.max - Defaults[key]?[0] = newUInt16 - XCTAssertEqual(Defaults[key]?[0], newUInt16) - } - - func testUInt16ToCodableCollectionUInt16() { - let uInt16 = UInt16.min - let keyName = "uInt16ToCodableCollectionUInt16" - setCodable(forKey: keyName, data: CodableBag([uInt16])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt16) - let newUInt16 = UInt16.max - Defaults[key]?[0] = newUInt16 - XCTAssertEqual(Defaults[key]?[0], newUInt16) - } - - func testInt32ToNativeInt32() { - let int32 = Int32.min - let keyName = "int32ToNativeInt32" - setCodable(forKey: keyName, data: int32) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], int32) - let newInt32 = Int32.max - Defaults[key] = newInt32 - XCTAssertEqual(Defaults[key], newInt32) - } - - func testInt32ToNativeCollectionInt32() { - let int32 = Int32.min - let keyName = "int32ToNativeCollectionInt32" - setCodable(forKey: keyName, data: [int32]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int32) - let newInt32 = Int32.max - Defaults[key]?[0] = newInt32 - XCTAssertEqual(Defaults[key]?[0], newInt32) - } - - func testInt32ToCodableCollectionInt32() { - let int32 = Int32.min - let keyName = "int32ToCodableCollectionInt32" - setCodable(forKey: keyName, data: CodableBag([int32])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int32) - let newInt32 = Int32.max - Defaults[key]?[0] = newInt32 - XCTAssertEqual(Defaults[key]?[0], newInt32) - } - - func testUInt32ToNativeUInt32() { - let uInt32 = UInt32.min - let keyName = "uInt32ToNativeUInt32" - setCodable(forKey: keyName, data: uInt32) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], uInt32) - let newUInt32 = UInt32.max - Defaults[key] = newUInt32 - XCTAssertEqual(Defaults[key], newUInt32) - } - - func testUInt32ToNativeCollectionUInt32() { - let uInt32 = UInt32.min - let keyName = "uInt32ToNativeCollectionUInt32" - setCodable(forKey: keyName, data: [uInt32]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt32) - let newUInt32 = UInt32.max - Defaults[key]?[0] = newUInt32 - XCTAssertEqual(Defaults[key]?[0], newUInt32) - } - - func testUInt32ToCodableCollectionUInt32() { - let uInt32 = UInt32.min - let keyName = "uInt32ToCodableCollectionUInt32" - setCodable(forKey: keyName, data: CodableBag([uInt32])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt32) - let newUInt32 = UInt32.max - Defaults[key]?[0] = newUInt32 - XCTAssertEqual(Defaults[key]?[0], newUInt32) - } - - func testInt64ToNativeInt64() { - let int64 = Int64.min - let keyName = "int64ToNativeInt64" - setCodable(forKey: keyName, data: int64) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], int64) - let newInt64 = Int64.max - Defaults[key] = newInt64 - XCTAssertEqual(Defaults[key], newInt64) - } - - func testInt64ToNativeCollectionInt64() { - let int64 = Int64.min - let keyName = "int64ToNativeCollectionInt64" - setCodable(forKey: keyName, data: [int64]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int64) - let newInt64 = Int64.max - Defaults[key]?[0] = newInt64 - XCTAssertEqual(Defaults[key]?[0], newInt64) - } - - func testInt64ToCodableCollectionInt64() { - let int64 = Int64.min - let keyName = "int64ToCodableCollectionInt64" - setCodable(forKey: keyName, data: CodableBag([int64])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], int64) - let newInt64 = Int64.max - Defaults[key]?[0] = newInt64 - XCTAssertEqual(Defaults[key]?[0], newInt64) - } - - func testUInt64ToNativeUInt64() { - let uInt64 = UInt64.min - let keyName = "uInt64ToNativeUInt64" - setCodable(forKey: keyName, data: uInt64) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], uInt64) - let newUInt64 = UInt64.max - Defaults[key] = newUInt64 - XCTAssertEqual(Defaults[key], newUInt64) - } - - func testUInt64ToNativeCollectionUInt64() { - let uInt64 = UInt64.min - let keyName = "uInt64ToNativeCollectionUInt64" - setCodable(forKey: keyName, data: [uInt64]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt64) - let newUInt64 = UInt64.max - Defaults[key]?[0] = newUInt64 - XCTAssertEqual(Defaults[key]?[0], newUInt64) - } - - func testUInt64ToCodableCollectionUInt64() { - let uInt64 = UInt64.min - let keyName = "uInt64ToCodableCollectionUInt64" - setCodable(forKey: keyName, data: CodableBag([uInt64])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], uInt64) - let newUInt64 = UInt64.max - Defaults[key]?[0] = newUInt64 - XCTAssertEqual(Defaults[key]?[0], newUInt64) - } - - func testArrayURLToNativeArrayURL() { - let url = URL(string: "https://sindresorhus.com")! - let keyName = "arrayURLToNativeArrayURL" - setCodable(forKey: keyName, data: [url]) - let key = Defaults.Key<[URL]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], url) - let newURL = URL(string: "https://example.com")! - Defaults[key]?.append(newURL) - XCTAssertEqual(Defaults[key]?[1], newURL) - } - - func testArrayURLToNativeCollectionURL() { - let url = URL(string: "https://sindresorhus.com")! - let keyName = "arrayURLToNativeCollectionURL" - setCodable(forKey: keyName, data: [url]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], url) - let newURL = URL(string: "https://example.com")! - Defaults[key]?.insert(element: newURL, at: 1) - XCTAssertEqual(Defaults[key]?[1], newURL) - } - - func testArrayToNativeArray() { - let keyName = "arrayToNativeArrayKey" - setCodable(forKey: keyName, data: ["a", "b", "c"]) - let key = Defaults.Key<[String]>(keyName, default: []) - Defaults.migrate(key, to: .v5) - let newValue = "d" - Defaults[key].append(newValue) - XCTAssertEqual(Defaults[key][0], "a") - XCTAssertEqual(Defaults[key][1], "b") - XCTAssertEqual(Defaults[key][2], "c") - XCTAssertEqual(Defaults[key][3], newValue) - } - - func testArrayToNativeStaticOptionalArray() { - let keyName = "arrayToNativeStaticArrayKey" - setCodable(forKey: keyName, data: ["a", "b", "c"]) - Defaults.migrate(.nativeArray, to: .v5) - let newValue = "d" - Defaults[.nativeArray]?.append(newValue) - XCTAssertEqual(Defaults[.nativeArray]?[0], "a") - XCTAssertEqual(Defaults[.nativeArray]?[1], "b") - XCTAssertEqual(Defaults[.nativeArray]?[2], "c") - XCTAssertEqual(Defaults[.nativeArray]?[3], newValue) - } - - func testArrayToNativeOptionalArray() { - let keyName = "arrayToNativeArrayKey" - setCodable(forKey: keyName, data: ["a", "b", "c"]) - let key = Defaults.Key<[String]?>(keyName) - Defaults.migrate(key, to: .v5) - let newValue = "d" - Defaults[key]?.append(newValue) - XCTAssertEqual(Defaults[key]?[0], "a") - XCTAssertEqual(Defaults[key]?[1], "b") - XCTAssertEqual(Defaults[key]?[2], "c") - XCTAssertEqual(Defaults[key]?[3], newValue) - } - - func testArrayDictionaryStringIntToNativeArray() { - let keyName = "arrayDictionaryStringIntToNativeArray" - setCodable(forKey: keyName, data: [["a": 0, "b": 1]]) - let key = Defaults.Key<[[String: Int]]?>(keyName) - Defaults.migrate(key, to: .v5) - let newValue = 2 - let newDictionary = ["d": 3] - Defaults[key]?[0]["c"] = newValue - Defaults[key]?.append(newDictionary) - XCTAssertEqual(Defaults[key]?[0]["a"], 0) - XCTAssertEqual(Defaults[key]?[0]["b"], 1) - XCTAssertEqual(Defaults[key]?[0]["c"], newValue) - XCTAssertEqual(Defaults[key]?[1]["d"], newDictionary["d"]) - } - - func testArrayToNativeSet() { - let keyName = "arrayToNativeSet" - setCodable(forKey: keyName, data: ["a", "b", "c"]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - let newValue = "d" - Defaults[key]?.insert(newValue) - XCTAssertEqual(Defaults[key], Set(["a", "b", "c", "d"])) - } - - func testArrayToNativeCollectionType() { - let string = "Hello World!" - let keyName = "arrayToNativeCollectionType" - setCodable(forKey: keyName, data: [string]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], string) - let newString = "Hank Chen" - Defaults[key]?[0] = newString - XCTAssertEqual(Defaults[key]?[0], newString) - } - - func testArrayToCodableCollectionType() { - let keyName = "arrayToCodableCollectionType" - setCodable(forKey: keyName, data: CodableBag(["a", "b", "c"])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - let newValue = "d" - Defaults[key]?.insert(element: newValue, at: 3) - XCTAssertEqual(Defaults[key]?[0], "a") - XCTAssertEqual(Defaults[key]?[1], "b") - XCTAssertEqual(Defaults[key]?[2], "c") - XCTAssertEqual(Defaults[key]?[3], newValue) - } - - func testArrayAndCodableElementToNativeCollectionType() { - let keyName = "arrayAndCodableElementToNativeCollectionType" - setCodable(forKey: keyName, data: [CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0].id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?.insert(element: .init(id: "1", name: newName), at: 1) - XCTAssertEqual(Defaults[key]?[1].name, newName) - } - - func testArrayAndCodableElementToNativeSetAlgebraType() { - let keyName = "arrayAndCodableElementToNativeSetAlgebraType" - setCodable(forKey: keyName, data: [CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?.store.first?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?.insert(.init(id: "1", name: newName)) - XCTAssertEqual(Set([TimeZone(id: "0", name: "Asia/Taipei"), TimeZone(id: "1", name: newName)]), Defaults[key]?.store) - } - - func testCodableToNativeType() { - let keyName = "codableCodableToNativeType" - setCodable(forKey: keyName, data: CodableTimeZone(id: "0", name: "Asia/Taipei")) - let key = Defaults.Key(keyName, default: .init(id: "1", name: "Asia/Tokio")) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key].id, "0") - let newName = "Asia/Tokyo" - Defaults[key].name = newName - XCTAssertEqual(Defaults[key].name, newName) - } - - func testCodableToNativeOptionalType() { - let keyName = "codableCodableToNativeOptionalType" - setCodable(forKey: keyName, data: CodableTimeZone(id: "0", name: "Asia/Taipei")) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?.name = newName - XCTAssertEqual(Defaults[key]?.name, newName) - } - - func testArrayAndCodableElementToNativeArray() { - let keyName = "codableArrayAndCodableElementToNativeArray" - setCodable(forKey: keyName, data: [CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key<[TimeZone]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0].id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?[0].name = newName - XCTAssertEqual(Defaults[key]?[0].name, newName) - } - - func testArrayAndCodableElementToNativeSet() { - let keyName = "arrayAndCodableElementToNativeSet" - setCodable(forKey: keyName, data: [CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], Set([TimeZone(id: "0", name: "Asia/Taipei")])) - let newId = "1" - let newName = "Asia/Tokyo" - Defaults[key]?.insert(.init(id: newId, name: newName)) - XCTAssertEqual(Defaults[key], Set([TimeZone(id: "0", name: "Asia/Taipei"), TimeZone(id: newId, name: newName)])) - } - - func testCodableToNativeCodableOptionalType() { - let keyName = "codableToNativeCodableOptionalType" - setCodable(forKey: keyName, data: ChosenTimeZone(id: "0", name: "Asia/Taipei")) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?.name = newName - XCTAssertEqual(Defaults[key]?.name, newName) - } - - func testCodableArrayToNativeCodableArrayType() { - let keyName = "codableToNativeCodableArrayType" - setCodable(forKey: keyName, data: [ChosenTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key<[ChosenTimeZone]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0].id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?[0].name = newName - XCTAssertEqual(Defaults[key]?[0].name, newName) - } - - func testCodableArrayToNativeCollectionType() { - let keyName = "codableToNativeCollectionType" - setCodable(forKey: keyName, data: CodableBag([ChosenTimeZone(id: "0", name: "Asia/Taipei")])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0].id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?[0].name = newName - XCTAssertEqual(Defaults[key]?[0].name, newName) - } - - func testDictionaryToNativelyDictionary() { - let keyName = "codableDictionaryToNativelyDictionary" - setCodable(forKey: keyName, data: ["Hank": "Chen"]) - let key = Defaults.Key<[String: String]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?["Hank"], "Chen") - } - - func testDictionaryAndCodableValueToNativeDictionary() { - let keyName = "codableArrayAndCodableElementToNativeArray" - setCodable(forKey: keyName, data: ["0": CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key<[String: TimeZone]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?["0"]?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?["0"]?.name = newName - XCTAssertEqual(Defaults[key]?["0"]?.name, newName) - } - - func testDictionaryCodableKeyAndCodableValueToNativeDictionary() { - let keyName = "dictionaryCodableKeyAndCodableValueToNativeDictionary" - setCodable(forKey: keyName, data: [123: CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key<[UInt32: TimeZone]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[123]?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?[123]?.name = newName - XCTAssertEqual(Defaults[key]?[123]?.name, newName) - } - - func testDictionaryCustomKeyAndCodableValueToNativeDictionary() { - let keyName = "dictionaryCustomAndCodableValueToNativeDictionary" - setCodable(forKey: keyName, data: [1234: CodableTimeZone(id: "0", name: "Asia/Taipei")]) - let key = Defaults.Key<[UniqueID: TimeZone]?>(keyName) - Defaults.migrate(key, to: .v5) - let id = UniqueID(id: 1234) - XCTAssertEqual(Defaults[key]?[id]?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?[id]?.name = newName - XCTAssertEqual(Defaults[key]?[id]?.name, newName) - } - - func testNestedDictionaryCustomKeyAndCodableValueToNativeNestedDictionary() { - let keyName = "nestedDictionaryCustomKeyAndCodableValueToNativeNestedDictionary" - setCodable(forKey: keyName, data: [12_345: [1234: CodableTimeZone(id: "0", name: "Asia/Taipei")]]) - let key = Defaults.Key<[UniqueID: [UniqueID: TimeZone]]?>(keyName) - Defaults.migrate(key, to: .v5) - let firstId = UniqueID(id: 12_345) - let secondId = UniqueID(id: 1234) - XCTAssertEqual(Defaults[key]?[firstId]?[secondId]?.id, "0") - let newName = "Asia/Tokyo" - Defaults[key]?[firstId]?[secondId]?.name = newName - XCTAssertEqual(Defaults[key]?[firstId]?[secondId]?.name, newName) - } - - func testEnumToNativeEnum() { - let keyName = "enumToNativeEnum" - setCodable(forKey: keyName, data: CodableEnumForm.tenMinutes) - let key = Defaults.Key(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key], .tenMinutes) - Defaults[key] = .halfHour - XCTAssertEqual(Defaults[key], .halfHour) - } - - func testArrayEnumToNativeArrayEnum() { - let keyName = "arrayEnumToNativeArrayEnum" - setCodable(forKey: keyName, data: [CodableEnumForm.tenMinutes]) - let key = Defaults.Key<[EnumForm]?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?[0], .tenMinutes) - Defaults[key]?.append(.halfHour) - XCTAssertEqual(Defaults[key]?[1], .halfHour) - } - - func testArrayEnumToNativeSetEnum() { - let keyName = "arrayEnumToNativeSetEnum" - setCodable(forKey: keyName, data: Set([CodableEnumForm.tenMinutes])) - let key = Defaults.Key?>(keyName) - Defaults.migrate(key, to: .v5) - XCTAssertEqual(Defaults[key]?.first, .tenMinutes) - Defaults[key]?.insert(.halfHour) - XCTAssertEqual(Defaults[key], Set([.tenMinutes, .halfHour])) - } -} diff --git a/migration.md b/migration.md deleted file mode 100644 index d6e6945..0000000 --- a/migration.md +++ /dev/null @@ -1,405 +0,0 @@ -# Migration guide from v4 to v5 - -**Warning: Test the migration thoroughly in your app. It might cause unintended data loss if you're not careful.** - -## Summary - -We have improved the stored representation of types. Some types will require migration. Previously, all `Codable` types were serialized to a JSON string and stored as a `UserDefaults` string. `Defaults` is now able to store more types using the appropriate native `UserDefaults` type. - -- The following types require no changes: - - `Int(8/16/32/64)` - - `UInt(8/16/32/64)` - - `Double` - - `CGFloat` - - `Float` - - `String` - - `Bool` - - `Date` - - `Data` - - `URL` -- Custom types (`struct`, `enum`, etc.) must now conform to `Defaults.Serializable` (in addition to `Codable`). -- `Array`, `Set`, and `Dictionary` will need to be manually migrated with `Defaults.migrate()`. - ---- - -In v4, `Defaults` stored many types as a JSON string.\ -In v5, `Defaults` stores many types as native `UserDefaults` types. - -```swift -// v4 -let key = Defaults.Key<[Int]>("key", default: [0, 1]) - -UserDefaults.standard.string(forKey: "key") -//=> "[0, 1]" -``` - -```swift -// v5 -let key = Defaults.Key<[Int]>("key", default: [0, 1]) - -UserDefaults.standard.dictionary(forKey: "key") -//=> [0, 1] -``` - -## Issues - -1. **The compiler complains that `Defaults.Key` does not conform to `Defaults.Serializable`.** - Since we replaced `Codable` with `Defaults.Serializable`, `Key` will have to conform to `Value: Defaults.Serializable`. - For this situation, please follow the guides below: - - [From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5](#from-codable-struct-in-defaults-v4-to-codable-struct-in-defaults-v5) - - [From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5](#from-codable-enum-in-defaults-v4-to-codable-enum-in-defaults-v5) - -2. **The previous value in `UserDefaults` is not readable. (for example, `Defaults[.array]` returns `nil`).** - In v5, `Defaults` reads value from `UserDefaults` as a natively supported type, but since `UserDefaults` only contains JSON string before migration for `Codable` types, `Defaults` will not be able to work with it. For this situation, `Defaults` provides the `Defaults.migrate()` method to automate the migration process. - - [From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set`(with natively supported elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionaryset-with-natively-supported-elements-in-defaults-v5) - - [From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set` (with codable elements) in Defaults v5](#from-codable-arraydictionaryset-in-defaults-v4-to-native-arraydictionaryset-with-codable-elements-in-defaults-v5) - -## Testing - -We recommend doing some manual testing after migrating. - -For example, let's say you are trying to migrate an array of `Codable` string to a native array. - -1. Get the previous value in `UserDefaults` (using `defaults` command or whatever you want). - -```swift -let string = "[\"a\",\"b\",\"c\"]" -``` - -2. Insert the above value into `UserDefaults`. - -```swift -UserDefaults.standard.set(string, forKey: "testKey") -``` - -3. Call `Defaults.migrate()` and then use `Defaults` to get its value. - -```swift -let key = Defaults.Key<[String]>("testKey", default: []) -Defaults.migrate(key, to: .v5) - -Defaults[key] //=> [a, b, c] -``` - -## Migrations - -### From `Codable` struct in Defaults v4 to `Codable` struct in Defaults v5 - -In v4, `struct` had to conform to `Codable` to store it as a JSON string. - -In v5, `struct` has to conform to `Codable` and `Defaults.Serializable` to store it as a JSON string. - -#### Before migration - -```swift -private struct TimeZone: Codable { - var id: String - var name: String -} - -extension Defaults.Keys { - static let timezone = Defaults.Key("TimeZone") -} -``` - -#### Migration steps - -1. Make `TimeZone` conform to `Defaults.Serializable`. - -```swift -private struct TimeZone: Codable, Defaults.Serializable { - var id: String - var name: String -} -``` - -2. Now `Defaults[.timezone]` should be readable. - -### From `Codable` enum in Defaults v4 to `Codable` enum in Defaults v5 - -In v4, `enum` had to conform to `Codable` to store it as a JSON string. - -In v5, `enum` has to conform to `Codable` and `Defaults.Serializable` to store it as a JSON string. - -#### Before migration - -```swift -private enum Period: String, Codable { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} - -extension Defaults.Keys { - static let period = Defaults.Key("period") -} -``` - -#### Migration steps - -1. Make `Period` conform to `Defaults.Serializable`. - -```swift -private enum Period: String, Defaults.Serializable, Codable { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} -``` - -2. Now `Defaults[.period]` should be readable. - -### From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set` (with natively supported elements) in Defaults v5 - -In v4, `Defaults` stored array/dictionary as a JSON string: `"[\"a\", \"b\", \"c\"]"`. - -In v5, `Defaults` stores it as a native array/dictionary with natively supported elements: `["a", "b", "c"]`. - -#### Before migration - -```swift -extension Defaults.Keys { - static let arrayString = Defaults.Key<[String]?>("arrayString") - static let setString = Defaults.Key?>("setString") - static let dictionaryStringInt = Defaults.Key<[String: Int]?>("dictionaryStringInt") - static let dictionaryStringIntInArray = Defaults.Key<[[String: Int]]?>("dictionaryStringIntInArray") -} -``` - -#### Migration steps - -1. **Call `Defaults.migrate(.arrayString, to: .v5)`, `Defaults.migrate(.setString, to: .v5)`, `Defaults.migrate(.dictionaryStringInt, to: .v5)`, `Defaults.migrate(.dictionaryStringIntInArray, to: .v5)`.** -2. Now `Defaults[.arrayString]`, `Defaults.[.setString]`, `Defaults[.dictionaryStringInt]`, `Defaults[.dictionaryStringIntInArray]` should be readable. - -### From `Codable` `Array/Dictionary/Set` in Defaults v4 to native `Array/Dictionary/Set` (with `Codable` elements) in Defaults v5 - -In v4, `Defaults` would store array/dictionary as a single JSON string: `"{\"id\": \"0\", \"name\": \"Asia/Taipei\"}"`, `"[\"10 Minutes\", \"30 Minutes\"]"`. - -In v5, `Defaults` will store it as a native array/dictionary with `Codable` elements: `{id: 0, name: "Asia/Taipei"}`, `["10 Minutes", "30 Minutes"]`. - -#### Before migration - -```swift -private struct TimeZone: Hashable, Codable { - var id: String - var name: String -} - -private enum Period: String, Hashable, Codable { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} - -extension Defaults.Keys { - static let arrayTimezone = Defaults.Key<[TimeZone]?>("arrayTimezone") - static let setTimezone = Defaults.Key<[TimeZone]?>("setTimezone") - static let arrayPeriod = Defaults.Key<[Period]?>("arrayPeriod") - static let setPeriod = Defaults.Key<[Period]?>("setPeriod") - static let dictionaryTimezone = Defaults.Key<[String: TimeZone]?>("dictionaryTimezone") - static let dictionaryPeriod = Defaults.Key<[String: Period]?>("dictionaryPeriod") -} -``` - -#### Migration steps - -1. Make `TimeZone` and `Period` conform to `Defaults.Serializable`. - -```swift -private struct TimeZone: Hashable, Codable, Defaults.Serializable { - var id: String - var name: String -} - -private enum Period: String, Hashable, Codable, Defaults.Serializable { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} -``` - -2. **Call `Defaults.migrate(.arrayTimezone, to: .v5)`, `Defaults.migrate(.setTimezone, to: .v5)`, `Defaults.migrate(.dictionaryTimezone, to: .v5)`, `Defaults.migrate(.arrayPeriod, to: .v5)`, `Defaults.migrate(.setPeriod, to: .v5)` , `Defaults.migrate(.dictionaryPeriod, to: .v5)`.** -3. Now `Defaults[.arrayTimezone]`, `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]`, `Defaults[.arrayPeriod]`, `Defaults[.setPeriod]` , `Defaults[.dictionaryPeriod]` should be readable. - ---- - -## Optional migrations - -### From `Codable` enum in Defaults v4 to `RawRepresentable` enum in Defaults v5 *(Optional)* - -In v4, `Defaults` will store `enum` as a JSON string: `"10 Minutes"`. - -In v5, `Defaults` can store `enum` as a native string: `10 Minutes`. - -#### Before migration - -```swift -private enum Period: String, Codable { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} - -extension Defaults.Keys { - static let period = Defaults.Key("period") -} -``` - -#### Migration steps - -1. Create another enum called `CodablePeriod` and create an extension of it. Make the extension conform to `Defaults.CodableType` and its associated type `NativeForm` to `Period`. - -```swift -private enum CodablePeriod: String { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} - -extension CodablePeriod: Defaults.CodableType { - typealias NativeForm = Period -} -``` - -2. Remove `Codable` conformance so `Period` can be stored natively. - -```swift -private enum Period: String { - case tenMinutes = "10 Minutes" - case halfHour = "30 Minutes" - case oneHour = "1 Hour" -} -``` - -3. Create an extension of `Period` that conforms to `Defaults.NativeType`. Its `CodableForm` should be `CodablePeriod`. - -```swift -extension Period: Defaults.NativeType { - typealias CodableForm = CodablePeriod -} -``` - -4. **Call `Defaults.migrate(.period)`** -5. Now `Defaults[.period]` should be readable. - -You can also instead implement the `toNative` function in `Defaults.CodableType` for flexibility: - -```swift -extension CodablePeriod: Defaults.CodableType { - typealias NativeForm = Period - - public func toNative() -> Period { - switch self { - case .tenMinutes: - return .tenMinutes - case .halfHour: - return .halfHour - case .oneHour: - return .oneHour - } - } -} -``` - -### From `Codable` struct in Defaults v4 to `Dictionary` in Defaults v5 *(Optional)* - -This happens when you have a struct which is stored as a `Codable` JSON string before, but now you want it to be stored as a native `UserDefaults` dictionary. - -#### Before migration - -```swift -private struct TimeZone: Codable { - var id: String - var name: String -} - -extension Defaults.Keys { - static let timezone = Defaults.Key("TimeZone") - static let arrayTimezone = Defaults.Key<[TimeZone]?>("arrayTimezone") - static let setTimezone = Defaults.Key?>("setTimezone") - static let dictionaryTimezone = Defaults.Key<[String: TimeZone]?>("setTimezone") -} -``` - -#### Migration steps - -1. Create a `TimeZoneBridge` which conforms to `Defaults.Bridge` and its `Value` is `TimeZone` and `Serializable` is `[String: String]`. - -```swift -private struct TimeZoneBridge: Defaults.Bridge { - typealias Value = TimeZone - typealias Serializable = [String: String] - - func serialize(_ value: TimeZone?) -> Serializable? { - guard let value else { - return nil - } - - return [ - "id": value.id, - "name": value.name - ] - } - - func deserialize(_ object: Serializable?) -> TimeZone? { - guard - let object, - let id = object["id"], - let name = object["name"] - else { - return nil - } - - return TimeZone( - id: id, - name: name - ) - } -} -``` - -2. Create an extension of `TimeZone` that conforms to `Defaults.NativeType` and its static bridge is `TimeZoneBridge`. The compiler will complain that `TimeZone` does not conform to `Defaults.NativeType`. We will resolve that later. - -```swift -private struct TimeZone: Hashable { - var id: String - var name: String -} - -extension TimeZone: Defaults.NativeType { - static let bridge = TimeZoneBridge() -} -``` - -3. Create an extension of `CodableTimeZone` that conforms to `Defaults.CodableType`. - -```swift -private struct CodableTimeZone { - var id: String - var name: String -} - -extension CodableTimeZone: Defaults.CodableType { - /** - Convert from `Codable` to native type. - */ - func toNative() -> TimeZone { - TimeZone(id: id, name: name) - } -} -``` - -4. Associate `TimeZone.CodableForm` to `CodableTimeZone` - -```swift -extension TimeZone: Defaults.NativeType { - typealias CodableForm = CodableTimeZone - - static let bridge = TimeZoneBridge() -} -``` - -5. **Call `Defaults.migrate(.timezone, to: .v5)`, `Defaults.migrate(.arrayTimezone, to: .v5)`, `Defaults.migrate(.setTimezone, to: .v5)`, `Defaults.migrate(.dictionaryTimezone, to: .v5)`**. -6. Now `Defaults[.timezone]`, `Defaults[.arrayTimezone]` , `Defaults[.setTimezone]`, `Defaults[.dictionaryTimezone]` should be readable. - -**See [DefaultsMigrationTests.swift](./Tests/DefaultsTests/DefaultsMigrationTests.swift) for more example.** diff --git a/readme.md b/readme.md index a936902..6e326e4 100644 --- a/readme.md +++ b/readme.md @@ -434,17 +434,6 @@ Execute the closure without triggering change events. Any `Defaults` key changes made within the closure will not propagate to `Defaults` event listeners (`Defaults.observe()` and `Defaults.publisher()`). This can be useful to prevent infinite recursion when you want to change a key in the callback listening to changes for the same key. -#### `Defaults.migrate(keys..., to: Version)` - -```swift -Defaults.migrate(keys..., to: Version) -Defaults.migrate(keys..., to: Version) -``` - -Type: `func` - -Migrate the given keys to the specific version. - ### `@Default(_ key:)` Get/set a `Defaults` item and also have the SwiftUI view be updated when the value changes.