Fix crashes in views with optional content (#364)
* Add TokamakStaticHTMLTests target * Add AnyOptional, clarify conformances issues
This commit is contained in:
parent
163005dfe0
commit
67aea3cc3b
|
@ -11,7 +11,7 @@
|
|||
{
|
||||
"label": "swift test",
|
||||
"type": "shell",
|
||||
"command": "swift test"
|
||||
"command": "swift build --product TokamakPackageTests && `xcrun --find xctest` .build/debug/TokamakPackageTests.xctest"
|
||||
},
|
||||
{
|
||||
"label": "carton dev",
|
||||
|
|
|
@ -154,5 +154,14 @@ let package = Package(
|
|||
name: "TokamakTests",
|
||||
dependencies: ["TokamakTestRenderer"]
|
||||
),
|
||||
// FIXME: re-enable when `ViewDeferredToRenderer` conformance conflicts issue is resolved
|
||||
// Currently, when multiple modules that have conflicting `ViewDeferredToRenderer`
|
||||
// implementations are linked in the same binary, only a single one is used with no defined
|
||||
// behavior for that. We need to replace `ViewDeferredToRenderer` with a different solution
|
||||
// that isn't prone to these hard to debug errors.
|
||||
// .testTarget(
|
||||
// name: "TokamakStaticHTMLTests",
|
||||
// dependencies: ["TokamakStaticHTML"]
|
||||
// ),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -59,8 +59,16 @@ public struct AnyView: View {
|
|||
bodyType = V.Body.self
|
||||
self.view = view
|
||||
if view is ViewDeferredToRenderer {
|
||||
bodyClosure = {
|
||||
let deferredView: Any
|
||||
if let opt = $0 as? AnyOptional, let value = opt.value {
|
||||
deferredView = value
|
||||
} else {
|
||||
deferredView = $0
|
||||
}
|
||||
// swiftlint:disable:next force_cast
|
||||
bodyClosure = { ($0 as! ViewDeferredToRenderer).deferredBody }
|
||||
return (deferredView as! ViewDeferredToRenderer).deferredBody
|
||||
}
|
||||
} else {
|
||||
// swiftlint:disable:next force_cast
|
||||
bodyClosure = { AnyView(($0 as! V).body) }
|
||||
|
|
|
@ -62,6 +62,19 @@ extension Optional: View where Wrapped: View {
|
|||
}
|
||||
}
|
||||
|
||||
protocol AnyOptional {
|
||||
var value: Any? { get }
|
||||
}
|
||||
|
||||
extension Optional: AnyOptional {
|
||||
var value: Any? {
|
||||
switch self {
|
||||
case let .some(value): return value
|
||||
case .none: return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@_functionBuilder public enum ViewBuilder {
|
||||
public static func buildBlock() -> EmptyView { EmptyView() }
|
||||
|
||||
|
|
|
@ -59,25 +59,29 @@ private func bindAction(to entry: UnsafeMutablePointer<GtkWidget>, textBinding:
|
|||
extension SecureField: ViewDeferredToRenderer where Label == Text {
|
||||
public var deferredBody: AnyView {
|
||||
let proxy = _SecureFieldProxy(self)
|
||||
return AnyView(WidgetView(build: { _ in
|
||||
return AnyView(WidgetView(
|
||||
build: { _ in
|
||||
build(textBinding: proxy.textBinding, label: proxy.label, visible: false)
|
||||
},
|
||||
update: { w in
|
||||
guard case let .widget(entry) = w.storage else { return }
|
||||
update(entry: entry, textBinding: proxy.textBinding, label: proxy.label)
|
||||
}) {})
|
||||
}
|
||||
) {})
|
||||
}
|
||||
}
|
||||
|
||||
extension TextField: ViewDeferredToRenderer where Label == Text {
|
||||
public var deferredBody: AnyView {
|
||||
let proxy = _TextFieldProxy(self)
|
||||
return AnyView(WidgetView(build: { _ in
|
||||
return AnyView(WidgetView(
|
||||
build: { _ in
|
||||
build(textBinding: proxy.textBinding, label: proxy.label)
|
||||
},
|
||||
update: { a in
|
||||
guard case let .widget(widget) = a.storage else { return }
|
||||
update(entry: widget, textBinding: proxy.textBinding, label: proxy.label)
|
||||
}) {})
|
||||
}
|
||||
) {})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2020 Tokamak contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Created by Max Desiatov on 07/12/2018.
|
||||
//
|
||||
|
||||
import TokamakStaticHTML
|
||||
import XCTest
|
||||
|
||||
final class ReconcilerTests: XCTestCase {
|
||||
struct Model {
|
||||
let text: Text
|
||||
}
|
||||
|
||||
private struct OptionalBody: View {
|
||||
var model: Model?
|
||||
|
||||
var body: some View {
|
||||
if let text = model?.text {
|
||||
VStack {
|
||||
text
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testOptional() {
|
||||
let renderer = StaticHTMLRenderer(OptionalBody(model: Model(text: Text("text"))))
|
||||
|
||||
XCTAssertEqual(renderer.html.count, 2777)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue