Skip to content

Commit

Permalink
fix(observer): [#30] Observe alias entity changes (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris committed Mar 9, 2023
1 parent 2ffba6f commit f443053
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 21 deletions.
40 changes: 25 additions & 15 deletions Sources/CohesionKit/Observer/AliasObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,47 33,57 @@ extension AliasObserver {
/// Create an observer sending updates every time:
/// - the ref node change
/// - the ref node value change
static func createObserve(
private static func createObserve(
for alias: Ref<EntityNode<T>?>,
queue: DispatchQueue,
onChange: @escaping OnChangeClosure
) -> Subscription {
var nestedSubscription: Subscription? = nil
var entityChangesSubscription: Subscription? = alias
.value
.map { node in EntityObserver(node: node, queue: .main) }?
.observe(onChange: onChange)

// subscribe to alias changes
let subscription = alias.addObserver { node in
let nodeObserver = node.map { EntityObserver(node: $0, queue: queue) }

queue.async { onChange(nodeObserver?.value) }
nestedSubscription = nodeObserver?.observe(onChange: onChange)
// update entity changes subscription
entityChangesSubscription = nodeObserver?.observe(onChange: onChange)
}

return Subscription {
subscription.unsubscribe()
nestedSubscription?.unsubscribe()
entityChangesSubscription?.unsubscribe()
}
}

/// Create an observer sending updates every time:
/// - the ref node change
/// - any of the ref node element change
static func createObserve<E>(
private static func createObserve<E>(
for alias: Ref<[EntityNode<E>]?>,
queue: DispatchQueue,
onChange: @escaping OnChangeClosure
) -> Subscription where T == Array<E> {
var nestedSubscription: Subscription? = nil
var entitiesChangesSubscriptions: Subscription? = alias
.value
.map { nodes in nodes.map { EntityObserver(node: $0, queue: queue) } }?
.observe(onChange: onChange)

// Subscribe to alias ref changes and to any changes made on the ref collection nodes.
let subscription = alias.addObserver { nodes in
let nodeObservers = nodes.map { $0.map { EntityObserver(node: $0, queue: queue) } }
let nodeObservers = nodes?.map { EntityObserver(node: $0, queue: queue) }

queue.async { onChange(nodeObservers?.value) }

nestedSubscription = nodeObservers?.observe(onChange: onChange)

// update collection changes subscription
entitiesChangesSubscriptions = nodeObservers?.observe(onChange: onChange)
}

return Subscription {
subscription.unsubscribe()
nestedSubscription?.unsubscribe()
entitiesChangesSubscriptions?.unsubscribe()
}
}

Expand Down
57 changes: 51 additions & 6 deletions Tests/CohesionKitTests/Observer/AliasObserverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 2,7 @@ import XCTest
@testable import CohesionKit

class AliasObserverTests: XCTestCase {
func test_observe_refValueChanged_returnRefValue() {
func test_observe_refValueChanged_onChangeIsCalled() {
let ref = Ref(value: Optional.some(EntityNode(SingleNodeFixture(id: 1), modifiedAt: 0)))
let observer = AliasObserver(alias: ref, queue: .main)
let newValue = SingleNodeFixture(id: 2)
Expand All @@ -21,8 21,28 @@ class AliasObserverTests: XCTestCase {
wait(for: [expectation], timeout: 1)
XCTAssertEqual(lastReceivedValue, newValue)
}

func test_observe_refValueChanged_subscribeToValueUpdates() throws {

func test_observe_entityIsUpdated_onChangeIsCalled() throws {
let node = EntityNode(RootFixture(id: 1, primitive: "", singleNode: SingleNodeFixture(id: 0), listNodes: []), modifiedAt: 0)
let observer = AliasObserver(alias: Ref(value: node), queue: .main)
let newValue = RootFixture(id: 1, primitive: "new value", singleNode: SingleNodeFixture(id: 1), listNodes: [])
var lastObservedValue: RootFixture?
let expectation = XCTestExpectation()

let subscription = observer.observe {
lastObservedValue = $0
expectation.fulfill()
}

try withExtendedLifetime(subscription) {
try node.updateEntity(newValue, modifiedAt: 1)

wait(for: [expectation], timeout: 1)
XCTAssertEqual(lastObservedValue, newValue)
}
}

func test_observe_refValueChanged_entityIsUpdated_onChangeIsCalled() throws {
let ref = Ref(value: Optional.some(EntityNode(SingleNodeFixture(id: 1), modifiedAt: 0)))
let observer = AliasObserver(alias: ref, queue: .main)
let newNode = EntityNode(SingleNodeFixture(id: 2), modifiedAt: 0)
Expand Down Expand Up @@ -51,13 71,38 @@ class AliasObserverTests: XCTestCase {
let observer = AliasObserver(alias: ref, queue: .main)
let newValue = SingleNodeFixture(id: 3)
var lastReceivedValue: SingleNodeFixture?

_ = observer.observe {
lastReceivedValue = $0
}

try node.updateEntity(newValue, modifiedAt: 1)

XCTAssertNil(lastReceivedValue)
}

func test_observeArray_oneElementChanged_onChangeIsCalled() throws {
let expectation = XCTestExpectation()
let nodes = [
EntityNode(SingleNodeFixture(id: 1), modifiedAt: 0),
EntityNode(SingleNodeFixture(id: 2), modifiedAt: 0)
]
let ref = Ref(value: Optional.some(nodes))
let observer = AliasObserver(alias: ref, queue: .main)
let update = SingleNodeFixture(id: 1, primitive: "Update")
var lastObservedValue: [SingleNodeFixture]?

let subscription = observer.observe {
lastObservedValue = $0
expectation.fulfill()
}

try withExtendedLifetime(subscription) {
// try ref.value?[0].updateEntity(SingleNodeFixture(id: 1, primitive: "Update"), modifiedAt: 1)
try nodes[0].updateEntity(SingleNodeFixture(id: 1, primitive: "Update"), modifiedAt: 1)

wait(for: [expectation], timeout: 1)
XCTAssertEqual(lastObservedValue?.first, update)
}
}
}
21 changes: 21 additions & 0 deletions Tests/CohesionKitTests/Observer/EntityObserverTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 1,21 @@
import XCTest
@testable import CohesionKit

class EntityObserverTests: XCTestCase {
func test_entityIsUpdated_onChangeIsCalled() throws {
let node = EntityNode(SingleNodeFixture(id: 0), modifiedAt: 0)
let observer = EntityObserver(node: node, queue: .main)
let newEntity = SingleNodeFixture(id: 0, primitive: "new entity version")
let expectation = XCTestExpectation()

let subscription = observer.observe {
expectation.fulfill()
XCTAssertEqual($0, newEntity)
}

try withExtendedLifetime(subscription) {
try node.updateEntity(newEntity, modifiedAt: 1)
wait(for: [expectation], timeout: 0.5)
}
}
}

0 comments on commit f443053

Please sign in to comment.