Add CustomCodablePreferenceKey
This commit is contained in:
parent
70227a7fa1
commit
e5c4fceacd
|
@ -27,17 +27,26 @@ final class Preference<Key: PreferenceKey>: Codable {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(from decoder: any Decoder) throws {
|
init(from decoder: any Decoder) throws {
|
||||||
if let container = try? decoder.singleValueContainer() {
|
if let keyType = Key.self as? any CustomCodablePreferenceKey.Type {
|
||||||
|
self.storedValue = try keyType.decode(from: decoder) as! Key.Value?
|
||||||
|
} else if let container = try? decoder.singleValueContainer() {
|
||||||
self.storedValue = try? container.decode(Key.Value.self)
|
self.storedValue = try? container.decode(Key.Value.self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(to encoder: any Encoder) throws {
|
func encode(to encoder: any Encoder) throws {
|
||||||
if let storedValue {
|
if let storedValue {
|
||||||
|
if let keyType = Key.self as? any CustomCodablePreferenceKey.Type {
|
||||||
|
func encode<K: CustomCodablePreferenceKey>(_: K.Type) throws {
|
||||||
|
try K.encode(value: storedValue as! K.Value, to: encoder)
|
||||||
|
}
|
||||||
|
return try _openExistential(keyType, do: encode)
|
||||||
|
} else {
|
||||||
var container = encoder.singleValueContainer()
|
var container = encoder.singleValueContainer()
|
||||||
try container.encode(storedValue)
|
try container.encode(storedValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static subscript(
|
static subscript(
|
||||||
_enclosingInstance instance: PreferenceStore,
|
_enclosingInstance instance: PreferenceStore,
|
||||||
|
|
|
@ -28,3 +28,8 @@ extension MigratablePreferenceKey {
|
||||||
oldValue != defaultValue
|
oldValue != defaultValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protocol CustomCodablePreferenceKey: PreferenceKey {
|
||||||
|
static func encode(value: Value, to encoder: any Encoder) throws
|
||||||
|
static func decode(from decoder: any Decoder) throws -> Value?
|
||||||
|
}
|
||||||
|
|
|
@ -15,11 +15,11 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
static let defaultValue = false
|
static let defaultValue = false
|
||||||
}
|
}
|
||||||
|
|
||||||
final class TestStore: Codable, ObservableObject {
|
final class TestStore<Key: PreferenceKey>: Codable, ObservableObject {
|
||||||
private var _test = Preference<TestKey>()
|
private var _test = Preference<Key>()
|
||||||
|
|
||||||
// the acutal subscript expects the enclosingInstance to be a PreferenceStore, so do it manually
|
// the acutal subscript expects the enclosingInstance to be a PreferenceStore, so do it manually
|
||||||
var test: Bool {
|
var test: Key.Value {
|
||||||
get {
|
get {
|
||||||
Preference.get(enclosingInstance: self, storage: \._test)
|
Preference.get(enclosingInstance: self, storage: \._test)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var testPublisher: some Publisher<TestKey.Value, Never> {
|
var testPublisher: some Publisher<Key.Value, Never> {
|
||||||
_test.projectedValue
|
_test.projectedValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
|
|
||||||
init(from decoder: any Decoder) throws {
|
init(from decoder: any Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
self._test = try container.decode(Preference<TestKey>.self, forKey: .test)
|
self._test = try container.decode(Preference<Key>.self, forKey: .test)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CodingKeys: CodingKey {
|
enum CodingKeys: CodingKey {
|
||||||
|
@ -52,18 +52,18 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
|
|
||||||
func testDecoding() throws {
|
func testDecoding() throws {
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
let present = try decoder.decode(PreferenceCoding<TestStore>.self, from: Data("""
|
let present = try decoder.decode(PreferenceCoding<TestStore<TestKey>>.self, from: Data("""
|
||||||
{"test": true}
|
{"test": true}
|
||||||
""".utf8)).wrapped
|
""".utf8)).wrapped
|
||||||
XCTAssertEqual(present.test, true)
|
XCTAssertEqual(present.test, true)
|
||||||
let absent = try decoder.decode(PreferenceCoding<TestStore>.self, from: Data("""
|
let absent = try decoder.decode(PreferenceCoding<TestStore<TestKey>>.self, from: Data("""
|
||||||
{}
|
{}
|
||||||
""".utf8)).wrapped
|
""".utf8)).wrapped
|
||||||
XCTAssertEqual(absent.test, false)
|
XCTAssertEqual(absent.test, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEncoding() throws {
|
func testEncoding() throws {
|
||||||
let store = TestStore()
|
let store = TestStore<TestKey>()
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
XCTAssertEqual(String(data: try encoder.encode(PreferenceCoding(wrapped: store)), encoding: .utf8)!, """
|
XCTAssertEqual(String(data: try encoder.encode(PreferenceCoding(wrapped: store)), encoding: .utf8)!, """
|
||||||
{}
|
{}
|
||||||
|
@ -83,7 +83,7 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
let specificPref = expectation(description: "preference publisher")
|
let specificPref = expectation(description: "preference publisher")
|
||||||
// initial and on change
|
// initial and on change
|
||||||
specificPref.expectedFulfillmentCount = 2
|
specificPref.expectedFulfillmentCount = 2
|
||||||
let store = TestStore()
|
let store = TestStore<TestKey>()
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
store.objectWillChange.sink {
|
store.objectWillChange.sink {
|
||||||
topLevel.fulfill()
|
topLevel.fulfill()
|
||||||
|
@ -97,4 +97,32 @@ final class PreferenceStoreTests: XCTestCase {
|
||||||
wait(for: [topLevel, specificPref])
|
wait(for: [topLevel, specificPref])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testCustomCodable() throws {
|
||||||
|
struct Key: CustomCodablePreferenceKey {
|
||||||
|
static let defaultValue = 1
|
||||||
|
static func encode(value: Int, to encoder: any Encoder) throws {
|
||||||
|
var container = encoder.singleValueContainer()
|
||||||
|
try container.encode(2)
|
||||||
|
}
|
||||||
|
static func decode(from decoder: any Decoder) throws -> Int? {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let store = TestStore<Key>()
|
||||||
|
store.test = 123
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
XCTAssertEqual(String(data: try encoder.encode(PreferenceCoding(wrapped: store)), encoding: .utf8)!, """
|
||||||
|
{"test":2}
|
||||||
|
""")
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let present = try decoder.decode(PreferenceCoding<TestStore<Key>>.self, from: Data("""
|
||||||
|
{"test":2}
|
||||||
|
""".utf8)).wrapped
|
||||||
|
XCTAssertEqual(present.test, 3)
|
||||||
|
let absent = try decoder.decode(PreferenceCoding<TestStore<Key>>.self, from: Data("""
|
||||||
|
{}
|
||||||
|
""".utf8)).wrapped
|
||||||
|
XCTAssertEqual(absent.test, 1)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue