TokamakUI / Tokamak

SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms
Apache License 2.0
2.62k stars 111 forks source link

Replace `ViewDeferredToRenderer`, fix renderer tests #408

Closed MaxDesiatov closed 3 years ago

MaxDesiatov commented 3 years ago

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.