From c9044d78f577231d610005810b860870bbbf848f Mon Sep 17 00:00:00 2001 From: Joe Fabisevich Date: Tue, 12 Dec 2023 23:01:59 -0500 Subject: [PATCH] Adding togglePresence to array variants of StoredValue and AsyncStoredValue --- Sources/Boutique/StoredValue+Array.swift | 41 ++++++++++++++++--- .../BoutiqueTests/AsyncStoredValueTests.swift | 17 ++++++++ Tests/BoutiqueTests/StoredValueTests.swift | 17 ++++++++ 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/Sources/Boutique/StoredValue+Array.swift b/Sources/Boutique/StoredValue+Array.swift index 78200b9..2dcc1f5 100644 --- a/Sources/Boutique/StoredValue+Array.swift +++ b/Sources/Boutique/StoredValue+Array.swift @@ -1,4 +1,4 @@ -public extension StoredValue { +public extension StoredValue where Item: RangeReplaceableCollection { /// A function to append a @``StoredValue`` represented by an `Array` /// without having to manually make an intermediate copy for every value update. /// @@ -14,9 +14,16 @@ public extension StoredValue { /// try await self.$redPandaList.append("Pabu") /// ``` @MainActor - func append(_ value: Value) where Item == [Value] { + func append(_ item: Item.Element) { var updatedArray = self.wrappedValue - updatedArray.append(value) + updatedArray.append(item) + self.set(updatedArray) + } + + @MainActor + func togglePresence(_ value: Value) where Item == [Value] { + var updatedArray = self.wrappedValue + updatedArray.togglePresence(value) self.set(updatedArray) } } @@ -48,7 +55,7 @@ public extension SecurelyStoredValue { } } -public extension AsyncStoredValue { +public extension AsyncStoredValue where Item: RangeReplaceableCollection { /// A function to append a @``StoredValue`` represented by an `Array` /// without having to manually make an intermediate copy for every value update. /// @@ -63,9 +70,31 @@ public extension AsyncStoredValue { /// ``` /// try await self.$redPandaList.append("Pabu") /// ``` - func append(_ value: Value) async throws where Item == [Value] { + func append(_ item: Item.Element) async throws { var updatedArray = self.wrappedValue - updatedArray.append(value) + updatedArray.append(item) + try await self.set(updatedArray) + } + + @MainActor + func togglePresence(_ value: Value) async throws where Item == [Value] { + var updatedArray = self.wrappedValue + updatedArray.togglePresence(value) try await self.set(updatedArray) } } + +private extension Array where Element: Equatable { + /// Adds a tag to an array if the tag doesn't exist in the array, otherwise removes the tag from the array. + /// This is useful for actions like a user tapping a button, where the current existence + /// of the tag in the array may not be known. + /// + /// - Parameter tag: The tag to add or remove + mutating func togglePresence(_ item: Element) { + if self.contains(where: { $0 == item }) { + self.removeAll(where: { $0 == item }) + } else { + self.append(item) + } + } +} diff --git a/Tests/BoutiqueTests/AsyncStoredValueTests.swift b/Tests/BoutiqueTests/AsyncStoredValueTests.swift index ac39797..af34745 100644 --- a/Tests/BoutiqueTests/AsyncStoredValueTests.swift +++ b/Tests/BoutiqueTests/AsyncStoredValueTests.swift @@ -88,6 +88,23 @@ final class AsyncStoredValueTests: XCTestCase { XCTAssertEqual(self.storedArrayValue, [BoutiqueItem.sweater, BoutiqueItem.belt]) } + func testStoredArrayValueTogglePresence() async throws { + XCTAssertEqual(self.storedArrayValue, []) + + try await self.$storedArrayValue.togglePresence(.sweater) + XCTAssertEqual(self.storedArrayValue, [.sweater]) + + try await self.$storedArrayValue.togglePresence(.sweater) + XCTAssertEqual(self.storedArrayValue, []) + + try await self.$storedArrayValue.togglePresence(.sweater) + try await self.$storedArrayValue.togglePresence(.belt) + XCTAssertEqual(self.storedArrayValue, [.sweater, .belt]) + + try await self.$storedArrayValue.togglePresence(.belt) + XCTAssertEqual(self.storedArrayValue, [.sweater]) + } + func testStoredBinding() async throws { XCTAssertEqual(self.$storedBinding.binding.wrappedValue, Binding.constant(BoutiqueItem.sweater).wrappedValue) diff --git a/Tests/BoutiqueTests/StoredValueTests.swift b/Tests/BoutiqueTests/StoredValueTests.swift index cf5fee1..c69f753 100644 --- a/Tests/BoutiqueTests/StoredValueTests.swift +++ b/Tests/BoutiqueTests/StoredValueTests.swift @@ -110,6 +110,23 @@ final class StoredValueTests: XCTestCase { XCTAssertEqual(self.storedArrayValue, [BoutiqueItem.sweater, BoutiqueItem.belt]) } + func testStoredArrayValueTogglePresence() async throws { + XCTAssertEqual(self.storedArrayValue, []) + + await self.$storedArrayValue.togglePresence(.sweater) + XCTAssertEqual(self.storedArrayValue, [.sweater]) + + await self.$storedArrayValue.togglePresence(.sweater) + XCTAssertEqual(self.storedArrayValue, []) + + await self.$storedArrayValue.togglePresence(.sweater) + await self.$storedArrayValue.togglePresence(.belt) + XCTAssertEqual(self.storedArrayValue, [.sweater, .belt]) + + await self.$storedArrayValue.togglePresence(.belt) + XCTAssertEqual(self.storedArrayValue, [.sweater]) + } + @MainActor func testStoredBinding() async throws { // Using wrappedValue for our tests to work around the fact that Binding doesn't conform to Equatable