mirror of
https://github.com/zhigang1992/Boutique.git
synced 2026-05-05 05:59:24 +08:00
Merge pull request #62 from mergesort/caching
Implementing an additional caching layer for StoredValue
This commit is contained in:
25
Sources/Boutique/CachedValue.swift
Normal file
25
Sources/Boutique/CachedValue.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
|
||||
/// `CachedValue` exists internally for the purpose of creating a reference value, preventing the need
|
||||
/// to create a `JSONDecoder` and invoke a decode step every time we need to access a `StoredValue` externally.
|
||||
internal final class CachedValue<Item: Codable> {
|
||||
private var cachedValue: Item?
|
||||
public var retrieveValue: () -> Item
|
||||
|
||||
init(retrieveValue: @escaping () -> Item) {
|
||||
self.retrieveValue = retrieveValue
|
||||
self.cachedValue = self.retrieveValue()
|
||||
}
|
||||
|
||||
func set(_ value: Item) {
|
||||
self.cachedValue = value
|
||||
}
|
||||
|
||||
var wrappedValue: Item? {
|
||||
if let cachedValue {
|
||||
cachedValue
|
||||
} else {
|
||||
self.retrieveValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,12 @@ public struct SecurelyStoredValue<Item: Codable> {
|
||||
if self.wrappedValue == nil {
|
||||
try self.insert(value)
|
||||
} else {
|
||||
try self.update(value)
|
||||
// This call to `remove` is a temporary workaround for broken functionality when trying to update a value.
|
||||
// Since updating a value does not seem to work, I've rewritten `set` to first set a `nil` value
|
||||
// then the desired value, which will effectively call `set` with a new value, which does work.
|
||||
// This will be fixed in the future, and we will restore the call-site to say `self.update(value)`.
|
||||
try self.remove()
|
||||
try self.insert(value)
|
||||
}
|
||||
} else {
|
||||
try self.remove()
|
||||
|
||||
@@ -14,7 +14,6 @@ public extension StoredValue {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public extension SecurelyStoredValue {
|
||||
/// A convenient way to create a `Binding` from a `SecurelyStoredValue`.
|
||||
///
|
||||
|
||||
@@ -45,6 +45,8 @@ public struct StoredValue<Item: Codable> {
|
||||
private let userDefaults: UserDefaults
|
||||
private let itemSubject: CurrentValueSubject<Item, Never>
|
||||
|
||||
private var cachedValue: CachedValue<Item>
|
||||
|
||||
public init(wrappedValue: Item, key: String, storage userDefaults: UserDefaults = UserDefaults.standard) {
|
||||
self.key = key
|
||||
self.defaultValue = wrappedValue
|
||||
@@ -52,11 +54,15 @@ public struct StoredValue<Item: Codable> {
|
||||
|
||||
let initialValue = Self.storedValue(forKey: key, userDefaults: userDefaults, defaultValue: defaultValue)
|
||||
self.itemSubject = CurrentValueSubject(initialValue)
|
||||
|
||||
self.cachedValue = CachedValue(retrieveValue: {
|
||||
Self.storedValue(forKey: key, userDefaults: userDefaults, defaultValue: initialValue)
|
||||
})
|
||||
}
|
||||
|
||||
/// The currently stored value
|
||||
public var wrappedValue: Item {
|
||||
Self.storedValue(forKey: self.key, userDefaults: self.userDefaults, defaultValue: self.defaultValue)
|
||||
self.cachedValue.retrieveValue()
|
||||
}
|
||||
|
||||
/// A ``StoredValue`` which exposes ``set(_:)`` and ``reset()`` functions alongside a ``publisher``.
|
||||
@@ -94,6 +100,7 @@ public struct StoredValue<Item: Codable> {
|
||||
let boxedValue = BoxedValue(value: value)
|
||||
if let data = try? JSONCoders.encoder.encode(boxedValue) {
|
||||
self.userDefaults.set(data, forKey: self.key)
|
||||
self.cachedValue.set(value)
|
||||
self.itemSubject.send(value)
|
||||
}
|
||||
}
|
||||
@@ -123,6 +130,7 @@ public struct StoredValue<Item: Codable> {
|
||||
let boxedValue = BoxedValue(value: self.defaultValue)
|
||||
if let data = try? JSONCoders.encoder.encode(boxedValue) {
|
||||
self.userDefaults.set(data, forKey: self.key)
|
||||
self.cachedValue.set(self.defaultValue)
|
||||
self.itemSubject.send(self.defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,80 +20,80 @@ final class AsyncStoreTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testInsertingItem() async throws {
|
||||
try await asyncStore.insert(BoutiqueItem.coat)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
try await asyncStore.insert(.coat)
|
||||
XCTAssertTrue(asyncStore.items.contains(.coat))
|
||||
|
||||
try await asyncStore.insert(BoutiqueItem.belt)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
try await asyncStore.insert(.belt)
|
||||
XCTAssertTrue(asyncStore.items.contains(.belt))
|
||||
XCTAssertEqual(asyncStore.items.count, 2)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testInsertingItems() async throws {
|
||||
try await asyncStore.insert([BoutiqueItem.coat, BoutiqueItem.sweater, BoutiqueItem.sweater, BoutiqueItem.purse])
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
try await asyncStore.insert([.coat, .sweater, .sweater, .purse])
|
||||
XCTAssertTrue(asyncStore.items.contains(.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(.purse))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testInsertingDuplicateItems() async throws {
|
||||
XCTAssertTrue(asyncStore.items.isEmpty)
|
||||
try await asyncStore.insert(BoutiqueItem.allItems)
|
||||
try await asyncStore.insert(.allItems)
|
||||
XCTAssertEqual(asyncStore.items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testReadingItems() async throws {
|
||||
try await asyncStore.insert(BoutiqueItem.allItems)
|
||||
try await asyncStore.insert(.allItems)
|
||||
|
||||
XCTAssertEqual(asyncStore.items[0], BoutiqueItem.coat)
|
||||
XCTAssertEqual(asyncStore.items[1], BoutiqueItem.sweater)
|
||||
XCTAssertEqual(asyncStore.items[2], BoutiqueItem.purse)
|
||||
XCTAssertEqual(asyncStore.items[3], BoutiqueItem.belt)
|
||||
XCTAssertEqual(asyncStore.items[0], .coat)
|
||||
XCTAssertEqual(asyncStore.items[1], .sweater)
|
||||
XCTAssertEqual(asyncStore.items[2], .purse)
|
||||
XCTAssertEqual(asyncStore.items[3], .belt)
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testReadingPersistedItems() async throws {
|
||||
try await asyncStore.insert(BoutiqueItem.allItems)
|
||||
try await asyncStore.insert(.allItems)
|
||||
|
||||
// The new store has to fetch items from disk.
|
||||
let newStore = try await Store<BoutiqueItem>(
|
||||
storage: SQLiteStorageEngine.default(appendingPath: "Tests"),
|
||||
cacheIdentifier: \.merchantID)
|
||||
|
||||
XCTAssertEqual(newStore.items[0], BoutiqueItem.coat)
|
||||
XCTAssertEqual(newStore.items[1], BoutiqueItem.sweater)
|
||||
XCTAssertEqual(newStore.items[2], BoutiqueItem.purse)
|
||||
XCTAssertEqual(newStore.items[3], BoutiqueItem.belt)
|
||||
XCTAssertEqual(newStore.items[0], .coat)
|
||||
XCTAssertEqual(newStore.items[1], .sweater)
|
||||
XCTAssertEqual(newStore.items[2], .purse)
|
||||
XCTAssertEqual(newStore.items[3], .belt)
|
||||
|
||||
XCTAssertEqual(newStore.items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testRemovingItems() async throws {
|
||||
try await asyncStore.insert(BoutiqueItem.allItems)
|
||||
try await asyncStore.remove(BoutiqueItem.coat)
|
||||
try await asyncStore.insert(.allItems)
|
||||
try await asyncStore.remove(.coat)
|
||||
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(asyncStore.items.contains(.coat))
|
||||
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(asyncStore.items.contains(.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(.purse))
|
||||
|
||||
try await asyncStore.remove([BoutiqueItem.sweater, BoutiqueItem.purse])
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
try await asyncStore.remove([.sweater, .purse])
|
||||
XCTAssertFalse(asyncStore.items.contains(.sweater))
|
||||
XCTAssertFalse(asyncStore.items.contains(.purse))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testRemoveAll() async throws {
|
||||
try await asyncStore.insert(BoutiqueItem.coat)
|
||||
try await asyncStore.insert(.coat)
|
||||
XCTAssertEqual(asyncStore.items.count, 1)
|
||||
try await asyncStore.removeAll()
|
||||
|
||||
try await asyncStore.insert(BoutiqueItem.uniqueItems)
|
||||
try await asyncStore.insert(.uniqueItems)
|
||||
XCTAssertEqual(asyncStore.items.count, 4)
|
||||
try await asyncStore.removeAll()
|
||||
XCTAssertTrue(asyncStore.items.isEmpty)
|
||||
@@ -101,112 +101,112 @@ final class AsyncStoreTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testChainingInsertOperations() async throws {
|
||||
try await asyncStore.insert(BoutiqueItem.uniqueItems)
|
||||
try await asyncStore.insert(.uniqueItems)
|
||||
|
||||
try await asyncStore
|
||||
.remove(BoutiqueItem.coat)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.remove(.coat)
|
||||
.insert(.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 3)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(.purse))
|
||||
XCTAssertTrue(asyncStore.items.contains(.belt))
|
||||
XCTAssertFalse(asyncStore.items.contains(.coat))
|
||||
|
||||
try await asyncStore.removeAll()
|
||||
|
||||
try await asyncStore
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.coat)
|
||||
.remove([BoutiqueItem.belt])
|
||||
.insert(BoutiqueItem.sweater)
|
||||
.insert(.belt)
|
||||
.insert(.coat)
|
||||
.remove([.belt])
|
||||
.insert(.sweater)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 2)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(asyncStore.items.contains(.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(.sweater))
|
||||
XCTAssertFalse(asyncStore.items.contains(.belt))
|
||||
|
||||
try await asyncStore
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert(BoutiqueItem.purse)
|
||||
.remove([BoutiqueItem.belt, .coat])
|
||||
.insert(BoutiqueItem.sweater)
|
||||
.insert(.belt)
|
||||
.insert(.coat)
|
||||
.insert(.purse)
|
||||
.remove([.belt, .coat])
|
||||
.insert(.sweater)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 2)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(asyncStore.items.contains(.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(.purse))
|
||||
XCTAssertFalse(asyncStore.items.contains(.coat))
|
||||
XCTAssertFalse(asyncStore.items.contains(.belt))
|
||||
|
||||
try await asyncStore.removeAll()
|
||||
|
||||
try await asyncStore
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert([BoutiqueItem.purse, BoutiqueItem.belt])
|
||||
.insert(.coat)
|
||||
.insert([.purse, .belt])
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 3)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(.purse))
|
||||
XCTAssertTrue(asyncStore.items.contains(.belt))
|
||||
XCTAssertTrue(asyncStore.items.contains(.coat))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testChainingRemoveOperations() async throws {
|
||||
try await asyncStore
|
||||
.insert(BoutiqueItem.uniqueItems)
|
||||
.remove(BoutiqueItem.belt)
|
||||
.remove(BoutiqueItem.purse)
|
||||
.insert(.uniqueItems)
|
||||
.remove(.belt)
|
||||
.remove(.purse)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 2)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(asyncStore.items.contains(.sweater))
|
||||
XCTAssertTrue(asyncStore.items.contains(.coat))
|
||||
|
||||
try await asyncStore.insert(BoutiqueItem.uniqueItems)
|
||||
try await asyncStore.insert(.uniqueItems)
|
||||
XCTAssertEqual(asyncStore.items.count, 4)
|
||||
|
||||
try await asyncStore
|
||||
.remove([BoutiqueItem.sweater, BoutiqueItem.coat])
|
||||
.remove(BoutiqueItem.belt)
|
||||
.remove([.sweater, .coat])
|
||||
.remove(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 1)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(asyncStore.items.contains(.purse))
|
||||
|
||||
try await asyncStore
|
||||
.removeAll()
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 1)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(asyncStore.items.contains(.belt))
|
||||
|
||||
try await asyncStore
|
||||
.removeAll()
|
||||
.remove(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.remove(.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 1)
|
||||
XCTAssertTrue(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(asyncStore.items.contains(.belt))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testChainingOperationsDontExecuteUnlessRun() async throws {
|
||||
let operation = try await asyncStore
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert([BoutiqueItem.purse, BoutiqueItem.belt])
|
||||
.insert(.coat)
|
||||
.insert([.purse, .belt])
|
||||
|
||||
XCTAssertEqual(asyncStore.items.count, 0)
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertFalse(asyncStore.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(asyncStore.items.contains(.purse))
|
||||
XCTAssertFalse(asyncStore.items.contains(.belt))
|
||||
XCTAssertFalse(asyncStore.items.contains(.coat))
|
||||
|
||||
// Adding this line to get rid of the error about
|
||||
// `operation` being unused, given that's the point of the test.
|
||||
@@ -215,7 +215,7 @@ final class AsyncStoreTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testPublishedItemsSubscription() async throws {
|
||||
let uniqueItems = BoutiqueItem.uniqueItems
|
||||
let uniqueItems = [BoutiqueItem].uniqueItems
|
||||
let expectation = XCTestExpectation(description: "uniqueItems is published and read")
|
||||
|
||||
asyncStore.$items
|
||||
|
||||
@@ -30,13 +30,13 @@ final class AsyncStoredValueTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStorageEngineBackedStoredValue() async throws {
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.coat)
|
||||
XCTAssertEqual(self.storedItem, .coat)
|
||||
|
||||
try await self.$storedItem.set(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.belt)
|
||||
try await self.$storedItem.set(.belt)
|
||||
XCTAssertEqual(self.storedItem, .belt)
|
||||
|
||||
try await self.$storedItem.reset()
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.coat)
|
||||
XCTAssertEqual(self.storedItem, .coat)
|
||||
}
|
||||
|
||||
func testBoolAsyncStoredValue() async throws {
|
||||
@@ -81,11 +81,11 @@ final class AsyncStoredValueTests: XCTestCase {
|
||||
func testStoredArrayValueAppend() async throws {
|
||||
XCTAssertEqual(self.storedArrayValue, [])
|
||||
|
||||
try await self.$storedArrayValue.append(BoutiqueItem.sweater)
|
||||
XCTAssertEqual(self.storedArrayValue, [BoutiqueItem.sweater])
|
||||
try await self.$storedArrayValue.append(.sweater)
|
||||
XCTAssertEqual(self.storedArrayValue, [.sweater])
|
||||
|
||||
try await self.$storedArrayValue.append(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedArrayValue, [BoutiqueItem.sweater, BoutiqueItem.belt])
|
||||
try await self.$storedArrayValue.append(.belt)
|
||||
XCTAssertEqual(self.storedArrayValue, [.sweater, .belt])
|
||||
}
|
||||
|
||||
func testStoredArrayValueTogglePresence() async throws {
|
||||
@@ -106,10 +106,10 @@ final class AsyncStoredValueTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStoredBinding() async throws {
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(BoutiqueItem.sweater).wrappedValue)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(.sweater).wrappedValue)
|
||||
|
||||
try await self.$storedBinding.set(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(BoutiqueItem.belt).wrappedValue)
|
||||
try await self.$storedBinding.set(.belt)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(.belt).wrappedValue)
|
||||
}
|
||||
|
||||
func testStoredValuePublishedSubscription() async throws {
|
||||
@@ -122,15 +122,15 @@ final class AsyncStoredValueTests: XCTestCase {
|
||||
values.append(item)
|
||||
|
||||
if values.count == 4 {
|
||||
XCTAssertEqual(values, [BoutiqueItem.coat, .sweater, .purse, .belt])
|
||||
XCTAssertEqual(values, [.coat, .sweater, .purse, .belt])
|
||||
expectation.fulfill()
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
try await self.$storedItem.set(BoutiqueItem.sweater)
|
||||
try await self.$storedItem.set(BoutiqueItem.purse)
|
||||
try await self.$storedItem.set(BoutiqueItem.belt)
|
||||
try await self.$storedItem.set(.sweater)
|
||||
try await self.$storedItem.set(.purse)
|
||||
try await self.$storedItem.set(.belt)
|
||||
|
||||
wait(for: [expectation], timeout: 1)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,9 @@ extension BoutiqueItem {
|
||||
merchantID: "4",
|
||||
value: "Belt"
|
||||
)
|
||||
}
|
||||
|
||||
extension [BoutiqueItem] {
|
||||
static let allItems = [
|
||||
BoutiqueItem.coat,
|
||||
BoutiqueItem.sweater,
|
||||
|
||||
@@ -136,11 +136,11 @@ final class SecurelyStoredValueTests: XCTestCase {
|
||||
func testStoredArrayValueAppend() async throws {
|
||||
XCTAssertEqual(self.storedArray, nil)
|
||||
|
||||
try await self.$storedArray.append(BoutiqueItem.sweater)
|
||||
XCTAssertEqual(self.storedArray, [BoutiqueItem.sweater])
|
||||
try await self.$storedArray.append(.sweater)
|
||||
XCTAssertEqual(self.storedArray, [.sweater])
|
||||
|
||||
try await self.$storedArray.append(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedArray, [BoutiqueItem.sweater, BoutiqueItem.belt])
|
||||
try await self.$storedArray.append(.belt)
|
||||
XCTAssertEqual(self.storedArray, [.sweater, .belt])
|
||||
}
|
||||
|
||||
@MainActor
|
||||
@@ -150,8 +150,8 @@ final class SecurelyStoredValueTests: XCTestCase {
|
||||
// Using wrappedValue for our tests to work around the fact that Binding doesn't conform to Equatable
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, nil)
|
||||
|
||||
try self.$storedBinding.set(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(BoutiqueItem.belt).wrappedValue)
|
||||
try self.$storedBinding.set(.belt)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(.belt).wrappedValue)
|
||||
}
|
||||
|
||||
func testPublishedValueSubscription() async throws {
|
||||
@@ -166,16 +166,16 @@ final class SecurelyStoredValueTests: XCTestCase {
|
||||
}
|
||||
|
||||
if values.count == 4 {
|
||||
XCTAssertEqual(values, [BoutiqueItem.coat, .purse, .sweater, .belt])
|
||||
XCTAssertEqual(values, [.coat, .purse, .sweater, .belt])
|
||||
expectation.fulfill()
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
try await self.$storedItem.set(BoutiqueItem.coat)
|
||||
try await self.$storedItem.set(BoutiqueItem.purse)
|
||||
try await self.$storedItem.set(BoutiqueItem.sweater)
|
||||
try await self.$storedItem.set(BoutiqueItem.belt)
|
||||
try await self.$storedItem.set(.coat)
|
||||
try await self.$storedItem.set(.purse)
|
||||
try await self.$storedItem.set(.sweater)
|
||||
try await self.$storedItem.set(.belt)
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1)
|
||||
}
|
||||
|
||||
@@ -26,80 +26,80 @@ final class StoreTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testInsertingItem() async throws {
|
||||
try await store.insert(BoutiqueItem.coat)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.coat))
|
||||
try await store.insert(.coat)
|
||||
XCTAssertTrue(store.items.contains(.coat))
|
||||
|
||||
try await store.insert(BoutiqueItem.belt)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.belt))
|
||||
try await store.insert(.belt)
|
||||
XCTAssertTrue(store.items.contains(.belt))
|
||||
XCTAssertEqual(store.items.count, 2)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testInsertingItems() async throws {
|
||||
try await store.insert([BoutiqueItem.coat, BoutiqueItem.sweater, BoutiqueItem.sweater, BoutiqueItem.purse])
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.purse))
|
||||
try await store.insert([.coat, .sweater, .sweater, .purse])
|
||||
XCTAssertTrue(store.items.contains(.coat))
|
||||
XCTAssertTrue(store.items.contains(.sweater))
|
||||
XCTAssertTrue(store.items.contains(.purse))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testInsertingDuplicateItems() async throws {
|
||||
XCTAssertTrue(store.items.isEmpty)
|
||||
try await store.insert(BoutiqueItem.allItems)
|
||||
try await store.insert(.allItems)
|
||||
XCTAssertEqual(store.items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testReadingItems() async throws {
|
||||
try await store.insert(BoutiqueItem.allItems)
|
||||
try await store.insert(.allItems)
|
||||
|
||||
XCTAssertEqual(store.items[0], BoutiqueItem.coat)
|
||||
XCTAssertEqual(store.items[1], BoutiqueItem.sweater)
|
||||
XCTAssertEqual(store.items[2], BoutiqueItem.purse)
|
||||
XCTAssertEqual(store.items[3], BoutiqueItem.belt)
|
||||
XCTAssertEqual(store.items[0], .coat)
|
||||
XCTAssertEqual(store.items[1], .sweater)
|
||||
XCTAssertEqual(store.items[2], .purse)
|
||||
XCTAssertEqual(store.items[3], .belt)
|
||||
|
||||
XCTAssertEqual(store.items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testReadingPersistedItems() async throws {
|
||||
try await store.insert(BoutiqueItem.allItems)
|
||||
try await store.insert(.allItems)
|
||||
|
||||
// The new store has to fetch items from disk.
|
||||
let newStore = try await Store<BoutiqueItem>(
|
||||
storage: SQLiteStorageEngine.default(appendingPath: "Tests"),
|
||||
cacheIdentifier: \.merchantID)
|
||||
|
||||
XCTAssertEqual(newStore.items[0], BoutiqueItem.coat)
|
||||
XCTAssertEqual(newStore.items[1], BoutiqueItem.sweater)
|
||||
XCTAssertEqual(newStore.items[2], BoutiqueItem.purse)
|
||||
XCTAssertEqual(newStore.items[3], BoutiqueItem.belt)
|
||||
XCTAssertEqual(newStore.items[0], .coat)
|
||||
XCTAssertEqual(newStore.items[1], .sweater)
|
||||
XCTAssertEqual(newStore.items[2], .purse)
|
||||
XCTAssertEqual(newStore.items[3], .belt)
|
||||
|
||||
XCTAssertEqual(newStore.items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testRemovingItems() async throws {
|
||||
try await store.insert(BoutiqueItem.allItems)
|
||||
try await store.remove(BoutiqueItem.coat)
|
||||
try await store.insert(.allItems)
|
||||
try await store.remove(.coat)
|
||||
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(store.items.contains(.coat))
|
||||
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(store.items.contains(.sweater))
|
||||
XCTAssertTrue(store.items.contains(.purse))
|
||||
|
||||
try await store.remove([BoutiqueItem.sweater, BoutiqueItem.purse])
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.purse))
|
||||
try await store.remove([.sweater, .purse])
|
||||
XCTAssertFalse(store.items.contains(.sweater))
|
||||
XCTAssertFalse(store.items.contains(.purse))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testRemoveAll() async throws {
|
||||
try await store.insert(BoutiqueItem.coat)
|
||||
try await store.insert(.coat)
|
||||
XCTAssertEqual(store.items.count, 1)
|
||||
try await store.removeAll()
|
||||
|
||||
try await store.insert(BoutiqueItem.uniqueItems)
|
||||
try await store.insert(.uniqueItems)
|
||||
XCTAssertEqual(store.items.count, 4)
|
||||
try await store.removeAll()
|
||||
XCTAssertTrue(store.items.isEmpty)
|
||||
@@ -107,112 +107,112 @@ final class StoreTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testChainingInsertOperations() async throws {
|
||||
try await store.insert(BoutiqueItem.uniqueItems)
|
||||
try await store.insert(.uniqueItems)
|
||||
|
||||
try await store
|
||||
.remove(BoutiqueItem.coat)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.remove(.coat)
|
||||
.insert(.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 3)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(store.items.contains(.sweater))
|
||||
XCTAssertTrue(store.items.contains(.purse))
|
||||
XCTAssertTrue(store.items.contains(.belt))
|
||||
XCTAssertFalse(store.items.contains(.coat))
|
||||
|
||||
try await store.removeAll()
|
||||
|
||||
try await store
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.coat)
|
||||
.remove([BoutiqueItem.belt])
|
||||
.insert(BoutiqueItem.sweater)
|
||||
.insert(.belt)
|
||||
.insert(.coat)
|
||||
.remove([.belt])
|
||||
.insert(.sweater)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 2)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(store.items.contains(.coat))
|
||||
XCTAssertTrue(store.items.contains(.sweater))
|
||||
XCTAssertFalse(store.items.contains(.belt))
|
||||
|
||||
try await store
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert(BoutiqueItem.purse)
|
||||
.remove([BoutiqueItem.belt, .coat])
|
||||
.insert(BoutiqueItem.sweater)
|
||||
.insert(.belt)
|
||||
.insert(.coat)
|
||||
.insert(.purse)
|
||||
.remove([.belt, .coat])
|
||||
.insert(.sweater)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 2)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(store.items.contains(.sweater))
|
||||
XCTAssertTrue(store.items.contains(.purse))
|
||||
XCTAssertFalse(store.items.contains(.coat))
|
||||
XCTAssertFalse(store.items.contains(.belt))
|
||||
|
||||
try await store.removeAll()
|
||||
|
||||
try await store
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert([BoutiqueItem.purse, BoutiqueItem.belt])
|
||||
.insert(.coat)
|
||||
.insert([.purse, .belt])
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 3)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(store.items.contains(.purse))
|
||||
XCTAssertTrue(store.items.contains(.belt))
|
||||
XCTAssertTrue(store.items.contains(.coat))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testChainingRemoveOperations() async throws {
|
||||
try await store
|
||||
.insert(BoutiqueItem.uniqueItems)
|
||||
.remove(BoutiqueItem.belt)
|
||||
.remove(BoutiqueItem.purse)
|
||||
.insert(.uniqueItems)
|
||||
.remove(.belt)
|
||||
.remove(.purse)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 2)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(store.items.contains(.sweater))
|
||||
XCTAssertTrue(store.items.contains(.coat))
|
||||
|
||||
try await store.insert(BoutiqueItem.uniqueItems)
|
||||
try await store.insert(.uniqueItems)
|
||||
XCTAssertEqual(store.items.count, 4)
|
||||
|
||||
try await store
|
||||
.remove([BoutiqueItem.sweater, BoutiqueItem.coat])
|
||||
.remove(BoutiqueItem.belt)
|
||||
.remove([.sweater, .coat])
|
||||
.remove(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 1)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(store.items.contains(.purse))
|
||||
|
||||
try await store
|
||||
.removeAll()
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 1)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(store.items.contains(.belt))
|
||||
|
||||
try await store
|
||||
.removeAll()
|
||||
.remove(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.remove(.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(store.items.count, 1)
|
||||
XCTAssertTrue(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(store.items.contains(.belt))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testChainingOperationsDontExecuteUnlessRun() async throws {
|
||||
let operation = try await store
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert([BoutiqueItem.purse, BoutiqueItem.belt])
|
||||
.insert(.coat)
|
||||
.insert([.purse, .belt])
|
||||
|
||||
XCTAssertEqual(store.items.count, 0)
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.purse))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.belt))
|
||||
XCTAssertFalse(store.items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(store.items.contains(.purse))
|
||||
XCTAssertFalse(store.items.contains(.belt))
|
||||
XCTAssertFalse(store.items.contains(.coat))
|
||||
|
||||
// Adding this line to get rid of the error about
|
||||
// `operation` being unused, given that's the point of the test.
|
||||
@@ -221,7 +221,7 @@ final class StoreTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testPublishedItemsSubscription() async throws {
|
||||
let uniqueItems = BoutiqueItem.uniqueItems
|
||||
let uniqueItems = [BoutiqueItem].uniqueItems
|
||||
let expectation = XCTestExpectation(description: "uniqueItems is published and read")
|
||||
|
||||
store.$items
|
||||
|
||||
@@ -23,44 +23,44 @@ final class StoredTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testInsertingItem() async throws {
|
||||
try await $items.insert(BoutiqueItem.coat)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.coat))
|
||||
try await $items.insert(.coat)
|
||||
XCTAssertTrue(items.contains(.coat))
|
||||
|
||||
try await $items.insert(BoutiqueItem.belt)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.belt))
|
||||
try await $items.insert(.belt)
|
||||
XCTAssertTrue(items.contains(.belt))
|
||||
XCTAssertEqual(items.count, 2)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testInsertingItems() async throws {
|
||||
try await $items.insert([BoutiqueItem.coat, BoutiqueItem.sweater, BoutiqueItem.sweater, BoutiqueItem.purse])
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.purse))
|
||||
try await $items.insert([.coat, .sweater, .sweater, .purse])
|
||||
XCTAssertTrue(items.contains(.coat))
|
||||
XCTAssertTrue(items.contains(.sweater))
|
||||
XCTAssertTrue(items.contains(.purse))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testInsertingDuplicateItems() async throws {
|
||||
XCTAssertTrue(items.isEmpty)
|
||||
try await $items.insert(BoutiqueItem.allItems)
|
||||
try await $items.insert(.allItems)
|
||||
XCTAssertEqual(items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testReadingItems() async throws {
|
||||
try await $items.insert(BoutiqueItem.allItems)
|
||||
try await $items.insert(.allItems)
|
||||
|
||||
XCTAssertEqual(items[0], BoutiqueItem.coat)
|
||||
XCTAssertEqual(items[1], BoutiqueItem.sweater)
|
||||
XCTAssertEqual(items[2], BoutiqueItem.purse)
|
||||
XCTAssertEqual(items[3], BoutiqueItem.belt)
|
||||
XCTAssertEqual(items[0], .coat)
|
||||
XCTAssertEqual(items[1], .sweater)
|
||||
XCTAssertEqual(items[2], .purse)
|
||||
XCTAssertEqual(items[3], .belt)
|
||||
|
||||
XCTAssertEqual(items.count, 4)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testReadingPersistedItems() async throws {
|
||||
try await $items.insert(BoutiqueItem.allItems)
|
||||
try await $items.insert(.allItems)
|
||||
|
||||
// The new store has to fetch items from disk.
|
||||
let newStore = try await Store<BoutiqueItem>(
|
||||
@@ -70,34 +70,34 @@ final class StoredTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(newStore.items.count, 4)
|
||||
|
||||
XCTAssertEqual(newStore.items[0], BoutiqueItem.coat)
|
||||
XCTAssertEqual(newStore.items[1], BoutiqueItem.sweater)
|
||||
XCTAssertEqual(newStore.items[2], BoutiqueItem.purse)
|
||||
XCTAssertEqual(newStore.items[3], BoutiqueItem.belt)
|
||||
XCTAssertEqual(newStore.items[0], .coat)
|
||||
XCTAssertEqual(newStore.items[1], .sweater)
|
||||
XCTAssertEqual(newStore.items[2], .purse)
|
||||
XCTAssertEqual(newStore.items[3], .belt)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testRemovingItems() async throws {
|
||||
try await $items.insert(BoutiqueItem.allItems)
|
||||
try await $items.remove(BoutiqueItem.coat)
|
||||
try await $items.insert(.allItems)
|
||||
try await $items.remove(.coat)
|
||||
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(items.contains(.coat))
|
||||
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(items.contains(.sweater))
|
||||
XCTAssertTrue(items.contains(.purse))
|
||||
|
||||
try await $items.remove([BoutiqueItem.sweater, BoutiqueItem.purse])
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.purse))
|
||||
try await $items.remove([.sweater, .purse])
|
||||
XCTAssertFalse(items.contains(.sweater))
|
||||
XCTAssertFalse(items.contains(.purse))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testRemoveAll() async throws {
|
||||
try await $items.insert(BoutiqueItem.coat)
|
||||
try await $items.insert(.coat)
|
||||
XCTAssertEqual(items.count, 1)
|
||||
try await $items.removeAll()
|
||||
|
||||
try await $items.insert(BoutiqueItem.uniqueItems)
|
||||
try await $items.insert(.uniqueItems)
|
||||
XCTAssertEqual(items.count, 4)
|
||||
try await $items.removeAll()
|
||||
XCTAssertTrue(items.isEmpty)
|
||||
@@ -105,112 +105,112 @@ final class StoredTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testChainingInsertOperations() async throws {
|
||||
try await $items.insert(BoutiqueItem.uniqueItems)
|
||||
try await $items.insert(.uniqueItems)
|
||||
|
||||
try await $items
|
||||
.remove(BoutiqueItem.coat)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.remove(.coat)
|
||||
.insert(.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 3)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(items.contains(.sweater))
|
||||
XCTAssertTrue(items.contains(.purse))
|
||||
XCTAssertTrue(items.contains(.belt))
|
||||
XCTAssertFalse(items.contains(.coat))
|
||||
|
||||
try await $items.removeAll()
|
||||
|
||||
try await $items
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.coat)
|
||||
.remove([BoutiqueItem.belt])
|
||||
.insert(BoutiqueItem.sweater)
|
||||
.insert(.belt)
|
||||
.insert(.coat)
|
||||
.remove([.belt])
|
||||
.insert(.sweater)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 2)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(items.contains(.coat))
|
||||
XCTAssertTrue(items.contains(.sweater))
|
||||
XCTAssertFalse(items.contains(.belt))
|
||||
|
||||
try await $items
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert(BoutiqueItem.purse)
|
||||
.remove([BoutiqueItem.belt, .coat])
|
||||
.insert(BoutiqueItem.sweater)
|
||||
.insert(.belt)
|
||||
.insert(.coat)
|
||||
.insert(.purse)
|
||||
.remove([.belt, .coat])
|
||||
.insert(.sweater)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 2)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.purse))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(items.contains(.sweater))
|
||||
XCTAssertTrue(items.contains(.purse))
|
||||
XCTAssertFalse(items.contains(.coat))
|
||||
XCTAssertFalse(items.contains(.belt))
|
||||
|
||||
try await $items.removeAll()
|
||||
|
||||
try await $items
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert([BoutiqueItem.purse, BoutiqueItem.belt])
|
||||
.insert(.coat)
|
||||
.insert([.purse, .belt])
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 3)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(items.contains(.purse))
|
||||
XCTAssertTrue(items.contains(.belt))
|
||||
XCTAssertTrue(items.contains(.coat))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testChainingRemoveOperations() async throws {
|
||||
try await $items
|
||||
.insert(BoutiqueItem.uniqueItems)
|
||||
.remove(BoutiqueItem.belt)
|
||||
.remove(BoutiqueItem.purse)
|
||||
.insert(.uniqueItems)
|
||||
.remove(.belt)
|
||||
.remove(.purse)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 2)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.sweater))
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertTrue(items.contains(.sweater))
|
||||
XCTAssertTrue(items.contains(.coat))
|
||||
|
||||
try await $items.insert(BoutiqueItem.uniqueItems)
|
||||
try await $items.insert(.uniqueItems)
|
||||
XCTAssertEqual(items.count, 4)
|
||||
|
||||
try await $items
|
||||
.remove([BoutiqueItem.sweater, BoutiqueItem.coat])
|
||||
.remove(BoutiqueItem.belt)
|
||||
.remove([.sweater, .coat])
|
||||
.remove(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 1)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.purse))
|
||||
XCTAssertTrue(items.contains(.purse))
|
||||
|
||||
try await $items
|
||||
.removeAll()
|
||||
.insert(BoutiqueItem.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 1)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(items.contains(.belt))
|
||||
|
||||
try await $items
|
||||
.removeAll()
|
||||
.remove(BoutiqueItem.belt)
|
||||
.insert(BoutiqueItem.belt)
|
||||
.remove(.belt)
|
||||
.insert(.belt)
|
||||
.run()
|
||||
|
||||
XCTAssertEqual(items.count, 1)
|
||||
XCTAssertTrue(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertTrue(items.contains(.belt))
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testChainingOperationsDontExecuteUnlessRun() async throws {
|
||||
let operation = try await $items
|
||||
.insert(BoutiqueItem.coat)
|
||||
.insert([BoutiqueItem.purse, BoutiqueItem.belt])
|
||||
.insert(.coat)
|
||||
.insert([.purse, .belt])
|
||||
|
||||
XCTAssertEqual(items.count, 0)
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.purse))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.belt))
|
||||
XCTAssertFalse(items.contains(BoutiqueItem.coat))
|
||||
XCTAssertFalse(items.contains(.purse))
|
||||
XCTAssertFalse(items.contains(.belt))
|
||||
XCTAssertFalse(items.contains(.coat))
|
||||
|
||||
// Adding this line to get rid of the error about
|
||||
// `operation` being unused, given that's the point of the test.
|
||||
@@ -219,7 +219,7 @@ final class StoredTests: XCTestCase {
|
||||
|
||||
@MainActor
|
||||
func testPublishedItemsSubscription() async throws {
|
||||
let uniqueItems = BoutiqueItem.uniqueItems
|
||||
let uniqueItems = [BoutiqueItem].uniqueItems
|
||||
let expectation = XCTestExpectation(description: "uniqueItems is published and read")
|
||||
|
||||
$items.$items
|
||||
|
||||
@@ -35,43 +35,43 @@ final class StoredValueTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStoredValueOperations() async throws {
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.coat)
|
||||
XCTAssertEqual(self.storedItem, .coat)
|
||||
|
||||
await self.$storedItem.set(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.belt)
|
||||
await self.$storedItem.set(.belt)
|
||||
XCTAssertEqual(self.storedItem, .belt)
|
||||
|
||||
await self.$storedItem.reset()
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.coat)
|
||||
XCTAssertEqual(self.storedItem, .coat)
|
||||
|
||||
await self.$storedItem.set(BoutiqueItem.sweater)
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.sweater)
|
||||
await self.$storedItem.set(.sweater)
|
||||
XCTAssertEqual(self.storedItem, .sweater)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func testStoredValueOnMainActorOperations() async throws {
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.coat)
|
||||
XCTAssertEqual(self.storedItem, .coat)
|
||||
|
||||
self.$storedItem.set(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.belt)
|
||||
self.$storedItem.set(.belt)
|
||||
XCTAssertEqual(self.storedItem, .belt)
|
||||
|
||||
self.$storedItem.reset()
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.coat)
|
||||
XCTAssertEqual(self.storedItem, .coat)
|
||||
|
||||
self.$storedItem.set(BoutiqueItem.sweater)
|
||||
XCTAssertEqual(self.storedItem, BoutiqueItem.sweater)
|
||||
self.$storedItem.set(.sweater)
|
||||
XCTAssertEqual(self.storedItem, .sweater)
|
||||
}
|
||||
|
||||
func testStoredNilValue() async throws {
|
||||
XCTAssertEqual(self.storedNilValue, nil)
|
||||
|
||||
await self.$storedNilValue.set(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedNilValue, BoutiqueItem.belt)
|
||||
await self.$storedNilValue.set(.belt)
|
||||
XCTAssertEqual(self.storedNilValue, .belt)
|
||||
|
||||
await self.$storedNilValue.reset()
|
||||
XCTAssertEqual(self.storedNilValue, nil)
|
||||
|
||||
await self.$storedNilValue.set(BoutiqueItem.sweater)
|
||||
XCTAssertEqual(self.storedNilValue, BoutiqueItem.sweater)
|
||||
await self.$storedNilValue.set(.sweater)
|
||||
XCTAssertEqual(self.storedNilValue, .sweater)
|
||||
}
|
||||
|
||||
func testStoredBoolValueToggle() async throws {
|
||||
@@ -103,11 +103,11 @@ final class StoredValueTests: XCTestCase {
|
||||
func testStoredArrayValueAppend() async throws {
|
||||
XCTAssertEqual(self.storedArrayValue, [])
|
||||
|
||||
await self.$storedArrayValue.append(BoutiqueItem.sweater)
|
||||
XCTAssertEqual(self.storedArrayValue, [BoutiqueItem.sweater])
|
||||
await self.$storedArrayValue.append(.sweater)
|
||||
XCTAssertEqual(self.storedArrayValue, [.sweater])
|
||||
|
||||
await self.$storedArrayValue.append(BoutiqueItem.belt)
|
||||
XCTAssertEqual(self.storedArrayValue, [BoutiqueItem.sweater, BoutiqueItem.belt])
|
||||
await self.$storedArrayValue.append(.belt)
|
||||
XCTAssertEqual(self.storedArrayValue, [.sweater, .belt])
|
||||
}
|
||||
|
||||
func testStoredArrayValueTogglePresence() async throws {
|
||||
@@ -130,11 +130,11 @@ final class StoredValueTests: XCTestCase {
|
||||
@MainActor
|
||||
func testStoredBinding() async throws {
|
||||
// Using wrappedValue for our tests to work around the fact that Binding doesn't conform to Equatable
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(BoutiqueItem.sweater).wrappedValue)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(.sweater).wrappedValue)
|
||||
|
||||
self.$storedBinding.set(BoutiqueItem.belt)
|
||||
self.$storedBinding.set(.belt)
|
||||
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(BoutiqueItem.belt).wrappedValue)
|
||||
XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(.belt).wrappedValue)
|
||||
}
|
||||
|
||||
func testPublishedValueSubscription() async throws {
|
||||
@@ -147,15 +147,15 @@ final class StoredValueTests: XCTestCase {
|
||||
values.append(item)
|
||||
|
||||
if values.count == 4 {
|
||||
XCTAssertEqual(values, [BoutiqueItem.coat, .purse, .sweater, .belt])
|
||||
XCTAssertEqual(values, [.coat, .purse, .sweater, .belt])
|
||||
expectation.fulfill()
|
||||
}
|
||||
})
|
||||
.store(in: &cancellables)
|
||||
|
||||
await self.$storedItem.set(BoutiqueItem.purse)
|
||||
await self.$storedItem.set(BoutiqueItem.sweater)
|
||||
await self.$storedItem.set(BoutiqueItem.belt)
|
||||
await self.$storedItem.set(.purse)
|
||||
await self.$storedItem.set(.sweater)
|
||||
await self.$storedItem.set(.belt)
|
||||
|
||||
await fulfillment(of: [expectation], timeout: 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user