Skip to content

Commit

Permalink
added generic elements
Browse files Browse the repository at this point in the history
  • Loading branch information
Lazar Otasevic committed Sep 5, 2024
1 parent 6e9b298 commit c3331a2
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 135 deletions.
3 changes: 0 additions & 3 deletions Sources/ViewInspection/Elements/ArrayToElements.swift

This file was deleted.

1 change: 1 addition & 0 deletions Sources/ViewInspection/Elements/ReflectionElement.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
protocol ReflectionElement {
var node: ReflectionNode { get }
init(node: ReflectionNode)
static func isValid(_ node: ReflectionNode) -> Bool
}
84 changes: 52 additions & 32 deletions Sources/ViewInspection/Elements/TestElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,101 @@ import SwiftUI

enum TestElement {
enum View {
typealias _Text = TypeDerivedElement<Text>
typealias _Image = TypeDerivedElement<Image>
typealias _Button = TypeDerivedElement<Button<AnyView>>
typealias _Toggle = TypeDerivedElement<Toggle<AnyView>>
typealias _GeometryReader = TypeDerivedElement<GeometryReader<AnyView>>
typealias _VStack = TypeDerivedElement<VStack<AnyView>>
typealias _HStack = TypeDerivedElement<HStack<AnyView>>
typealias _ForEach = TypeDerivedElement<ForEach<Data, Int, AnyView>>
@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
typealias _NavigationStack = TypeDerivedElement<NavigationStack<Data, AnyView>>
typealias _Text = SameTypeElement<Text>
typealias _Image = SameTypeElement<Image>
typealias _Button = SameBaseElement<Button<AnyView>>
typealias _Toggle = SameBaseElement<Toggle<AnyView>>
typealias _GeometryReader = SameBaseElement<GeometryReader<AnyView>>
typealias _VStack = SameBaseElement<VStack<AnyView>>
typealias _HStack = SameBaseElement<HStack<AnyView>>
typealias _ForEach = SameBaseElement<ForEach<Data, Int, AnyView>>
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
typealias _NavigationStack = SameBaseElement<NavigationStack<Data, AnyView>>
}

enum Modifier {
struct _Refreshable: ReflectionElement { let node: ReflectionNode }
struct _Task: ReflectionElement { let node: ReflectionNode }
struct _OnAppear: ReflectionElement { let node: ReflectionNode }
struct _OnTap: ReflectionElement { let node: ReflectionNode }
struct _Refreshable: ModifierDerivedElement {
let node: ReflectionNode
static func makeModifiedContent() -> Any { EmptyView().refreshable {} }
}

struct _Task: ModifierDerivedElement {
let node: ReflectionNode
static func makeModifiedContent() -> Any { EmptyView().task {} }
}

struct _OnAppear: ModifierDerivedElement {
let node: ReflectionNode
static func makeModifiedContent() -> Any { EmptyView().onAppear {} }
}

struct _OnDisappear: ModifierDerivedElement {
let node: ReflectionNode
static func makeModifiedContent() -> Any { EmptyView().onDisappear {} }
}

struct _OnTap: ModifierDerivedElement {
let node: ReflectionNode
static func makeModifiedContent() -> Any { EmptyView().onTapGesture {} }
}
}

enum PropertyWrapper {
final class DummyObservableObject: ObservableObject {}
typealias _State = TypeDerivedElement<State<Any>>
typealias _Binding = TypeDerivedElement<Binding<Any>>
typealias _Environment = TypeDerivedElement<Environment<Any>>
typealias _EnvironmentObject = TypeDerivedElement<EnvironmentObject<DummyObservableObject>>
typealias _State = SameBaseElement<State<Any>>
typealias _Binding = SameBaseElement<Binding<Any>>
typealias _Environment = SameBaseElement<Environment<Any>>
typealias _EnvironmentObject = SameBaseElement<EnvironmentObject<DummyObservableObject>>
}

enum Value {
typealias _Bool = TypeDerivedElement<Bool>
typealias _String = TypeDerivedElement<String>
typealias _closure = TypeDerivedElement<() -> Void>
typealias _voidParamClosure = TypeDerivedElement<(()) -> Void>
typealias _asyncClosure = TypeDerivedElement<() async -> Void>
typealias _Bool = SameTypeElement<Bool>
typealias _String = SameTypeElement<String>
typealias _closure = ClosureElement<() -> Void>
typealias _voidParamClosure = ClosureElement<(()) -> Void>
typealias _asyncClosure = ClosureElement<() async -> Void>
}
}

extension TestElement.Modifier._Refreshable {
func doRefresh() async {
await node.asyncActions[0].castValue()
await node.oneElement(TestElement.Value._asyncClosure.self).castValue()
}
}

extension TestElement.Modifier._Task {
func doTask() async {
await node.asyncActions[0].castValue()
await node.oneElement(TestElement.Value._asyncClosure.self).castValue()
}
}

extension TestElement.Modifier._OnAppear {
func doOnAppear() {
node.actions[0].castValue()
node.oneElement(TestElement.Value._closure.self).castValue()
}
}

extension TestElement.Modifier._OnTap {
func doTap() {
node.voidParamActions[0].castValue(())
node.oneElement(TestElement.Value._voidParamClosure.self).castValue(())
}
}

extension TestElement.View._Text {
var string: String {
node.strings[0].castValue
node.oneElement(TestElement.Value._String.self).castValue
}
}

extension TestElement.View._Image {
var name: String {
node.strings[0].castValue
var name: String? {
node.allElements(TestElement.Value._String.self).first { $0.node.label == "name" }?.castValue
}
}

extension TestElement.View._Button {
func tap() {
node.actions[0].castValue()
node.oneElement(TestElement.Value._closure.self).castValue()
}
}

Expand All @@ -87,7 +107,7 @@ extension TestElement.View._Toggle {
}

var isOn: Binding<Bool> {
let binding = node.bindings[0]
let binding = node.oneElement(TestElement.PropertyWrapper._Binding.self)
if let boolBinding = binding.node.object as? Binding<Bool> {
return boolBinding
}
Expand Down
59 changes: 41 additions & 18 deletions Sources/ViewInspection/Elements/TypeDerivedElement.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
struct TypeDerivedElement<T>: ReflectionElement {
let node: ReflectionNode
import SwiftUI

protocol TypeDerivedElement: ReflectionElement {
associatedtype RelatedType
}

extension TypeDerivedElement {
static var typeInfo: TypeInfo { TypeInfo(type: T.self) }

static func isSameType(_ node: ReflectionNode) -> Bool {
node.object is T
}

static func isSameBasetype(_ node: ReflectionNode) -> Bool {
node.typeInfo.baseTypename == typeInfo.baseTypename
}

static func isSameClosure(_ node: ReflectionNode) -> Bool {
node.typeInfo.typename.hasSuffix(typeInfo.typename)
}

var castValue: T {
CastingUtils.memoryCast(node.object)
static var typeInfo: TypeInfo { TypeInfo(type: RelatedType.self) }
}

protocol CastableTypeDerivedElement: TypeDerivedElement {
var castValue: RelatedType { get }
}

extension CastableTypeDerivedElement {
var castValue: RelatedType { CastingUtils.memoryCast(node.object) }
}

struct SameBaseElement<T>: TypeDerivedElement {
let node: ReflectionNode
typealias RelatedType = T
static func isValid(_ node: ReflectionNode) -> Bool { node.typeInfo.baseTypename == typeInfo.baseTypename }
}

protocol ModifierDerivedElement: ReflectionElement {
static func makeModifiedContent() -> Any
}

extension ModifierDerivedElement {
static func isValid(_ node: ReflectionNode) -> Bool {
let modChild = ReflectionNode(object: makeModifiedContent()).children[1]
return node.typeInfo.typename == modChild.typeInfo.typename
}
}

struct SameTypeElement<T>: CastableTypeDerivedElement {
let node: ReflectionNode
typealias RelatedType = T
static func isValid(_ node: ReflectionNode) -> Bool { node.object is T }
}

struct ClosureElement<T>: CastableTypeDerivedElement {
let node: ReflectionNode
typealias RelatedType = T
static func isValid(_ node: ReflectionNode) -> Bool { node.typeInfo.typename.hasSuffix(typeInfo.typename) }
}
70 changes: 6 additions & 64 deletions Sources/ViewInspection/Reflection/ReflectionNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,72 +26,14 @@ extension ReflectionNode {
var allNodes: [ReflectionNode] {
children.reduce([self]) { $0 + $1.allNodes }
}

func basetypeElements<T>() -> [TypeDerivedElement<T>] {
allNodes.filter(TypeDerivedElement<T>.isSameBasetype).toElements()
}

func typeElements<T>() -> [TypeDerivedElement<T>] {
allNodes.filter(TypeDerivedElement<T>.isSameType).toElements()
}

func closureElements<T>() -> [TypeDerivedElement<T>] {
allNodes.filter(TypeDerivedElement<T>.isSameClosure).toElements()
}

var toggles: [TestElement.View._Toggle] { basetypeElements() }

var buttons: [TestElement.View._Button] { basetypeElements() }

var environments: [TestElement.PropertyWrapper._Environment] { basetypeElements() }

var states: [TestElement.PropertyWrapper._State] { basetypeElements() }

var bindings: [TestElement.PropertyWrapper._Binding] { basetypeElements() }

var texts: [TestElement.View._Text] { typeElements() }

var strings: [TestElement.Value._String] { typeElements() }

var images: [TestElement.View._Image] { typeElements() }

var voidParamActions: [TestElement.Value._voidParamClosure] { closureElements() }

var actions: [TestElement.Value._closure] { closureElements() }

var asyncActions: [TestElement.Value._asyncClosure] { closureElements() }

var refreshableModifiers: [TestElement.Modifier._Refreshable] {
func isModifier(_ ref: ReflectionNode) -> Bool {
ref.typeInfo.baseTypename.hasPrefix("SwiftUI.") && ref.typeInfo.baseTypename.hasSuffix(".RefreshableModifier")
}
return allNodes.filter(isModifier).toElements()
}

var taskModifiers: [TestElement.Modifier._Task] {
func isModifier(_ ref: ReflectionNode) -> Bool {
ref.typeInfo.baseTypename.hasPrefix("SwiftUI.") && ref.typeInfo.baseTypename.hasSuffix("._TaskModifier")
}
return allNodes.filter(isModifier).toElements()
}

var onAppearModifiers: [TestElement.Modifier._OnAppear] {
func isModifier(_ ref: ReflectionNode) -> Bool {
ref.typeInfo.baseTypename.hasPrefix("SwiftUI.") && ref.typeInfo.baseTypename.hasSuffix("._AppearanceActionModifier")
}
return allNodes.filter(isModifier).toElements()
func allElements<CP: ReflectionElement>(_ t: CP.Type = CP.self) -> [CP] {
allNodes.filter(CP.isValid).map(CP.init)
}

var onTapModifiers: [TestElement.Modifier._OnTap] {
func isModifier(_ ref: ReflectionNode) -> Bool {
ref.typeInfo.typename == "SwiftUI._EndedGesture<SwiftUI.TapGesture>"
}
return allNodes.filter(isModifier).toElements()
func oneElement<CP: ReflectionElement>(_ t: CP.Type = CP.self) -> CP {
let arr: [CP] = allElements()
assert(arr.count == 1)
return arr[0]
}

var geometryReaders: [TestElement.View._GeometryReader] { basetypeElements() }
var forEachs: [TestElement.View._ForEach] { basetypeElements() }

@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
var navigationStacks: [TestElement.View._NavigationStack] { basetypeElements() }
}
4 changes: 2 additions & 2 deletions Tests/ViewInspectionTests/InteractiveViewElementsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ final class InteractiveViewElementsTests: XCTestCase {}
@MainActor extension InteractiveViewElementsTests {
func testToggle() {
let b = Binding<Bool>.variable(false)
Toggle("", isOn: b).reflectionSnapshot.toggles[0].toggle()
Toggle("", isOn: b).reflectionSnapshot.oneElement(TestElement.View._Toggle.self).toggle()
XCTAssertEqual(b.wrappedValue, true)
}

func testButton() {
var b = false
Button("") { b = true }.reflectionSnapshot.buttons[0].tap()
Button("") { b = true }.reflectionSnapshot.oneElement(TestElement.View._Button.self).tap()
XCTAssertEqual(b, true)
}
}
8 changes: 4 additions & 4 deletions Tests/ViewInspectionTests/ModifierElementsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ final class ModifierElementsTests: XCTestCase {}
@MainActor extension ModifierElementsTests {
func testRefreshable() async {
var x = 0
await EmptyView().refreshable { x = 1 }.reflectionSnapshot.refreshableModifiers[0].doRefresh()
await EmptyView().refreshable { x = 1 }.reflectionSnapshot.oneElement(TestElement.Modifier._Refreshable.self).doRefresh()
XCTAssert(x == 1)
}

func testTask() async {
var x = 0
await EmptyView().task { x = 1 }.reflectionSnapshot.taskModifiers[0].doTask()
await EmptyView().task { x = 1 }.reflectionSnapshot.oneElement(TestElement.Modifier._Task.self).doTask()
XCTAssert(x == 1)
}

func testOnAppear() {
var x = 0
EmptyView().onAppear { x = 1 }.reflectionSnapshot.onAppearModifiers[0].doOnAppear()
EmptyView().onAppear { x = 1 }.reflectionSnapshot.oneElement(TestElement.Modifier._OnAppear.self).doOnAppear()
XCTAssert(x == 1)
}

func testOnTap() {
var x = 0
EmptyView().onTapGesture { x = 1 }.reflectionSnapshot.onTapModifiers[0].doTap()
EmptyView().onTapGesture { x = 1 }.reflectionSnapshot.oneElement(TestElement.Modifier._OnTap.self).doTap()
XCTAssert(x == 1)
}
}
6 changes: 3 additions & 3 deletions Tests/ViewInspectionTests/PropertyWrappersTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ final class PropertyWrappersTests: XCTestCase {}
@Environment(\.dismiss) private var dismiss
let body = EmptyView()
}
let t = Dummy().reflectionSnapshot.environments
let t = Dummy().reflectionSnapshot.allElements(TestElement.PropertyWrapper._Environment.self)
XCTAssertEqual(t.count, 2)
XCTAssert(t[1].node.parent === t[0].node)
}
Expand All @@ -20,14 +20,14 @@ final class PropertyWrappersTests: XCTestCase {}
@State private var x = 0
let body = EmptyView()
}
XCTAssertEqual(Dummy().reflectionSnapshot.states.count, 1)
XCTAssertEqual(Dummy().reflectionSnapshot.allElements(TestElement.PropertyWrapper._State.self).count, 1)
}

func testBinding() {
struct Dummy: View {
@Binding var x: Int
let body = EmptyView()
}
XCTAssertEqual(Dummy(x: .constant(1)).reflectionSnapshot.bindings.count, 1)
XCTAssertEqual(Dummy(x: .constant(1)).reflectionSnapshot.allElements(TestElement.PropertyWrapper._Binding.self).count, 1)
}
}
Loading

0 comments on commit c3331a2

Please sign in to comment.