Implement node ref updates in TestRenderer (#51)

This is a prerequisite for #49, the actual `UIKitRenderer` implementation will follow in a separate PR.
This commit is contained in:
Max Desiatov 2019-02-23 11:30:48 +00:00 committed by GitHub
parent c9ae90e96f
commit 5fabe139dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 8 deletions

View File

@ -15,7 +15,7 @@ public struct AnyNode: Equatable {
lhs.props == rhs.props
}
let ref: AnyObject?
public let ref: AnyObject?
public let props: AnyEquatable
public let children: AnyEquatable
let type: ComponentType
@ -115,7 +115,7 @@ extension CompositeComponent {
extension RefComponent {
public static func node(
ref: Ref<RefType>,
ref: Ref<RefTarget?>,
_ props: Props,
_ children: Children
) -> AnyNode {

View File

@ -13,8 +13,24 @@ public protocol AnyHostComponent {}
public protocol HostComponent: AnyHostComponent, Component {}
public protocol RefComponent: HostComponent {
associatedtype RefType
public protocol AnyRefComponent {
static func update(ref: AnyObject, with value: Any)
}
public protocol RefComponent: AnyRefComponent, HostComponent {
associatedtype RefTarget
}
extension AnyRefComponent where Self: RefComponent {
public static func update(ref: AnyObject, with value: Any) {
guard let ref = ref as? Ref<RefTarget?>,
let value = value as? RefTarget else {
assertionFailure("failed to cast objects passed to \(#function)")
return
}
ref.value = value
}
}
/// Type-erased version of `CompositeComponent` to work around

View File

@ -28,13 +28,21 @@ public final class TestRenderer: Renderer {
let result = TestView(component.node)
parent.add(subview: result)
update(target: result, with: component)
return result
}
public func update(
target: TestView,
with component: TestRenderer.MountedHost
) {}
) {
guard
let componentType = component.type as? AnyRefComponent.Type,
let anyRef = component.node.ref else { return }
componentType.update(ref: anyRef, with: target)
}
public func unmount(
target: TestView,

View File

@ -10,6 +10,10 @@ import XCTest
@testable import Tokamak
extension Button: RefComponent {
public typealias RefTarget = TestView
}
extension Int: Updatable {
public enum Action {
case increment
@ -39,10 +43,14 @@ struct Test: LeafComponent {
let state1 = hooks.custom()
let state2 = hooks.custom()
let state3 = hooks.custom()
let ref = hooks.ref(type: TestView.self)
return StackView.node([
Button.node(.init(onPress: Handler { state1.set { $0 += 1 } }),
"Increment"),
Button.node(
ref: ref,
.init(onPress: Handler { state1.set { $0 += 1 } }),
"Increment"
),
Label.node("\(state1.value)"),
Button.node(.init(onPress: Handler { state2.set { $0 + 1 } }),
"Increment"),
@ -67,12 +75,15 @@ final class HooksTests: XCTestCase {
let button2Props = stack.subviews[2].props(Button.Props.self),
let button2Handler = button2Props.handlers[.touchUpInside]?.value,
let button3Props = stack.subviews[4].props(Button.Props.self),
let button3Handler = button3Props.handlers[.touchUpInside]?.value
let button3Handler = button3Props.handlers[.touchUpInside]?.value,
let button1Ref = stack.subviews[0].node.ref as? Ref<TestView?>
else {
XCTAssert(false, "components have no handlers")
return
}
XCTAssertTrue(button1Ref.value === stack.subviews[0])
button1Handler(())
button2Handler(())