SwiftUI-Introspect/Tests/Tests/TestUtils.swift

111 lines
3.4 KiB
Swift

import SwiftUI
import XCTest
#if canImport(UIKit)
enum TestUtils {
private static let window = UIWindow(frame: UIScreen.main.bounds)
static func present(view: some View) {
window.rootViewController = UIHostingController(rootView: view)
window.makeKeyAndVisible()
window.layoutIfNeeded()
}
}
#elseif canImport(AppKit)
enum TestUtils {
static func present(view: some View) {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered,
defer: true
)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: view)
window.makeKeyAndOrderFront(nil)
window.layoutIfNeeded()
}
}
#endif
func XCTAssertViewIntrospection<V: View, PV: AnyObject>(
of type: PV.Type,
@ViewBuilder view: (Spies<PV>) -> V,
extraAssertions: ([PV]) -> Void = { _ in },
file: StaticString = #file,
line: UInt = #line
) {
let spies = Spies<PV>()
let view = view(spies)
TestUtils.present(view: view)
XCTWaiter(delegate: spies).wait(for: spies.expectations.values.map(\.0), timeout: 3)
extraAssertions(spies.objects.sorted(by: { $0.key < $1.key }).map(\.value))
}
final class Spies<PV: AnyObject>: NSObject, XCTWaiterDelegate {
private(set) var objects: [Int: PV] = [:]
private(set) var expectations: [ObjectIdentifier: (XCTestExpectation, StaticString, UInt)] = [:]
subscript(
number: Int,
file: StaticString = #file,
line: UInt = #line
) -> (PV) -> Void {
let expectation = XCTestExpectation()
expectations[ObjectIdentifier(expectation)] = (expectation, file, line)
return { [self] in
if let object = objects[number] {
XCTAssert(object === $0, "Found view was overriden by another view", file: file, line: line)
}
objects[number] = $0
expectation.fulfill()
}
}
func waiter(
_ waiter: XCTWaiter,
didTimeoutWithUnfulfilledExpectations unfulfilledExpectations: [XCTestExpectation]
) {
for expectation in unfulfilledExpectations {
let (_, file, line) = expectations[ObjectIdentifier(expectation)]!
XCTFail("Spy not called", file: file, line: line)
}
}
func nestedWaiter(
_ waiter: XCTWaiter,
wasInterruptedByTimedOutWaiter outerWaiter: XCTWaiter
) {
XCTFail("wasInterruptedByTimedOutWaiter")
}
func waiter(
_ waiter: XCTWaiter,
fulfillmentDidViolateOrderingConstraintsFor expectation: XCTestExpectation,
requiredExpectation: XCTestExpectation
) {
XCTFail("fulfillmentDidViolateOrderingConstraintsFor")
}
func waiter(
_ waiter: XCTWaiter,
didFulfillInvertedExpectation expectation: XCTestExpectation
) {
XCTFail("didFulfillInvertedExpectation")
}
}
extension Collection {
subscript(safe index: Index, file: StaticString = #file, line: UInt = #line) -> Element? {
get {
guard indices.contains(index) else {
XCTFail("Index \(index) is out of bounds", file: file, line: line)
return nil
}
return self[index]
}
}
}