Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace ViewDeferredToRenderer, fix renderer tests #408

Merged
merged 11 commits into from
Jun 7, 2021

Conversation

MaxDesiatov
Copy link
Collaborator

@MaxDesiatov MaxDesiatov commented May 30, 2021

This allows writing tests for TokamakStaticHTML, TokamakDOM, and TokamakGTK targets.

The issue was caused by conflicting ViewDeferredToRenderer conformances declared in different modules, including the TokamakTestRenderer module.

This works around a general limitation in Swift, which was discussed at length on Swift Forums previously. When multiple conflicting conformances to the same protocol (ViewDeferredToRenderer in our case) exist in different modules, only one of them is available in a given binary (even a test binary). Also, only of them can be loaded and used. Which one exactly is loaded can't be known at compile-time, which is hard to debug and leads to breaking tests that cover code in different renderers. We had to disable TokamakStaticHTMLTests for this reason.

The workaround is to declare two new functions in the Renderer protocol:

public protocol Renderer: AnyObject {
  // ...
  // Functions unrelated to the issue at hand skipped for brevity.

  /** Returns a body of a given pritimive view, or `nil` if `view` is not a primitive view for
   this renderer.
   */
  func body(for view: Any) -> AnyView?

  /** Returns `true` if a given view type is a primitive view that should be deferred to this
   renderer.
   */
  func isPrimitiveView(_ type: Any.Type) -> Bool
}

Now each renderer can declare their own protocols for their primitive views, i.e. HTMLPrimitive, DOMPrimitive, GTKPrimitive etc, delegating to them from the implementations of body(for view:) and isPrimitiveView(_:). Conformances to these protocols can't conflict across different modules. Also, these protocols can have internal visibility, as opposed to ViewDeferredToRenderer, which had to be declared as public in TokamakCore to be visible in renderer modules.

@MaxDesiatov MaxDesiatov added the refactor No user-visible functionality change label May 30, 2021
@MaxDesiatov MaxDesiatov changed the title Replace ViewDeferredToRenderer, fix renderer tests Replace ViewDeferredToRenderer, fix renderer tests Jun 6, 2021
@MaxDesiatov MaxDesiatov marked this pull request as ready for review June 6, 2021 20:23
@@ -30,7 +30,7 @@ final class MountedApp<R: Renderer>: MountedCompositeElement<R> {
// They also have no parents, so the `parent` argument is discarded as well.
let childBody = reconciler.render(mountedApp: self)

let child: MountedElement<R> = mountChild(childBody)
let child: MountedElement<R> = mountChild(reconciler.renderer, childBody)
Copy link
Collaborator Author

@MaxDesiatov MaxDesiatov Jun 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderer instances now need to be explicitly passed through in a lot more places. This allows mounted elements to call back to the renderer to determine whether a given view is primitive for this renderer.

render(compositeElement: compositeView, body: \.view.view, result: \.view.bodyClosure)
let view = body(of: compositeView, keyPath: \.view.view)

guard let renderedBody = renderer.body(for: view) else {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only refactored ViewDeferredToRenderer for now. SceneDeferredToRenderer is kept as is to keep the PR small. And we didn't have problems with scenes (yet).

bodyClosure = { AnyView(($0 as! V).body) }
}
// swiftlint:disable:next force_cast
bodyClosure = { AnyView(($0 as! V).body) }
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So happy this mess in AnyView can be finally simplified!

@@ -48,7 +48,7 @@ public struct Button<Label>: View where Label: View {
}
}

public struct _Button<Label>: PrimitiveView where Label: View {
public struct _Button<Label>: _PrimitiveView where Label: View {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PrimitiveView is not meant to be re-exported by any renderer modules, making that explicit here with underscore naming.

@@ -18,6 +18,149 @@
import TokamakStaticHTML
import XCTest

private let expectedHTML =
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to use https://github.com/pointfreeco/swift-snapshot-testing here instead, but we're running these tests in the Wasm environment, and file access is tricky there. Hardcoding the test fixture here for now.

@MaxDesiatov MaxDesiatov requested a review from a team June 6, 2021 20:39
@MaxDesiatov MaxDesiatov added the test suite Changes to the framework's test suite label Jun 6, 2021
Copy link
Member

@carson-katri carson-katri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, excited to get some good testing going.

/** Returns a body of a given pritimive view, or `nil` if `view` is not a primitive view for
this renderer.
*/
func body(for view: Any) -> AnyView?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense for this to be called primitiveBody(for:)? Then it’s more clear that it only returns the body of a primitive view?

@MaxDesiatov MaxDesiatov requested a review from carson-katri June 7, 2021 15:33
@MaxDesiatov MaxDesiatov merged commit 5926e9f into main Jun 7, 2021
@MaxDesiatov MaxDesiatov deleted the deferred-refactor branch June 7, 2021 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
refactor No user-visible functionality change test suite Changes to the framework's test suite
Development

Successfully merging this pull request may close these issues.

2 participants