Skip to content

Commit

Permalink
updated
Browse files Browse the repository at this point in the history
  • Loading branch information
omochi committed Apr 24, 2024
1 parent 94f43f0 commit 4ce6e54
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 75 deletions.
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import PackageDescription

// for development
let usesJavaScriptKitMockOnMac = false
let usesJavaScriptKitMockOnMac = true
let usesLocalJavaScriptKit = false

let javaScriptKitDependency: Package.Dependency = usesLocalJavaScriptKit ?
Expand Down
5 changes: 5 additions & 0 deletions Sources/React/Hooks/Effect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public struct Effect: _AnyEffectHook {
shouldExecute = false
return Task(object: self, setup: setup)
}

func cleanupTask() -> Task? {
if cleanup == nil { return nil }
return Task(object: self, setup: nil)
}
}

final class Task {
Expand Down
18 changes: 15 additions & 3 deletions Sources/React/Renderer/Instance.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SRTDOM

internal final class Instance {
package final class Instance {
final class ListenerBridge {
init() {}

Expand All @@ -13,13 +13,25 @@ internal final class Instance {
}

weak var owner: VNode?
var hooks: [any _AnyHookWrapper]?
var dom: JSNode?
var hooks: [any _AnyHookWrapper] = []
public var dom: JSNode?
var attributes: Attributes = [:]
var listeners: [String: ListenerBridge] = [:]
var contextValueHolder: ContextValueHolder?
var isDirty: Bool = false

var contextHooks: [any _AnyContextHook] {
hooks.compactMap { $0 as? any _AnyContextHook }
}

var stateHooks: [any _AnyStateHook] {
hooks.compactMap { $0 as? any _AnyStateHook }
}

var effectHooks: [any _AnyEffectHook] {
hooks.compactMap { $0 as? any _AnyEffectHook }
}

func renderDOMAttributes(
attributes newAttributes: Attributes
) throws {
Expand Down
77 changes: 40 additions & 37 deletions Sources/React/Renderer/ReactRoot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,14 @@ public final class ReactRoot {
if let newTree {
let oldInstance = oldTree?.instance
oldTree?.instance = nil

let instance = oldInstance ?? Instance()
newTree.instance = instance
let isFirst = oldInstance == nil

try renderDOM(tree: newTree, instance: instance)
try moveDOM(instance: instance)
try updateContextValue(tree: newTree, instance: instance)
prepareHooks(component: newTree.component, instance: instance)
prepareHooks(component: newTree.component, instance: instance, isFirst: isFirst)
subscribeHooks(instance: instance)

// short circuit
Expand All @@ -180,26 +180,13 @@ public final class ReactRoot {
try skipRenderChildren(newTree: newTree, oldTree: oldTree, isMove: isMove)
}

if newTree == nil {
if let oldTree {
try oldTree.dom?.remove()
}
}

if let newTree {
for effect in newTree.ghost.effects {
let object = effect.effectObject
if let task = object.taskIfShouldExecute() {
scheduleEffect(task)
}
if let instance = newTree.instance {
try postRender(instance: instance)
}
} else if let oldTree {
for effect in oldTree.ghost.effects {
let object = effect.effectObject
if let _ = object.cleanup {
let task = Effect.Task(object: object, setup: nil)
scheduleEffect(task)
}
if let instance = oldTree.instance {
try cleanup(instance: instance)
}
}
}
Expand Down Expand Up @@ -263,17 +250,17 @@ public final class ReactRoot {
holder.value = value
}

private func prepareHooks(component: any Component, instance: Instance) {
private func prepareHooks(component: any Component, instance: Instance, isFirst: Bool) {
let hooks = Components.extractHooks(component)

if let oldHooks = instance.hooks {
for (new, old) in zip(hooks, oldHooks) {
new._prepareAny(object: old.object)
}
} else {
if isFirst {
for hook in hooks {
hook._prepareAny(object: nil)
}
} else {
for (new, old) in zip(hooks, instance.hooks) {
new._prepareAny(object: old.object)
}
}

instance.hooks = hooks
Expand All @@ -286,27 +273,43 @@ public final class ReactRoot {
self.scheduleUpdate(instance: instance)
}

for hook in instance.hooks ?? [] {
switch hook {
case let context as any _AnyContextHook:
if let holder = contextValueHolders[ObjectIdentifier(context.valueType)] {
let dsp = holder.emitter.on(handler: updater)
context.setHolder(holder, disposable: dsp)
} else {
context.setHolder(nil, disposable: nil)
}
case let state as any _AnyStateHook:
state.setDidChange(updater)
default: break
for context in instance.contextHooks {
if let holder = contextValueHolders[ObjectIdentifier(context.valueType)] {
let dsp = holder.emitter.on(handler: updater)
context.setHolder(holder, disposable: dsp)
} else {
context.setHolder(nil, disposable: nil)
}
}

for state in instance.stateHooks {
state.setDidChange(updater)
}
}

private func isChanged(new: VNode, old: VNode?) -> Bool {
guard let newDeps = new.component.deps else { return false }
return newDeps != old?.component.deps
}

private func postRender(instance: Instance) throws {
for effect in instance.effectHooks {
if let task = effect.effectObject.taskIfShouldExecute() {
scheduleEffect(task)
}
}
}

private func cleanup(instance: Instance) throws {
try instance.dom?.remove()

for effect in instance.effectHooks {
if let task = effect.effectObject.cleanupTask() {
scheduleEffect(task)
}
}
}

private func buildContextValueHolders(for node: VNode) -> [ObjectIdentifier: ContextValueHolder] {
var result: [ObjectIdentifier: ContextValueHolder] = [:]

Expand Down
2 changes: 1 addition & 1 deletion Sources/React/VDOM/VNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ package final class VNode: Hashable {
public let component: any Component
public let equality: Equality

internal var instance: Instance? {
package var instance: Instance? {
get { _instance }
set {
_instance = newValue
Expand Down
24 changes: 8 additions & 16 deletions Sources/React/VDOM/VNodeEx.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import SRTDOM

extension VNode {
public var tagElement: HTMLElement? {
ghost.component as? HTMLElement
public var htmlElement: HTMLElement? {
component as? HTMLElement
}

package var textElement: TextElement? {
ghost.component as? TextElement
}

public var domTag: JSHTMLElement? {
dom?.asHTMLElement()
}

public var domText: JSText? {
dom?.asText()
public var textElement: TextElement? {
component as? TextElement
}

public var parentTagNode: VNode? {
Expand All @@ -25,7 +17,7 @@ extension VNode {
return nil
}

if node.tagElement != nil {
if node.htmlElement != nil {
return node
}

Expand All @@ -48,15 +40,15 @@ extension VNode {
if let found = node.find(
direction: .left,
predicate: { (node) in
node.tagElement != nil ||
node.htmlElement != nil ||
node.textElement != nil
}
) {
return found
}
}

if parent.tagElement != nil {
if parent.htmlElement != nil {
return nil
}

Expand All @@ -69,7 +61,7 @@ extension VNode {
var doms: [JSNode] = []

walk { (node) in
if let dom = node.dom {
if let dom = node.instance?.dom {
doms.append(dom)
return .skipChildren
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/ReactTests/RefHookTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ final class RefHookTests: XCTestCase {

let btn: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "button" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "button" }?
.instance?.dom?.asHTMLElement()
)

XCTAssertEqual(refs.count, 2)
Expand Down Expand Up @@ -88,8 +88,8 @@ final class RefHookTests: XCTestCase {

let btn: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "button" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "button" }?
.instance?.dom?.asHTMLElement()
)

XCTAssertEqual(refs.count, 2)
Expand Down
8 changes: 4 additions & 4 deletions Tests/ReactTests/RenderPlanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ final class RenderPlanTests: XCTestCase {

let btn: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "button" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "button" }?
.instance?.dom?.asHTMLElement()
)
try btn.click()

Expand Down Expand Up @@ -148,8 +148,8 @@ final class RenderPlanTests: XCTestCase {

let section: Section = try XCTUnwrap(
root.root?
.find { $0.ghost.component is Section }?
.ghost.component as? Section
.find { $0.component is Section }?
.component as? Section
)
XCTAssertEqual(evs, ["c", "s"])

Expand Down
12 changes: 6 additions & 6 deletions Tests/ReactTests/RenderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,8 @@ final class RenderTests: XCTestCase {

let divDom0: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "div" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "div" }?
.instance?.dom?.asHTMLElement()
)

let MouseEvent = JSWindow.global.MouseEvent
Expand All @@ -266,8 +266,8 @@ final class RenderTests: XCTestCase {
// update check
let divDom1: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "div" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "div" }?
.instance?.dom?.asHTMLElement()
)
XCTAssertEqual(divDom0, divDom1)
_ = consume divDom1
Expand All @@ -289,8 +289,8 @@ final class RenderTests: XCTestCase {
// update check
let divDom2: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "div" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "div" }?
.instance?.dom?.asHTMLElement()
)
XCTAssertEqual(divDom0, divDom2)
_ = consume divDom2
Expand Down
4 changes: 2 additions & 2 deletions Tests/ReactTests/StateHookTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ final class StateHookTests: XCTestCase {

let btn: JSHTMLElement = try XCTUnwrap(
root.root?
.find { $0.tagElement?.tagName == "button" }?
.dom?.asHTMLElement()
.find { $0.htmlElement?.tagName == "button" }?
.instance?.dom?.asHTMLElement()
)
try btn.click()

Expand Down
19 changes: 19 additions & 0 deletions Tests/ReactTests/VNodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@ import XCTest
import SRTTestSupport
import React

extension VNode {
fileprivate static func tag(
_ name: String,
_ attributes: Attributes = [:],
_ children: [Node] = []
) -> VNode {
let tag = HTMLElement(
tagName: name,
attributes: attributes,
children: children
)
return VNode(component: tag)
}

fileprivate static func component(_ c: any Component) -> VNode {
VNode(component: c)
}
}

final class VNodeTests: XCTestCase {

struct AView: Component {
Expand Down
10 changes: 9 additions & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ $ bin/test

### Host Execution Mode via Swift CLI

Like a typical Swift Package, you execute tests on the host machine with the following command:
In this mode, tests connect to a mock library that emulates the DOM.

To enable the mock, please edit Package.swift as follows:

```swift
let usesJavaScriptKitMockOnMac = true
```

After that, just like a normal Swift package, you execute tests on the host machine with the following command:

```sh
$ swift test
Expand Down

0 comments on commit 4ce6e54

Please sign in to comment.