Skip to content

Commit

Permalink
Fix change & receive
Browse files Browse the repository at this point in the history
  • Loading branch information
Szymon Lorenz committed Sep 15, 2023
1 parent cf86cbf commit 29998d7
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 39 deletions.
14 changes: 6 additions & 8 deletions Sources/TokamakCore/Modifiers/OnChangeModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,17 @@ struct OnChangeModifier<V: Equatable>: ViewModifier {

func body(content: Content) -> some View {
content
.onReceive(Just(value)) { newValue in
// TODO: Fix, when @State if working with in a ViewModifier
// ignore first call when oldValue == nil. For now old value is always nil
if newValue != oldValue {
action(oldValue ?? value, newValue)
}
oldValue = value
}
.onAppear {
if initial {
action(value, value)
}
}
.onReceive(Just(value)) { newValue in
if let oldValue, newValue != oldValue {
action(oldValue, newValue)
}
oldValue = value
}
}
}

Expand Down
25 changes: 6 additions & 19 deletions Sources/TokamakCore/Modifiers/OnReceiveModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,18 @@
import Foundation
import OpenCombineShim

private struct OnReceiveModifier<P: Publisher>: ViewModifier where P.Failure == Never {
@ObservedObject
var cancellableHolder = CancellableHolder()

struct OnReceiveModifier<P: Publisher>: ViewModifier where P.Failure == Never {
init(publisher: P, action: @escaping (P.Output) -> ()) {
cancellableHolder.cancellable = publisher.sink(receiveValue: action)
Task {
for await value in publisher.values {
action(value)
}
}
}

func body(content: Content) -> some View {
content
}

// MARK: Types

final class CancellableHolder: ObservableObject {
var cancellable: AnyCancellable? {
didSet {
oldValue?.cancel()
}
}

deinit {
cancellable?.cancel()
}
}
}

public extension View {
Expand Down
49 changes: 49 additions & 0 deletions Sources/TokamakDemo/Modifiers/ReceiveChangeDemo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2020 Tokamak contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Created by Szymon on 24/8/2023.
//

#if os(WASI) && compiler(>=5.5) && (canImport(Concurrency) || canImport(_Concurrency))
import TokamakDOM

struct ReceiveChangeDemo: View {
@State
private var count = 0
@State
private var count2 = 0

var body: some View {
VStack {
Text("Count: \(count)")
Text("Count2: \(count2)")

HStack {
Button("Increment") {
count += 1
}
Button("Increment2") {
count2 += 1
}
}
}
.onChange(of: count) { oldValue, newValue in
print("🚺 changed \(oldValue) - \(newValue)")
}
.onChange(of: count2, initial: true) { oldValue, newValue in
print("▶️ init, changed \(oldValue) - \(newValue)")
}
}
}
#endif
1 change: 1 addition & 0 deletions Sources/TokamakDemo/TokamakDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ struct TokamakDemoView: View {
Section(header: Text("Modifiers")) {
NavItem("Shadow", destination: ShadowDemo())
#if os(WASI) && compiler(>=5.5) && (canImport(Concurrency) || canImport(_Concurrency))
NavItem("Receive Change", destination: ReceiveChangeDemo())
NavItem("Task", destination: TaskDemo())
#endif
}
Expand Down
36 changes: 24 additions & 12 deletions Tests/TokamakTests/ViewReactToDataChangesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,31 @@ class ViewModifierTests: XCTestCase {
let publisher = PassthroughSubject<String, Never>()
var receivedValue = ""

let firstExpectation = XCTestExpectation(description: "First value received from publisher")
let secondExpectation = XCTestExpectation(description: "Second value received from publisher")

let contentView = Text("Hello, world!")
.onReceive(publisher) { value in
receivedValue = value
if receivedValue == "Simulate publisher emitting a first value" {
firstExpectation.fulfill()
} else if receivedValue == "Simulate publisher emitting a next value" {
secondExpectation.fulfill()
}
}

XCTAssertEqual(receivedValue, "")
_ = TestFiberRenderer(.root, size: .zero).render(contentView)

// Simulate publisher emitting a value
publisher.send("Testing onReceive")
XCTAssertEqual(receivedValue, "Testing onReceive")
let fisrPush = "Simulate publisher emitting a first value"
publisher.send(fisrPush)
wait(for: [firstExpectation], timeout: 1.0)
XCTAssertEqual(receivedValue, fisrPush)

// Simulate publisher emitting a value
publisher.send("Second onReceive")
XCTAssertEqual(receivedValue, "Second onReceive")
let secondPush = "Simulate publisher emitting a next value"
publisher.send(secondPush)
wait(for: [secondExpectation], timeout: 1.0)
XCTAssertEqual(receivedValue, secondPush)
}

func testOnChangeWithValue() {
Expand All @@ -56,14 +67,14 @@ class ViewModifierTests: XCTestCase {
count = 5

// Re-evaluate the view
let reconciler = TestFiberRenderer(.root, size: .zero).render(contentView)
_ = TestFiberRenderer(.root, size: .zero).render(contentView)

XCTAssertEqual(count, 5)
XCTAssertEqual(oldCount, 0)
}

func testOnChangeWithInitialValue() {
var count = 0
let count = 0
var actionFired = false

let contentView = Text("Hello, world!")
Expand All @@ -74,12 +85,13 @@ class ViewModifierTests: XCTestCase {
XCTAssertFalse(actionFired)

// Re-evaluate the view
let reconciler = TestFiberRenderer(.root, size: .zero).render(contentView)
_ = TestFiberRenderer(.root, size: .zero).render(contentView)

XCTAssertTrue(actionFired)
}

func testModifierComposition() {
let expectation = XCTestExpectation(description: "")
let publisher = PassthroughSubject<Int, Never>()
var receivedValue = 0
var count = 0
Expand All @@ -90,19 +102,19 @@ class ViewModifierTests: XCTestCase {
}
.onReceive(publisher) { value in
receivedValue = value
expectation.fulfill()
}

XCTAssertEqual(count, 0)
XCTAssertEqual(receivedValue, 0)
_ = TestFiberRenderer(.root, size: .zero).render(contentView)

// Simulate publisher emitting a value
publisher.send(10)
// Simulate a change in value
count = 5

// Re-evaluate the view
let reconciler = TestFiberRenderer(.root, size: .zero).render(contentView)

wait(for: [expectation], timeout: 1.0)
XCTAssertEqual(count, 5)
XCTAssertEqual(receivedValue, 10)
}
Expand Down

0 comments on commit 29998d7

Please sign in to comment.