Define and set RefTarget in TokamakUIKit (#54)

Also add `UIView.animate` animation effect to example code. Also removes redundant `Updatable` protocol.

Resolves #49 

* Define and set RefTarget in TokamakUIKit
* Add animation example
* Rename backgroundColor to currentColor
* Add comments to Animation example
This commit is contained in:
Max Desiatov 2019-02-25 11:50:09 +00:00 committed by GitHub
parent 5fabe139dd
commit 923ffd02fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 153 additions and 99 deletions

View File

@ -15,6 +15,7 @@
A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; }; A6D5AF87221B131400DBF186 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D5AF86221B131400DBF186 /* Image.swift */; };
C449B806DFEE55B6CEE6478C /* libPods-TokamakDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B96B435A9D67621D318616E /* libPods-TokamakDemo.a */; }; C449B806DFEE55B6CEE6478C /* libPods-TokamakDemo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B96B435A9D67621D318616E /* libPods-TokamakDemo.a */; };
D11DB6432219C03000013FC3 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11DB6422219C03000013FC3 /* Timer.swift */; }; D11DB6432219C03000013FC3 /* Timer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D11DB6422219C03000013FC3 /* Timer.swift */; };
D1BB3D302223F6B400C30062 /* Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BB3D2F2223F6B400C30062 /* Animation.swift */; };
D1BFAF772215795900845EA0 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF762215795900845EA0 /* Router.swift */; }; D1BFAF772215795900845EA0 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF762215795900845EA0 /* Router.swift */; };
D1BFAF792215800A00845EA0 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF782215800A00845EA0 /* Counter.swift */; }; D1BFAF792215800A00845EA0 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF782215800A00845EA0 /* Counter.swift */; };
D1BFAF7B22158B4000845EA0 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF7A22158B4000845EA0 /* List.swift */; }; D1BFAF7B22158B4000845EA0 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1BFAF7A22158B4000845EA0 /* List.swift */; };
@ -43,6 +44,7 @@
A9EEF813955DAEEFE1D52ED4 /* Pods-TokamakDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokamakDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokamakDemo/Pods-TokamakDemo.debug.xcconfig"; sourceTree = "<group>"; }; A9EEF813955DAEEFE1D52ED4 /* Pods-TokamakDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokamakDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TokamakDemo/Pods-TokamakDemo.debug.xcconfig"; sourceTree = "<group>"; };
C6DA99382B6892EAB361742F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; }; C6DA99382B6892EAB361742F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
D11DB6422219C03000013FC3 /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; }; D11DB6422219C03000013FC3 /* Timer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timer.swift; sourceTree = "<group>"; };
D1BB3D2F2223F6B400C30062 /* Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation.swift; sourceTree = "<group>"; };
D1BFAF762215795900845EA0 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; }; D1BFAF762215795900845EA0 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
D1BFAF782215800A00845EA0 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = "<group>"; }; D1BFAF782215800A00845EA0 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = "<group>"; };
D1BFAF7A22158B4000845EA0 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = "<group>"; }; D1BFAF7A22158B4000845EA0 /* List.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = "<group>"; };
@ -162,6 +164,7 @@
D1F2C3262214407B008358DC /* TableModal.swift */, D1F2C3262214407B008358DC /* TableModal.swift */,
D11DB6422219C03000013FC3 /* Timer.swift */, D11DB6422219C03000013FC3 /* Timer.swift */,
A6D5AF86221B131400DBF186 /* Image.swift */, A6D5AF86221B131400DBF186 /* Image.swift */,
D1BB3D2F2223F6B400C30062 /* Animation.swift */,
); );
path = Components; path = Components;
sourceTree = "<group>"; sourceTree = "<group>";
@ -283,6 +286,7 @@
D1F7185D2215A4A1004E5951 /* LayerProps.swift in Sources */, D1F7185D2215A4A1004E5951 /* LayerProps.swift in Sources */,
D1BFAF792215800A00845EA0 /* Counter.swift in Sources */, D1BFAF792215800A00845EA0 /* Counter.swift in Sources */,
D1F718612215A617004E5951 /* Modals.swift in Sources */, D1F718612215A617004E5951 /* Modals.swift in Sources */,
D1BB3D302223F6B400C30062 /* Animation.swift in Sources */,
D1F7185522159EAD004E5951 /* DatePickers.swift in Sources */, D1F7185522159EAD004E5951 /* DatePickers.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;

View File

@ -0,0 +1,67 @@
//
// Animation.swift
// TokamakDemo
//
// Created by Max Desiatov on 25/02/2019.
// Copyright © 2019 Tokamak. All rights reserved.
//
import Tokamak
struct Animation: CompositeComponent {
private static let colors: [(Color, String)] = [
(.white, "white"),
(.red, "red"),
(.green, "green"),
(.blue, "blue"),
]
typealias Props = Null
typealias Children = [AnyNode]
static func render(props: Null, children: Children, hooks: Hooks) -> AnyNode {
let previousColor = hooks.state(0)
let currentColor = hooks.state(0)
let ref = hooks.ref(type: UIView.self)
hooks.effect(currentColor.value) {
guard let view = ref.value else { return }
guard currentColor.value != previousColor.value else {
view.backgroundColor = UIColor(colors[currentColor.value].0)
return
}
UIView.animate(withDuration: 0.5, animations: {
view.backgroundColor = UIColor(colors[currentColor.value].0)
}, completion: { _ in
previousColor.set(currentColor.value)
})
}
return View.node(
.init(Style(
Edges.equal(to: .parent),
backgroundColor: .white
)),
StackView.node(.init(
Edges.equal(to: .safeArea),
axis: .vertical,
distribution: .fillEqually
), [
View.node(ref: ref, .init(), children),
SegmentedControl.node(
.init(
value: currentColor.value,
valueHandler: Handler {
// sometimes UISegmentedControl allows deselecting all segments
guard $0 >= 0 else { return }
currentColor.set($0)
}
),
colors.map { $0.1 }
),
])
)
}
}

View File

@ -29,42 +29,18 @@ struct NavigationModal: PureLeafComponent {
} }
} }
struct SimpleModal: LeafComponent { struct SimpleModal: PureLeafComponent {
struct Props: Equatable { struct Props: Equatable {
let isPresented: State<Bool> let isPresented: State<Bool>
} }
private static let colors: [(Color, String)] = [ static func render(props: Props) -> AnyNode {
(.white, "white"),
(.red, "red"),
(.green, "green"),
(.blue, "blue"),
]
static func render(props: Props, hooks: Hooks) -> AnyNode {
let backgroundColor = hooks.state(0)
return props.isPresented.value ? ModalPresenter.node( return props.isPresented.value ? ModalPresenter.node(
View.node( Animation.node(Null(),
.init(Style( Button.node(.init(
Edges.equal(to: .parent), Style(Center.equal(to: .parent)),
backgroundColor: colors[backgroundColor.value].0 onPress: Handler { props.isPresented.set(false) }
)), ), "Close Modal"))
StackView.node(.init(
Edges.equal(to: .parent),
axis: .vertical,
distribution: .fillEqually
), [
Button.node(.init(
onPress: Handler { props.isPresented.set(false) }
), "Close Modal"),
SegmentedControl.node(
.init(value: backgroundColor.value,
valueHandler: Handler(backgroundColor.set)),
colors.map { $0.1 }
),
])
)
) : Null.node() ) : Null.node()
} }
} }

View File

@ -17,7 +17,7 @@ struct TimerCounter: LeafComponent {
let timer = hooks.ref(type: Timer.self) let timer = hooks.ref(type: Timer.self)
let interval = hooks.state(1.0) let interval = hooks.state(1.0)
hooks.effect(interval.value) { () -> () -> () in hooks.finalizedEffect(interval.value) {
timer.value = Timer.scheduledTimer( timer.value = Timer.scheduledTimer(
withTimeInterval: interval.value, withTimeInterval: interval.value,
repeats: true repeats: true

View File

@ -18,6 +18,7 @@ enum AppRoute: String, CaseIterable {
case layerProps = "Layer Props" case layerProps = "Layer Props"
case timer case timer
case image case image
case animation
} }
extension AppRoute: CustomStringConvertible { extension AppRoute: CustomStringConvertible {
@ -59,6 +60,8 @@ struct Router: NavigationRouter {
result = TimerCounter.node() result = TimerCounter.node()
case .image: case .image:
result = ImageExample.node() result = ImageExample.node()
case .animation:
result = Animation.node()
} }
return NavigationItem.node( return NavigationItem.node(

View File

@ -77,6 +77,12 @@ extension Component where Children == [AnyNode] {
} }
} }
extension Component where Props == Null, Children == [AnyNode] {
public static func node() -> AnyNode {
return node(Null(), [])
}
}
extension Component where Props: Default, Props.DefaultValue == Props, extension Component where Props: Default, Props.DefaultValue == Props,
Children == [AnyNode] { Children == [AnyNode] {
public static func node(_ child: AnyNode) -> AnyNode { public static func node(_ child: AnyNode) -> AnyNode {
@ -127,3 +133,23 @@ extension RefComponent {
) )
} }
} }
extension RefComponent where Children == [AnyNode] {
public static func node(
ref: Ref<RefTarget?>,
_ props: Props,
_ child: AnyNode
) -> AnyNode {
return node(ref: ref, props, [child])
}
public static func node(ref: Ref<RefTarget?>, _ props: Props) -> AnyNode {
return node(ref: ref, props, [])
}
}
extension RefComponent where Children == Null {
public static func node(ref: Ref<RefTarget?>, _ props: Props) -> AnyNode {
return node(ref: ref, props, Null())
}
}

View File

@ -15,7 +15,7 @@ extension Hooks {
closure should return a cleanup closure to be executed before the next closure should return a cleanup closure to be executed before the next
call to `render` or when a component is unmounted. call to `render` or when a component is unmounted.
*/ */
public func effect(closure: @escaping () -> () -> ()) { public func finalizedEffect(closure: @escaping () -> () -> ()) {
scheduleEffect(nil, closure) scheduleEffect(nil, closure)
} }
@ -55,7 +55,10 @@ extension Hooks {
trigger effect execution (unsubscribing from updates on old user ID and trigger effect execution (unsubscribing from updates on old user ID and
subscribing for new user ID) when the ID has changed. subscribing for new user ID) when the ID has changed.
*/ */
public func effect<T>(_ observed: T, closure: @escaping () -> () -> ()) public func finalizedEffect<T>(
_ observed: T,
closure: @escaping () -> () -> ()
)
where T: Equatable { where T: Equatable {
scheduleEffect(AnyEquatable(observed), closure) scheduleEffect(AnyEquatable(observed), closure)
} }

View File

@ -5,16 +5,6 @@
// Created by Max Desiatov on 09/02/2019. // Created by Max Desiatov on 09/02/2019.
// //
/// Formalizes an update to a value with a given action. Note that `update`
/// function is mutating here to allow efficient in-place updates. As long as
/// `Updatable` is implemented on a value type, this still allows freezing it
/// into an immutable value when needed.
public protocol Updatable {
associatedtype Action
mutating func update(_ action: Action)
}
typealias Updater<T> = (inout T) -> () typealias Updater<T> = (inout T) -> ()
/** Note that `set` functions are not `mutating`, they never update the /** Note that `set` functions are not `mutating`, they never update the
@ -54,14 +44,6 @@ public struct State<T> {
extension State: Equatable where T: Equatable {} extension State: Equatable where T: Equatable {}
extension State where T: Updatable {
/// For any `Reduceable` state you can dispatch an `Action` to reduce that
/// state to a different value.
public func set(_ action: T.Action) {
updateHandler.value { $0.update(action) }
}
}
extension Hooks { extension Hooks {
/** Allows a component to have its own state and to be updated when the state /** Allows a component to have its own state and to be updated when the state
changes. Returns a very simple state container, which on initial call of changes. Returns a very simple state container, which on initial call of

View File

@ -42,6 +42,8 @@ public final class MountedHostComponent<R: Renderer>: MountedComponent<R> {
self.target = target self.target = target
reconciler.renderer?.update(target: target, with: self)
switch node.children.value { switch node.children.value {
case let nodes as [AnyNode]: case let nodes as [AnyNode]:
mountedChildren = nodes.map { $0.makeMountedComponent(target) } mountedChildren = nodes.map { $0.makeMountedComponent(target) }

View File

@ -28,8 +28,6 @@ public final class TestRenderer: Renderer {
let result = TestView(component.node) let result = TestView(component.node)
parent.add(subview: result) parent.add(subview: result)
update(target: result, with: component)
return result return result
} }

View File

@ -19,4 +19,8 @@ class ViewBox<T: UIView>: ViewControllerBox<UIViewController> {
super.init(viewController, node) super.init(viewController, node)
} }
override var refTarget: Any {
return view
}
} }

View File

@ -19,4 +19,8 @@ class ViewControllerBox<T: UIViewController>: UITarget {
override var viewController: UIViewController { override var viewController: UIViewController {
return containerViewController return containerViewController
} }
override var refTarget: Any {
return containerViewController
}
} }

View File

@ -19,6 +19,7 @@ final class TokamakButton: UIButton, Default {
extension Button: UIControlComponent { extension Button: UIControlComponent {
typealias Target = TokamakButton typealias Target = TokamakButton
public typealias RefTarget = UIButton
static func update(control box: ControlBox<TokamakButton>, static func update(control box: ControlBox<TokamakButton>,
_ props: Button.Props, _ props: Button.Props,

View File

@ -27,6 +27,7 @@ final class TokamakDatePicker: UIDatePicker, Default, ValueStorage {
extension DatePicker: UIValueComponent { extension DatePicker: UIValueComponent {
typealias Target = TokamakDatePicker typealias Target = TokamakDatePicker
public typealias RefTarget = UIDatePicker
static func update(valueBox: ValueControlBox<TokamakDatePicker>, static func update(valueBox: ValueControlBox<TokamakDatePicker>,
_ props: DatePicker.Props, _ props: DatePicker.Props,

View File

@ -28,6 +28,8 @@ extension UIImage.RenderingMode {
} }
extension Image: UIViewComponent { extension Image: UIViewComponent {
public typealias RefTarget = UIImageView
static func update( static func update(
view box: ViewBox<TokamakImage>, view box: ViewBox<TokamakImage>,
_ props: Image.Props, _ props: Image.Props,

View File

@ -32,6 +32,8 @@ extension NSTextAlignment {
} }
extension Label: UIViewComponent { extension Label: UIViewComponent {
public typealias RefTarget = UILabel
static func update(view box: ViewBox<TokamakLabel>, static func update(view box: ViewBox<TokamakLabel>,
_ props: Label.Props, _ props: Label.Props,
_ children: String) { _ children: String) {

View File

@ -9,6 +9,8 @@ import Tokamak
import UIKit import UIKit
extension ListView: UIViewComponent { extension ListView: UIViewComponent {
public typealias RefTarget = UITableView
static func box( static func box(
for view: TokamakTableView, for view: TokamakTableView,
_ viewController: UIViewController, _ viewController: UIViewController,

View File

@ -41,7 +41,6 @@ extension NavigationItem: UIHostComponent {
let viewController = UIViewController() let viewController = UIViewController()
let result = ViewControllerBox(viewController, component.node) let result = ViewControllerBox(viewController, component.node)
update(target: result, node: component.node)
parent.containerViewController.pushViewController( parent.containerViewController.pushViewController(
viewController, viewController,

View File

@ -25,6 +25,7 @@ final class TokamakSegmentedControl: UISegmentedControl, Default, ValueStorage {
extension SegmentedControl: UIValueComponent { extension SegmentedControl: UIValueComponent {
typealias Target = TokamakSegmentedControl typealias Target = TokamakSegmentedControl
public typealias RefTarget = UISegmentedControl
static func update(valueBox: ValueControlBox<TokamakSegmentedControl>, static func update(valueBox: ValueControlBox<TokamakSegmentedControl>,
_ props: SegmentedControl.Props, _ props: SegmentedControl.Props,

View File

@ -16,6 +16,7 @@ final class TokamakSlider: UISlider, Default, ValueStorage {
extension Slider: UIValueComponent { extension Slider: UIValueComponent {
typealias Target = TokamakSlider typealias Target = TokamakSlider
public typealias RefTarget = UISlider
static func update(valueBox: ValueControlBox<TokamakSlider>, static func update(valueBox: ValueControlBox<TokamakSlider>,
_ props: Slider.Props, _ props: Slider.Props,

View File

@ -60,6 +60,8 @@ extension UIStackView.Distribution {
} }
extension StackView: UIViewComponent { extension StackView: UIViewComponent {
public typealias RefTarget = UIStackView
static func update(view box: ViewBox<TokamakStackView>, static func update(view box: ViewBox<TokamakStackView>,
_ props: StackView.Props, _ props: StackView.Props,
_: [AnyNode]) { _: [AnyNode]) {

View File

@ -16,6 +16,7 @@ final class TokamakStepper: UIStepper, Default, ValueStorage {
extension Stepper: UIValueComponent { extension Stepper: UIValueComponent {
typealias Target = TokamakStepper typealias Target = TokamakStepper
public typealias RefTarget = UIStepper
static func update(valueBox: ValueControlBox<TokamakStepper>, static func update(valueBox: ValueControlBox<TokamakStepper>,
_ props: Stepper.Props, _ props: Stepper.Props,

View File

@ -27,6 +27,7 @@ final class TokamakSwitch: UISwitch, Default, ValueStorage {
extension Switch: UIValueComponent { extension Switch: UIValueComponent {
typealias Target = TokamakSwitch typealias Target = TokamakSwitch
public typealias RefTarget = UISwitch
static func update(valueBox: ValueControlBox<TokamakSwitch>, static func update(valueBox: ValueControlBox<TokamakSwitch>,
_ props: Switch.Props, _ props: Switch.Props,

View File

@ -15,6 +15,8 @@ final class TokamakView: UIView, Default {
} }
extension View: UIViewComponent { extension View: UIViewComponent {
public typealias RefTarget = UIView
static func update(view: ViewBox<TokamakView>, static func update(view: ViewBox<TokamakView>,
_ props: View.Props, _ props: View.Props,
_: [AnyNode]) {} _: [AnyNode]) {}

View File

@ -9,7 +9,7 @@ import Tokamak
import UIKit import UIKit
extension UIColor { extension UIColor {
convenience init(_ color: Color) { public convenience init(_ color: Color) {
switch color.space { switch color.space {
case .sRGB: case .sRGB:
self.init(red: CGFloat(color.red), self.init(red: CGFloat(color.red),

View File

@ -9,7 +9,7 @@ import Tokamak
import UIKit import UIKit
extension UIControl.Event { extension UIControl.Event {
init(_ value: Event) { public init(_ value: Event) {
switch value { switch value {
case .touchDown: case .touchDown:
self = .touchDown self = .touchDown

View File

@ -8,7 +8,7 @@
import Tokamak import Tokamak
import UIKit import UIKit
protocol UIViewComponent: UIHostComponent, HostComponent { protocol UIViewComponent: UIHostComponent, RefComponent {
associatedtype Target: UIView & Default associatedtype Target: UIView & Default
static func update(view box: ViewBox<Target>, static func update(view box: ViewBox<Target>,
@ -94,16 +94,6 @@ extension UIViewComponent where Target == Target.DefaultValue,
component: UIKitRenderer.MountedHost, component: UIKitRenderer.MountedHost,
_ renderer: UIKitRenderer _ renderer: UIKitRenderer
) -> UITarget? { ) -> UITarget? {
guard let children = component.node.children.value as? Children else {
childrenAssertionFailure()
return nil
}
guard let props = component.node.props.value as? Props else {
propsAssertionFailure()
return nil
}
let target = Target.defaultValue let target = Target.defaultValue
let result: ViewBox<Target> let result: ViewBox<Target>
@ -167,9 +157,6 @@ extension UIViewComponent where Target == Target.DefaultValue,
parentAssertionFailure() parentAssertionFailure()
} }
applyStyle(result, props)
update(view: result, props, children)
return result return result
} }

View File

@ -30,7 +30,11 @@ struct HackyProvider: SimpleCellProvider {
class UITarget: Target { class UITarget: Target {
var viewController: UIViewController { var viewController: UIViewController {
fatalError("viewController should be overriden in UITarget subclass") fatalError("\(#function) should be overriden in UITarget subclass")
}
var refTarget: Any {
fatalError("\(#function) should be overriden in UITarget subclass")
} }
} }
@ -79,6 +83,12 @@ final class UIKitRenderer: Renderer {
rendererComponent.update(target: target, rendererComponent.update(target: target,
node: component.node) node: component.node)
guard
let componentType = component.type as? AnyRefComponent.Type,
let anyRef = component.node.ref else { return }
componentType.update(ref: anyRef, with: target.refTarget)
} }
func unmount( func unmount(

View File

@ -14,22 +14,6 @@ extension Button: RefComponent {
public typealias RefTarget = TestView public typealias RefTarget = TestView
} }
extension Int: Updatable {
public enum Action {
case increment
case decrement
}
public mutating func update(_ action: Int.Action) {
switch action {
case .decrement:
self -= 1
case .increment:
self += 1
}
}
}
private extension Hooks { private extension Hooks {
func custom() -> State<Int> { func custom() -> State<Int> {
return state(42) return state(42)
@ -42,7 +26,6 @@ struct Test: LeafComponent {
static func render(props: Null, hooks: Hooks) -> AnyNode { static func render(props: Null, hooks: Hooks) -> AnyNode {
let state1 = hooks.custom() let state1 = hooks.custom()
let state2 = hooks.custom() let state2 = hooks.custom()
let state3 = hooks.custom()
let ref = hooks.ref(type: TestView.self) let ref = hooks.ref(type: TestView.self)
return StackView.node([ return StackView.node([
@ -55,9 +38,6 @@ struct Test: LeafComponent {
Button.node(.init(onPress: Handler { state2.set { $0 + 1 } }), Button.node(.init(onPress: Handler { state2.set { $0 + 1 } }),
"Increment"), "Increment"),
Label.node("\(state2.value)"), Label.node("\(state2.value)"),
Button.node(.init(onPress: Handler { state3.set(.increment) }),
"Increment"),
Label.node("\(state3.value)"),
]) ])
} }
} }
@ -74,8 +54,6 @@ final class HooksTests: XCTestCase {
let button1Handler = button1Props.handlers[.touchUpInside]?.value, let button1Handler = button1Props.handlers[.touchUpInside]?.value,
let button2Props = stack.subviews[2].props(Button.Props.self), let button2Props = stack.subviews[2].props(Button.Props.self),
let button2Handler = button2Props.handlers[.touchUpInside]?.value, let button2Handler = button2Props.handlers[.touchUpInside]?.value,
let button3Props = stack.subviews[4].props(Button.Props.self),
let button3Handler = button3Props.handlers[.touchUpInside]?.value,
let button1Ref = stack.subviews[0].node.ref as? Ref<TestView?> let button1Ref = stack.subviews[0].node.ref as? Ref<TestView?>
else { else {
XCTAssert(false, "components have no handlers") XCTAssert(false, "components have no handlers")
@ -89,16 +67,11 @@ final class HooksTests: XCTestCase {
button2Handler(()) button2Handler(())
button2Handler(()) button2Handler(())
button3Handler(())
button3Handler(())
button3Handler(())
let e = expectation(description: "rerender") let e = expectation(description: "rerender")
DispatchQueue.main.async { DispatchQueue.main.async {
XCTAssertEqual(stack.subviews[1].node.children, AnyEquatable("43")) XCTAssertEqual(stack.subviews[1].node.children, AnyEquatable("43"))
XCTAssertEqual(stack.subviews[3].node.children, AnyEquatable("44")) XCTAssertEqual(stack.subviews[3].node.children, AnyEquatable("44"))
XCTAssertEqual(stack.subviews[5].node.children, AnyEquatable("45"))
e.fulfill() e.fulfill()
} }