mirror of
https://github.com/pointfreeco/swift-composable-architecture.git
synced 2025-12-20 09:11:33 +01:00
799 lines
21 KiB
Swift
799 lines
21 KiB
Swift
import Combine
|
|
import ComposableArchitecture
|
|
import XCTest
|
|
|
|
final class ObservableTests: BaseTCATestCase {
|
|
func testBasics() async {
|
|
var state = ChildState()
|
|
let countDidChange = self.expectation(description: "count.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.count
|
|
} onChange: {
|
|
countDidChange.fulfill()
|
|
}
|
|
|
|
state.count += 1
|
|
await self.fulfillment(of: [countDidChange], timeout: 0)
|
|
XCTAssertEqual(state.count, 1)
|
|
}
|
|
|
|
func testAssignEqualValue() async {
|
|
var state = ChildState()
|
|
let didChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.count
|
|
} onChange: {
|
|
didChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.count = state.count
|
|
XCTAssertEqual(state.count, 0)
|
|
XCTAssert(!didChange.withValue { $0 })
|
|
}
|
|
|
|
func testCopyMutation() async {
|
|
XCTTODO(
|
|
"""
|
|
Ideally this test would pass but it does not because making a copy of a child state, mutating
|
|
it, and assigning it does not change the identified array's IDs, and therefore the fast-path
|
|
of _$isIdentityEqual prevents observation.
|
|
"""
|
|
)
|
|
|
|
var state = ParentState(children: [ChildState(count: 42)])
|
|
let countDidChange = self.expectation(description: "count.didChange")
|
|
var copy = state.children[0]
|
|
copy.count += 1
|
|
|
|
withPerceptionTracking {
|
|
_ = state.children[0].count
|
|
} onChange: {
|
|
countDidChange.fulfill()
|
|
}
|
|
|
|
state.children[0] = copy
|
|
|
|
await self.fulfillment(of: [countDidChange], timeout: 0.1)
|
|
XCTAssertEqual(state.children[0].count, 43)
|
|
}
|
|
|
|
func testCopyMutation_WithPerturbation() async {
|
|
var state = ParentState(children: [ChildState(count: 42)])
|
|
let countDidChange = self.expectation(description: "count.didChange")
|
|
var copy = state.children[0]
|
|
copy.count += 1
|
|
|
|
withPerceptionTracking {
|
|
_ = state.children[0].count
|
|
} onChange: {
|
|
countDidChange.fulfill()
|
|
}
|
|
|
|
state.children[0] = copy
|
|
state.children[0]._$willModify()
|
|
|
|
await self.fulfillment(of: [countDidChange], timeout: 0.1)
|
|
XCTAssertEqual(state.children[0].count, 43)
|
|
}
|
|
|
|
func testReplace() async {
|
|
#if swift(<6.2)
|
|
if #available(iOS 17, macOS 14, tvOS 14, watchOS 10, *) {
|
|
XCTTODO("Ideally this would pass but we cannot detect this kind of mutation currently.")
|
|
}
|
|
#endif
|
|
var state = ChildState(count: 42)
|
|
let didChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.count
|
|
} onChange: {
|
|
didChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.replace(with: ChildState())
|
|
XCTAssertEqual(state.count, 0)
|
|
XCTAssert(didChange.withValue { $0 })
|
|
}
|
|
|
|
func testReset() async {
|
|
#if swift(<6.2)
|
|
if #available(iOS 17, macOS 14, tvOS 14, watchOS 10, *) {
|
|
XCTTODO("Ideally this would pass but we cannot detect this kind of mutation currently.")
|
|
}
|
|
#endif
|
|
|
|
var state = ChildState(count: 42)
|
|
let didChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.count
|
|
} onChange: {
|
|
didChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.reset()
|
|
XCTAssertEqual(state.count, 0)
|
|
XCTAssert(didChange.withValue { $0 })
|
|
}
|
|
|
|
func testChildCountMutation() async {
|
|
var state = ParentState()
|
|
let childCountDidChange = LockIsolated(false)
|
|
let childDidChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.child.count
|
|
} onChange: {
|
|
childCountDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.child
|
|
} onChange: {
|
|
childDidChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.child.count += 1
|
|
XCTAssertEqual(state.child.count, 1)
|
|
XCTAssert(childCountDidChange.withValue { $0 })
|
|
XCTAssert(!childDidChange.withValue { $0 })
|
|
}
|
|
|
|
func testChildReset() async {
|
|
var state = ParentState()
|
|
let childCountDidChange = LockIsolated(false)
|
|
let childDidChange = LockIsolated(false)
|
|
|
|
let child = state.child
|
|
withPerceptionTracking {
|
|
_ = child.count
|
|
} onChange: {
|
|
childCountDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.child
|
|
} onChange: {
|
|
childDidChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.child = ChildState(count: 42)
|
|
XCTAssertEqual(state.child.count, 42)
|
|
XCTAssert(!childCountDidChange.withValue { $0 })
|
|
XCTAssert(childDidChange.withValue { $0 })
|
|
}
|
|
|
|
func testReplaceChild() async {
|
|
var state = ParentState()
|
|
let childDidChange = self.expectation(description: "child.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.child
|
|
} onChange: {
|
|
childDidChange.fulfill()
|
|
}
|
|
|
|
state.child.replace(with: ChildState(count: 42))
|
|
await self.fulfillment(of: [childDidChange], timeout: 0)
|
|
XCTAssertEqual(state.child.count, 42)
|
|
}
|
|
|
|
func testResetChild() async {
|
|
var state = ParentState(child: ChildState(count: 42))
|
|
let childDidChange = self.expectation(description: "child.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.child
|
|
} onChange: {
|
|
childDidChange.fulfill()
|
|
}
|
|
|
|
state.child.reset()
|
|
await self.fulfillment(of: [childDidChange], timeout: 0)
|
|
XCTAssertEqual(state.child.count, 0)
|
|
}
|
|
|
|
func testSwapSiblings() async {
|
|
var state = ParentState(
|
|
child: ChildState(count: 1),
|
|
sibling: ChildState(count: -1)
|
|
)
|
|
let childDidChange = self.expectation(description: "child.didChange")
|
|
let siblingDidChange = self.expectation(description: "sibling.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.child
|
|
} onChange: {
|
|
childDidChange.fulfill()
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.sibling
|
|
} onChange: {
|
|
siblingDidChange.fulfill()
|
|
}
|
|
|
|
state.swap()
|
|
await self.fulfillment(of: [childDidChange], timeout: 0)
|
|
await self.fulfillment(of: [siblingDidChange], timeout: 0)
|
|
XCTAssertEqual(state.child.count, -1)
|
|
XCTAssertEqual(state.sibling.count, 1)
|
|
}
|
|
|
|
func testPresentOptional() async {
|
|
var state = ParentState()
|
|
let optionalDidChange = self.expectation(description: "optional.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.optional
|
|
} onChange: {
|
|
optionalDidChange.fulfill()
|
|
}
|
|
|
|
state.optional = ChildState(count: 42)
|
|
await self.fulfillment(of: [optionalDidChange], timeout: 0)
|
|
XCTAssertEqual(state.optional?.count, 42)
|
|
}
|
|
|
|
func testMutatePresentedOptional() async {
|
|
var state = ParentState(optional: ChildState())
|
|
let optionalDidChange = LockIsolated(false)
|
|
let optionalCountDidChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.optional
|
|
} onChange: {
|
|
optionalDidChange.withValue { $0 = true }
|
|
}
|
|
let optional = state.optional
|
|
withPerceptionTracking {
|
|
_ = optional?.count
|
|
} onChange: {
|
|
optionalCountDidChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.optional?.count += 1
|
|
XCTAssertEqual(state.optional?.count, 1)
|
|
XCTAssert(!optionalDidChange.withValue { $0 })
|
|
XCTAssert(optionalCountDidChange.withValue { $0 })
|
|
}
|
|
|
|
func testPresentDestination() async {
|
|
var state = ParentState()
|
|
let destinationDidChange = self.expectation(description: "destination.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.destination
|
|
} onChange: {
|
|
destinationDidChange.fulfill()
|
|
}
|
|
|
|
state.destination = .child1(ChildState(count: 42))
|
|
await self.fulfillment(of: [destinationDidChange], timeout: 0)
|
|
XCTAssertEqual(state.destination?.child1?.count, 42)
|
|
}
|
|
|
|
func testDismissDestination() async {
|
|
var state = ParentState(destination: .child1(ChildState()))
|
|
let destinationDidChange = self.expectation(description: "destination.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.destination
|
|
} onChange: {
|
|
destinationDidChange.fulfill()
|
|
}
|
|
|
|
state.destination = nil
|
|
await self.fulfillment(of: [destinationDidChange], timeout: 0)
|
|
XCTAssertEqual(state.destination, nil)
|
|
}
|
|
|
|
func testChangeDestination() async {
|
|
var state = ParentState(destination: .child1(ChildState()))
|
|
let destinationDidChange = self.expectation(description: "destination.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.destination
|
|
} onChange: {
|
|
destinationDidChange.fulfill()
|
|
}
|
|
|
|
state.destination = .child2(ChildState(count: 42))
|
|
await self.fulfillment(of: [destinationDidChange], timeout: 0)
|
|
XCTAssertEqual(state.destination?.child2?.count, 42)
|
|
}
|
|
|
|
func testChangeDestination_KeepIdentity() async {
|
|
let childState = ChildState(count: 42)
|
|
var state = ParentState(destination: .child1(childState))
|
|
let destinationDidChange = self.expectation(description: "destination.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.destination
|
|
} onChange: {
|
|
destinationDidChange.fulfill()
|
|
}
|
|
|
|
state.destination = .child2(childState)
|
|
await self.fulfillment(of: [destinationDidChange], timeout: 0)
|
|
XCTAssertEqual(state.destination?.child2?.count, 42)
|
|
}
|
|
|
|
func testMutatingDestination_NonObservableCase() async {
|
|
let expectation = self.expectation(description: "destination.didChange")
|
|
var state = ParentState(destination: .inert(0))
|
|
|
|
withPerceptionTracking {
|
|
_ = state.destination
|
|
} onChange: {
|
|
expectation.fulfill()
|
|
}
|
|
|
|
state.destination = .inert(1)
|
|
XCTAssertEqual(state.destination, .inert(1))
|
|
await self.fulfillment(of: [expectation])
|
|
}
|
|
|
|
func testReplaceWithCopy() async {
|
|
let childState = ChildState(count: 1)
|
|
var childStateCopy = childState
|
|
childStateCopy.count = 2
|
|
var state = ParentState(child: childState, sibling: childStateCopy)
|
|
let childCountDidChange = self.expectation(description: "child.count.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.child.count
|
|
} onChange: {
|
|
childCountDidChange.fulfill()
|
|
}
|
|
|
|
state.child.replace(with: state.sibling)
|
|
|
|
await self.fulfillment(of: [childCountDidChange], timeout: 0)
|
|
XCTAssertEqual(state.child.count, 2)
|
|
XCTAssertEqual(state.sibling.count, 2)
|
|
}
|
|
|
|
@MainActor
|
|
func testStore_ReplaceChild() async {
|
|
let store = Store<ParentState, Void>(initialState: ParentState()) {
|
|
Reduce { state, _ in
|
|
state.child.replace(with: ChildState(count: 42))
|
|
return .none
|
|
}
|
|
}
|
|
let childDidChange = self.expectation(description: "child.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = store.child
|
|
} onChange: {
|
|
childDidChange.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
await self.fulfillment(of: [childDidChange], timeout: 0)
|
|
XCTAssertEqual(store.child.count, 42)
|
|
}
|
|
|
|
@MainActor
|
|
func testStore_Replace() async {
|
|
let store = Store<ChildState, Void>(initialState: ChildState()) {
|
|
Reduce { state, _ in
|
|
state.replace(with: ChildState(count: 42))
|
|
return .none
|
|
}
|
|
}
|
|
let countDidChange = self.expectation(description: "child.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = store.count
|
|
} onChange: {
|
|
countDidChange.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
await self.fulfillment(of: [countDidChange], timeout: 0)
|
|
XCTAssertEqual(store.count, 42)
|
|
}
|
|
|
|
@MainActor
|
|
func testStore_ResetChild() async {
|
|
let store = Store<ParentState, Void>(initialState: ParentState(child: ChildState(count: 42))) {
|
|
Reduce { state, _ in
|
|
state.child.reset()
|
|
return .none
|
|
}
|
|
}
|
|
let childDidChange = self.expectation(description: "child.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = store.child
|
|
} onChange: {
|
|
childDidChange.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
await self.fulfillment(of: [childDidChange], timeout: 0)
|
|
XCTAssertEqual(store.child.count, 0)
|
|
}
|
|
|
|
@MainActor
|
|
func testStore_Reset() async {
|
|
let store = Store<ChildState, Void>(initialState: ChildState(count: 42)) {
|
|
Reduce { state, _ in
|
|
state.reset()
|
|
return .none
|
|
}
|
|
}
|
|
let countDidChange = self.expectation(description: "child.didChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = store.count
|
|
} onChange: {
|
|
countDidChange.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
await self.fulfillment(of: [countDidChange], timeout: 0)
|
|
XCTAssertEqual(store.count, 0)
|
|
}
|
|
|
|
func testIdentifiedArray_AddElement() {
|
|
var state = ParentState()
|
|
let rowsDidChange = self.expectation(description: "rowsDidChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.rows
|
|
} onChange: {
|
|
rowsDidChange.fulfill()
|
|
}
|
|
|
|
state.rows.append(ChildState())
|
|
XCTAssertEqual(state.rows.count, 1)
|
|
self.wait(for: [rowsDidChange], timeout: 0)
|
|
}
|
|
|
|
func testIdentifiedArray_MutateElement() {
|
|
var state = ParentState(rows: [
|
|
ChildState(),
|
|
ChildState(),
|
|
])
|
|
let rowsDidChange = LockIsolated(false)
|
|
let firstRowDidChange = LockIsolated(false)
|
|
let firstRowCountDidChange = LockIsolated(false)
|
|
let secondRowDidCountChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.rows
|
|
} onChange: {
|
|
rowsDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.rows[0]
|
|
} onChange: {
|
|
firstRowDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.rows[0].count
|
|
} onChange: {
|
|
firstRowCountDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.rows[1].count
|
|
} onChange: {
|
|
secondRowDidCountChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.rows[0].count += 1
|
|
XCTAssertEqual(state.rows[0].count, 1)
|
|
XCTAssert(!rowsDidChange.withValue { $0 })
|
|
XCTAssert(!firstRowDidChange.withValue { $0 })
|
|
XCTAssert(firstRowCountDidChange.withValue { $0 })
|
|
XCTAssert(!secondRowDidCountChange.withValue { $0 })
|
|
}
|
|
|
|
func testIdentifiedArray_CopyMutateAssignElement() {
|
|
var state = ParentState(rows: [
|
|
ChildState(),
|
|
ChildState(),
|
|
])
|
|
let rowsDidChange = LockIsolated(0)
|
|
let firstRowDidChange = LockIsolated(0)
|
|
let firstRowCountDidChange = LockIsolated(0)
|
|
let secondRowDidCountChange = LockIsolated(0)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.rows
|
|
} onChange: {
|
|
rowsDidChange.withValue { $0 += 1 }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.rows[0]
|
|
} onChange: {
|
|
firstRowDidChange.withValue { $0 += 1 }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.rows[0].count
|
|
} onChange: {
|
|
firstRowCountDidChange.withValue { $0 += 1 }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.rows[1].count
|
|
} onChange: {
|
|
secondRowDidCountChange.withValue { $0 += 1 }
|
|
}
|
|
|
|
var copy = state.rows[0]
|
|
copy.count += 1
|
|
state.rows[id: copy.id] = copy
|
|
|
|
XCTAssertEqual(state.rows[0].count, 1)
|
|
XCTAssert(rowsDidChange.withValue(\.self) == 1)
|
|
XCTAssert(firstRowDidChange.withValue(\.self) == 1)
|
|
XCTAssert(firstRowCountDidChange.withValue(\.self) == 1)
|
|
XCTAssert(secondRowDidCountChange.withValue(\.self) == 1)
|
|
|
|
state.rows[id: state.rows[0].id] = nil
|
|
XCTAssertEqual(state.rows.count, 1)
|
|
XCTAssertEqual(state.rows[0].count, 0)
|
|
XCTAssertEqual(rowsDidChange.withValue(\.self), 2)
|
|
XCTAssertEqual(firstRowDidChange.withValue(\.self), 1)
|
|
XCTAssertEqual(firstRowCountDidChange.withValue(\.self), 1)
|
|
XCTAssertEqual(secondRowDidCountChange.withValue(\.self), 1)
|
|
|
|
let new = ChildState(count: 42)
|
|
state.rows[id: new.id] = new
|
|
XCTAssertEqual(state.rows[1].count, 42)
|
|
XCTAssertEqual(rowsDidChange.withValue(\.self), 3)
|
|
XCTAssertEqual(firstRowDidChange.withValue(\.self), 1)
|
|
XCTAssertEqual(firstRowCountDidChange.withValue(\.self), 1)
|
|
XCTAssertEqual(secondRowDidCountChange.withValue(\.self), 1)
|
|
}
|
|
|
|
func testPresents_NilToNonNil() {
|
|
var state = ParentState()
|
|
let presentationDidChange = self.expectation(description: "presentationDidChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.presentation
|
|
} onChange: {
|
|
presentationDidChange.fulfill()
|
|
}
|
|
|
|
state.presentation = ChildState()
|
|
XCTAssertEqual(state.presentation?.count, 0)
|
|
self.wait(for: [presentationDidChange], timeout: 0)
|
|
}
|
|
|
|
func testPresents_Mutate() {
|
|
var state = ParentState(presentation: ChildState())
|
|
let presentationDidChange = LockIsolated(false)
|
|
let presentationCountDidChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.presentation
|
|
} onChange: {
|
|
presentationDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.presentation?.count
|
|
} onChange: {
|
|
presentationCountDidChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.presentation?.count += 1
|
|
XCTAssertEqual(state.presentation?.count, 1)
|
|
XCTAssert(!presentationDidChange.withValue { $0 })
|
|
XCTAssert(presentationCountDidChange.withValue { $0 })
|
|
}
|
|
|
|
func testStackState_AddElement() {
|
|
var state = ParentState()
|
|
let pathDidChange = self.expectation(description: "pathDidChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.path
|
|
} onChange: {
|
|
pathDidChange.fulfill()
|
|
}
|
|
|
|
state.path.append(ChildState())
|
|
XCTAssertEqual(state.path.count, 1)
|
|
self.wait(for: [pathDidChange], timeout: 0)
|
|
}
|
|
|
|
func testStackState_MutateElement() {
|
|
var state = ParentState(
|
|
path: StackState([
|
|
ChildState(),
|
|
ChildState(),
|
|
])
|
|
)
|
|
let pathDidChange = LockIsolated(false)
|
|
let firstElementDidChange = LockIsolated(false)
|
|
let firstElementCountDidChange = LockIsolated(false)
|
|
let secondElementCountDidChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.path
|
|
} onChange: {
|
|
pathDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.path[0]
|
|
} onChange: {
|
|
firstElementDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.path[0].count
|
|
} onChange: {
|
|
firstElementCountDidChange.withValue { $0 = true }
|
|
}
|
|
withPerceptionTracking {
|
|
_ = state.path[1].count
|
|
} onChange: {
|
|
secondElementCountDidChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.path[id: 0]?.count += 1
|
|
XCTAssertEqual(state.path[0].count, 1)
|
|
XCTAssert(!pathDidChange.withValue { $0 })
|
|
XCTAssert(!firstElementDidChange.withValue { $0 })
|
|
XCTAssert(firstElementCountDidChange.withValue { $0 })
|
|
XCTAssert(!secondElementCountDidChange.withValue { $0 })
|
|
}
|
|
|
|
func testCopy() {
|
|
var state = ParentState()
|
|
var childCopy = state.child.copy()
|
|
childCopy.count = 42
|
|
let childCountDidChange = self.expectation(description: "childCountDidChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.child.count
|
|
} onChange: {
|
|
childCountDidChange.fulfill()
|
|
}
|
|
|
|
state.child.replace(with: childCopy)
|
|
XCTAssertEqual(state.child.count, 42)
|
|
self.wait(for: [childCountDidChange], timeout: 0)
|
|
}
|
|
|
|
func testArrayAppend() {
|
|
var state = ParentState()
|
|
let childrenDidChange = self.expectation(description: "childrenDidChange")
|
|
|
|
withPerceptionTracking {
|
|
_ = state.children
|
|
} onChange: {
|
|
childrenDidChange.fulfill()
|
|
}
|
|
|
|
state.children.append(ChildState())
|
|
self.wait(for: [childrenDidChange])
|
|
}
|
|
|
|
func testArrayMutate() {
|
|
var state = ParentState(children: [ChildState()])
|
|
var didChange = LockIsolated(false)
|
|
|
|
withPerceptionTracking {
|
|
_ = state.children
|
|
} onChange: {
|
|
didChange.withValue { $0 = true }
|
|
}
|
|
|
|
state.children[0].count += 1
|
|
XCTAssert(!didChange.withValue { $0 })
|
|
}
|
|
|
|
@MainActor
|
|
func testEnumStateWithInertCases() {
|
|
let store = Store<EnumState, Void>(initialState: EnumState.count(.one)) {
|
|
Reduce { state, _ in
|
|
state = .count(.two)
|
|
return .none
|
|
}
|
|
}
|
|
let onChangeExpectation = self.expectation(description: "onChange")
|
|
withPerceptionTracking {
|
|
_ = store.state
|
|
} onChange: {
|
|
onChangeExpectation.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
|
|
self.wait(for: [onChangeExpectation], timeout: 0)
|
|
}
|
|
|
|
@MainActor
|
|
func testEnumStateWithInertCasesTricky() {
|
|
let store = Store<EnumState, Void>(initialState: EnumState.count(.one)) {
|
|
Reduce { state, _ in
|
|
state = .anotherCount(.one)
|
|
return .none
|
|
}
|
|
}
|
|
let onChangeExpectation = self.expectation(description: "onChange")
|
|
withPerceptionTracking {
|
|
_ = store.state
|
|
} onChange: {
|
|
onChangeExpectation.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
|
|
self.wait(for: [onChangeExpectation], timeout: 0)
|
|
}
|
|
|
|
@MainActor
|
|
func testEnumStateWithIntCase() {
|
|
let store = Store<EnumState, Void>(initialState: EnumState.int(0)) {
|
|
Reduce { state, _ in
|
|
state = .int(1)
|
|
return .none
|
|
}
|
|
}
|
|
let onChangeExpectation = self.expectation(description: "onChange")
|
|
withPerceptionTracking {
|
|
_ = store.state
|
|
} onChange: {
|
|
onChangeExpectation.fulfill()
|
|
}
|
|
|
|
store.send(())
|
|
|
|
self.wait(for: [onChangeExpectation], timeout: 0)
|
|
}
|
|
}
|
|
|
|
@ObservableState
|
|
private struct ChildState: Equatable, Identifiable {
|
|
var count = 0
|
|
mutating func replace(with other: Self) {
|
|
self = other
|
|
}
|
|
mutating func reset() {
|
|
self = Self()
|
|
}
|
|
mutating func copy() -> Self {
|
|
self
|
|
}
|
|
}
|
|
@ObservableState
|
|
private struct ParentState: Equatable {
|
|
var child = ChildState()
|
|
@Presents var destination: DestinationState?
|
|
var children: [ChildState] = []
|
|
@Presents var optional: ChildState?
|
|
var path = StackState<ChildState>()
|
|
@Presents var presentation: ChildState?
|
|
var rows: IdentifiedArrayOf<ChildState> = []
|
|
var sibling = ChildState()
|
|
mutating func swap() {
|
|
let childCopy = child
|
|
self.child = self.sibling
|
|
self.sibling = childCopy
|
|
}
|
|
}
|
|
@dynamicMemberLookup
|
|
@CasePathable
|
|
@ObservableState
|
|
private enum DestinationState: Equatable {
|
|
case child1(ChildState)
|
|
case child2(ChildState)
|
|
case inert(Int)
|
|
}
|
|
@ObservableState
|
|
private enum EnumState: Equatable {
|
|
case count(Count)
|
|
case anotherCount(Count)
|
|
case int(Int)
|
|
@ObservableState
|
|
enum Count: String {
|
|
case one, two
|
|
}
|
|
}
|