104 lines
3.1 KiB
Swift
104 lines
3.1 KiB
Swift
// Copyright 2022 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 Carson Katri on 6/30/22.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
@_spi(TokamakCore)
|
|
import TokamakCore
|
|
|
|
/// A proxy for an identified view in the `TestFiberRenderer`.
|
|
///
|
|
/// The properties are evaluated on access,
|
|
/// so you will never unintentionally access an `alternate` value.
|
|
@dynamicMemberLookup
|
|
public struct TestViewProxy<V: View> {
|
|
/// The id to lookup.
|
|
let id: AnyHashable
|
|
|
|
/// The active reconciler instance to search in.
|
|
let reconciler: FiberReconciler<TestFiberRenderer>
|
|
|
|
/// Searches for a `Fiber` representing `id`.
|
|
///
|
|
/// - Note: This returns the child of the `identified(by:)` modifier,
|
|
/// not the `IdentifiedView` itself.
|
|
@_spi(TokamakCore)
|
|
public var fiber: FiberReconciler<TestFiberRenderer>.Fiber? {
|
|
let id = AnyHashable(id)
|
|
let result = TokamakCore.walk(
|
|
reconciler.current
|
|
) { fiber -> WalkWorkResult<FiberReconciler<TestFiberRenderer>.Fiber?> in
|
|
guard case let .view(view, _) = fiber.content,
|
|
!(view is AnyOptional),
|
|
(view as? IdentifiedViewProtocol)?.id == AnyHashable(id),
|
|
let child = fiber.child
|
|
else { return WalkWorkResult.continue }
|
|
return WalkWorkResult.break(with: child)
|
|
}
|
|
guard case let .success(fiber) = result else { return nil }
|
|
return fiber
|
|
}
|
|
|
|
/// The `fiber`'s content casted to `V`.
|
|
public var view: V? {
|
|
guard case let .view(view, _) = fiber?.content else { return nil }
|
|
return view as? V
|
|
}
|
|
|
|
/// Access properties from the `view` without specifying `.view` manually.
|
|
public subscript<T>(dynamicMember member: KeyPath<V, T>) -> T? {
|
|
self.view?[keyPath: member]
|
|
}
|
|
}
|
|
|
|
/// An erased `IdentifiedView`.
|
|
protocol IdentifiedViewProtocol {
|
|
var id: AnyHashable { get }
|
|
}
|
|
|
|
/// A wrapper that identifies a `View` in a test.
|
|
struct IdentifiedView<Content: View>: View, IdentifiedViewProtocol {
|
|
let id: AnyHashable
|
|
let content: Content
|
|
|
|
var body: some View {
|
|
content
|
|
}
|
|
}
|
|
|
|
public extension View {
|
|
/// Identifies a `View` in a test.
|
|
///
|
|
/// You can access this view from the `FiberReconciler` with `findView(id:as:)`.
|
|
func identified<ID: Hashable>(by id: ID) -> some View {
|
|
IdentifiedView(id: id, content: self)
|
|
}
|
|
}
|
|
|
|
public extension FiberReconciler where Renderer == TestFiberRenderer {
|
|
/// Find the `View` identified by `ID`.
|
|
///
|
|
/// - Note: This returns a proxy to the child of the `identified(by:)` modifier,
|
|
/// not the `IdentifiedView` itself.
|
|
func findView<ID: Hashable, V: View>(
|
|
id: ID,
|
|
as type: V.Type = V.self
|
|
) -> TestViewProxy<V> {
|
|
TestViewProxy<V>(id: id, reconciler: self)
|
|
}
|
|
}
|