Skip to content

Commit

Permalink
Add support for WKInterfaceObjectRepresentable
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Naumov committed Sep 18, 2021
1 parent 28fc167 commit 3bcc2bc
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 8 deletions.
16 changes: 16 additions & 0 deletions Sources/ViewInspector/SwiftUI/CustomView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,20 @@ public extension Inspectable where Self: UIViewControllerRepresentable {
"Please use `.actualView().viewController()` for inspecting the contents of UIViewControllerRepresentable")
}
}
#elseif os(watchOS)

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
public extension WKInterfaceObjectRepresentable where Self: Inspectable {
func interfaceObject() throws -> WKInterfaceObjectType {
return try ViewHosting.lookup(Self.self)
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
public extension Inspectable where Self: WKInterfaceObjectRepresentable {
func extractContent(environmentObjects: [AnyObject]) throws -> Any {
throw InspectionError.notSupported(
"Please use `.actualView().interfaceObject()` for inspecting the contents of WKInterfaceObjectRepresentable")
}
}
#endif
31 changes: 31 additions & 0 deletions Sources/ViewInspector/ViewHosting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,40 @@ internal extension ViewHosting {
.first else { throw InspectionError.viewNotFound(parent: name) }
return vc
}
#elseif os(watchOS)
static func lookup<V>(_ view: V.Type) throws -> V.WKInterfaceObjectType
where V: Inspectable & WKInterfaceObjectRepresentable {
let name = Inspector.typeName(type: view)
guard let rootVC = WKExtension.shared().rootInterfaceController,
let viewCache = try? Inspector.attribute(path: """
super|$__lazy_storage_$_hostingController|some|\
host|renderer|renderer|some|viewCache|map
""", value: rootVC, type: ArrayConvertible.self).allValues(),
let object = viewCache.compactMap({ value in
try? Inspector.attribute(
path: "view|representedViewProvider",
value: value, type: V.WKInterfaceObjectType.self)
}).first
else {
throw InspectionError.viewNotFound(parent: name)
}
return object
}
#endif
}

#if os(watchOS)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
internal protocol ArrayConvertible {
func allValues() -> [Any]
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
extension Dictionary: ArrayConvertible {
func allValues() -> [Any] { Array(values) as [Any] }
}
#endif

#if os(macOS)
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
private extension NSView {
Expand Down
39 changes: 31 additions & 8 deletions Tests/ViewInspectorTests/SwiftUI/CustomViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,29 +66,50 @@ final class CustomViewTests: XCTestCase {
wait(for: [exp], timeout: 0.1)
}

@available(watchOS, deprecated: 7.0)
func testExtractionOfTestViewRepresentable() throws {
let view = AnyView(TestViewRepresentable())
let sut = try view.inspect().anyView().view(TestViewRepresentable.self)
XCTAssertNoThrow(try sut.actualView())
#if os(macOS)
XCTAssertThrows(try sut.hStack(),
"Please use `.actualView().nsView()` for inspecting the contents of NSViewRepresentable")
// Needs view hosting:
XCTAssertThrows(try sut.actualView().nsView(),
"View for TestViewRepresentable is absent")
#elseif os(watchOS)
XCTAssertThrows(try sut.hStack(),
"Please use `.actualView().interfaceObject()` for inspecting the contents of WKInterfaceObjectRepresentable")
// Needs view hosting:
XCTAssertThrows(try sut.actualView().interfaceObject(),
"View for TestViewRepresentable is absent")
#else
XCTAssertThrows(try sut.hStack(),
"Please use `.actualView().uiView()` for inspecting the contents of UIViewRepresentable")
// Needs view hosting:
XCTAssertThrows(try sut.actualView().uiView(),
"View for TestViewRepresentable is absent")
#endif
}

func testExtractionOfViewControllerRepresentable() throws {
#if !os(watchOS)
let view = AnyView(TestViewControllerRepresentable())
let sut = try view.inspect().anyView().view(TestViewControllerRepresentable.self)
XCTAssertNoThrow(try sut.actualView())
#if os(macOS)
XCTAssertThrows(try sut.hStack(),
"Please use `.actualView().viewController()` for inspecting the contents of NSViewControllerRepresentable")
// Needs view hosting:
XCTAssertThrows(try sut.actualView().viewController(),
"View for TestViewControllerRepresentable is absent")
#else
XCTAssertThrows(try sut.hStack(),
"Please use `.actualView().viewController()` for inspecting the contents of UIViewControllerRepresentable")
// Needs view hosting:
XCTAssertThrows(try sut.actualView().viewController(),
"View for TestViewControllerRepresentable is absent")
#endif
#endif
}

Expand Down Expand Up @@ -282,17 +303,19 @@ private struct TestViewControllerRepresentable: UIViewControllerRepresentable, I
}
}
#elseif os(watchOS)
// !!!
struct TestViewRepresentable: View, Inspectable {
var body: some View {
EmptyView()

@available(watchOS, deprecated: 7.0)
private struct TestViewRepresentable: WKInterfaceObjectRepresentable, Inspectable {

typealias Context = WKInterfaceObjectRepresentableContext<TestViewRepresentable>
func makeWKInterfaceObject(context: Context) -> some WKInterfaceObject {
return WKInterfaceMap()
}
}
struct TestViewControllerRepresentable: View, Inspectable {
var body: some View {
EmptyView()

func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceObjectType, context: Context) {
}
}

#endif

// MARK: - Misc
Expand Down
65 changes: 65 additions & 0 deletions Tests/ViewInspectorTests/ViewHostingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,39 @@ final class ViewHostingTests: XCTestCase {
wait(for: [exp], timeout: 0.2)
}
}
#elseif os(watchOS)

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
@available(watchOS, deprecated: 7.0)
final class ViewHostingTests: XCTestCase {

func testWKTestView() throws {
let exp = XCTestExpectation(description: #function)
exp.expectedFulfillmentCount = 2
var sut = WKTestView.WrapperView(didUpdate: {
exp.fulfill()
})
sut.didAppear = { view in
ViewHosting.expel()
}
ViewHosting.host(view: sut)
wait(for: [exp], timeout: 0.1)
}

func testWKViewExtraction() throws {
let exp = XCTestExpectation(description: #function)
let sut = WKTestView(didUpdate: { })
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
sut.inspect { view in
XCTAssertNoThrow(try view.actualView().interfaceObject())
ViewHosting.expel()
exp.fulfill()
}
}
ViewHosting.host(view: sut)
wait(for: [exp], timeout: 1)
}
}
#endif

// MARK: - Test Views
Expand Down Expand Up @@ -228,6 +261,38 @@ extension UITestView {
}
}
}
#elseif os(watchOS)

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
@available(watchOS, deprecated: 7.0)
private struct WKTestView: WKInterfaceObjectRepresentable, Inspectable {

var didUpdate: () -> Void

typealias Context = WKInterfaceObjectRepresentableContext<WKTestView>
func makeWKInterfaceObject(context: Context) -> some WKInterfaceObject {
return WKInterfaceMap()
}

func updateWKInterfaceObject(_ wkInterfaceObject: WKInterfaceObjectType, context: Context) {
didUpdate()
}
}

@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 7.0, *)
@available(watchOS, deprecated: 7.0)
extension WKTestView {
struct WrapperView: View, Inspectable {

var didAppear: ((Self) -> Void)?
var didUpdate: () -> Void

var body: some View {
WKTestView(didUpdate: didUpdate)
.onAppear { self.didAppear?(self) }
}
}
}
#endif

#if os(macOS)
Expand Down

0 comments on commit 3bcc2bc

Please sign in to comment.