diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIApplication.h b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIApplication.h new file mode 100644 index 000000000..54a12f50e --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIApplication.h @@ -0,0 +1,26 @@ +// +// OpenSwiftUI+UIApplication.h +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#ifndef OpenSwiftUI_UIApplication_h +#define OpenSwiftUI_UIApplication_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_IOS + +#import + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +OPENSWIFTUI_EXPORT +UIContentSizeCategory _UIApplicationDefaultContentSizeCategory(); + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif /* OPENSWIFTUI_TARGET_OS_IOS */ + +#endif /* OpenSwiftUI_UIApplication_h */ diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIApplication.m b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIApplication.m new file mode 100644 index 000000000..d481bb69c --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIApplication.m @@ -0,0 +1,27 @@ +// +// OpenSwiftUI+UIApplication.m +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#include "OpenSwiftUI+UIApplication.h" + +#if OPENSWIFTUI_TARGET_OS_IOS +#include + +UIContentSizeCategory _UIApplicationDefaultContentSizeCategory() { + typedef UIContentSizeCategory (*Func)(Class, SEL); + SEL selector = NSSelectorFromString(@"_defaultContentSizeCategory"); + Func func = nil; + if ([UIApplication resolveClassMethod:selector]) { + IMP impl = class_getMethodImplementation(UIApplication.class, selector); + func = (Func)impl; + } + if (func == nil) { + return UIContentSizeCategoryLarge; + } + return func(UIApplication.class, selector); +} + +#endif /* OPENSWIFTUI_TARGET_OS_IOS */ diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h index 249771dae..91e60ba10 100644 --- a/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUI+UIColor.h @@ -28,6 +28,6 @@ BOOL _UIColorDependsOnTraitCollection(UIColor *color); OPENSWIFTUI_ASSUME_NONNULL_END -#endif +#endif /* OPENSWIFTUI_TARGET_OS_IOS */ #endif /* OpenSwiftUI_UIColor_h */ diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles+UIKit.h b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles+UIKit.h new file mode 100644 index 000000000..55888e56e --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles+UIKit.h @@ -0,0 +1,33 @@ +// +// OpenSwiftUITesting_Swizzles+UIKit.h +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#ifndef OpenSwiftUITesting_Swizzles_UIKit_h +#define OpenSwiftUITesting_Swizzles_UIKit_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_IOS +#include + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +@interface UIScreen (OpenSwiftUITesting_Swizzles) ++ (void)_performOpenSwiftUITestingOverrides; +- (CGFloat)_OpenSwiftUITesting_currentScreenScale; +- (BOOL)_OpenSwiftUITesting_wantsWideContentMargins; +@end + +@interface UICollectionView (OpenSwiftUITesting_Swizzles) ++ (void)_performOpenSwiftUITestingOverrides; +- (id)_OpenSwiftUITesting__viewAnimationsForCurrentUpdateWithCollectionViewAnimator:(id)arg1; +@end + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif /* OPENSWIFTUI_TARGET_OS_IOS */ + +#endif /* OpenSwiftUITesting_Swizzles_UIKit_h */ diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles+UIKit.m b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles+UIKit.m new file mode 100644 index 000000000..6478d2a5b --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles+UIKit.m @@ -0,0 +1,37 @@ +// +// OpenSwiftUITesting_Swizzles+UIKit.m +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#include "OpenSwiftUITesting_Swizzles+UIKit.h" +#include "OpenSwiftUITesting_Swizzles.h" + +#if OPENSWIFTUI_TARGET_OS_IOS + +@implementation UIScreen (OpenSwiftUITesting_Swizzles) ++ (void)_performOpenSwiftUITestingOverrides { + _SwizzleMethods(UIScreen.class, @selector(scale), @selector(_OpenSwiftUITesting_currentScreenScale)); + _SwizzleMethods(UIScreen.class, @selector(_wantsWideContentMargins), @selector(_OpenSwiftUITesting_wantsWideContentMargins)); +} + +- (CGFloat)_OpenSwiftUITesting_currentScreenScale { + return 2.0; +} + +- (BOOL)_OpenSwiftUITesting_wantsWideContentMargins { + return NO; +} +@end + +@implementation UICollectionView (OpenSwiftUITesting_Swizzles) ++ (void)_performOpenSwiftUITestingOverrides { + _SwizzleMethods(UIScreen.class, @selector(_viewAnimationsForCurrentUpdateWithCollectionViewAnimator:), @selector(_OpenSwiftUITesting__viewAnimationsForCurrentUpdateWithCollectionViewAnimator:)); +} + +- (id)_OpenSwiftUITesting__viewAnimationsForCurrentUpdateWithCollectionViewAnimator:(id)arg1 { + return nil; +} +@end +#endif diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles.h b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles.h new file mode 100644 index 000000000..75c64917e --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles.h @@ -0,0 +1,27 @@ +// +// OpenSwiftUITesting_Swizzles.h +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#ifndef OpenSwiftUITesting_Swizzles_h +#define OpenSwiftUITesting_Swizzles_h + +#include "OpenSwiftUIBase.h" + +OPENSWIFTUI_ASSUME_NONNULL_BEGIN + +#if OPENSWIFTUI_TARGET_OS_IOS + +OPENSWIFTUI_EXPORT +void _PerformTestingSwizzles(); + +OPENSWIFTUI_EXPORT +void _SwizzleMethods(Class class, SEL originalSelector, SEL swizzledSelector); + +#endif /* OPENSWIFTUI_TARGET_OS_IOS */ + +OPENSWIFTUI_ASSUME_NONNULL_END + +#endif /* OpenSwiftUITesting_Swizzles_h */ diff --git a/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles.m b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles.m new file mode 100644 index 000000000..e7f6c7b88 --- /dev/null +++ b/Sources/COpenSwiftUI/UIKit/OpenSwiftUITesting_Swizzles.m @@ -0,0 +1,37 @@ +// +// OpenSwiftUITesting_Swizzles.m +// COpenSwiftUI +// +// Audited for iOS 18.0 +// Status: Complete + +#include "OpenSwiftUITesting_Swizzles.h" + +#if OPENSWIFTUI_TARGET_OS_IOS +#include +#include "UIKit/OpenSwiftUITesting_Swizzles+UIKit.h" + +void _PerformTestingSwizzles() { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [UIScreen _performOpenSwiftUITestingOverrides]; + [UICollectionView _performOpenSwiftUITestingOverrides]; + }); +} + +void _SwizzleMethods(Class class, SEL originalSelector, SEL swizzledSelector) { + Method originalMethod = class_getInstanceMethod(class, originalSelector); + Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); + IMP swizzledImp = method_getImplementation(swizzledMethod); + const char *swizzledTypeEncoding = method_getTypeEncoding(swizzledMethod); + BOOL success = class_addMethod(class, originalSelector, swizzledImp, swizzledTypeEncoding); + if (success) { + IMP originalImp = method_getImplementation(originalMethod); + const char *originalTypeEncoding = method_getTypeEncoding(originalMethod); + class_replaceMethod(class, swizzledSelector, originalImp, originalTypeEncoding); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } +} + +#endif /* OPENSWIFTUI_TARGET_OS_IOS */ diff --git a/Sources/OpenSwiftUI/CoreGlue/OpenSwiftUIGlue.swift b/Sources/OpenSwiftUI/CoreGlue/OpenSwiftUIGlue.swift index 7ef5405d7..492c14f93 100644 --- a/Sources/OpenSwiftUI/CoreGlue/OpenSwiftUIGlue.swift +++ b/Sources/OpenSwiftUI/CoreGlue/OpenSwiftUIGlue.swift @@ -3,27 +3,94 @@ // OpenSwiftUI // // Audited for iOS 18.0 -// Status: Empty +// Status: WIP @_spi(ForOpenSwiftUIOnly) public import OpenSwiftUICore +import COpenSwiftUI #if canImport(Darwin) +public import Foundation + @_spi(ForOpenSwiftUIOnly) @_silgen_name("OpenSwiftUIGlueClass") public func OpenSwiftUIGlueClass() -> CoreGlue.Type { OpenSwiftUIGlue.self } -final class OpenSwiftUIGlue: CoreGlue { - override var defaultImplicitRootType: DefaultImplicitRootTypeResult { +@_spi(ForOpenSwiftUIOnly) +@objc(OpenSwiftUIGlue) +final public class OpenSwiftUIGlue: CoreGlue { + override final public var defaultImplicitRootType: DefaultImplicitRootTypeResult { DefaultImplicitRootTypeResult(_VStackLayout.self) } - override func makeDefaultLayoutComputer() -> MakeDefaultLayoutComputerResult { + override final public func makeDefaultLayoutComputer() -> MakeDefaultLayoutComputerResult { MakeDefaultLayoutComputerResult(value: ViewGraph.current.$defaultLayoutComputer) } } +@_spi(ForOpenSwiftUIOnly) +@available(*, unavailable) +extension OpenSwiftUIGlue: Sendable {} + +@_spi(ForOpenSwiftUIOnly) +@_silgen_name("OpenSwiftUIGlue2Class") +public func OpenSwiftUIGlue2Class() -> CoreGlue2.Type { + OpenSwiftUIGlue2.self +} + +@_spi(ForOpenSwiftUIOnly) +@objc(OpenSwiftUIGlue2) +final public class OpenSwiftUIGlue2: CoreGlue2 { + #if os(iOS) + override public final func initializeTestApp() { + _PerformTestingSwizzles() + } + #endif + + override public final func isStatusBarHidden() -> Bool? { + #if os(iOS) + guard let scene = UIApplication.shared.connectedScenes.first, + let windowScene = scene as? UIWindowScene + else { + return nil + } + return windowScene.statusBarManager?.isStatusBarHidden ?? false + #else + nil + #endif + } + + override public final func configureDefaultEnvironment(_: inout EnvironmentValues) { + } + + override public final func makeRootView(base: AnyView, rootFocusScope: Namespace.ID) -> AnyView { + AnyView(base.safeAreaInsets(.zero, next: nil)) + } + + override public final var systemDefaultDynamicTypeSize: DynamicTypeSize { + #if os(iOS) + let size = _UIApplicationDefaultContentSizeCategory() + let dynamicSize = DynamicTypeSize(size) + return dynamicSize ?? .large + #else + // TODO: macOS + return .large + #endif + } + + override public final var codableAttachmentCellType: CodableAttachmentCellTypeResult { + CodableAttachmentCellTypeResult(nil) + } + + override public final func linkURL(_ parameters: LinkURLParameters) -> URL? { + preconditionFailure("TODO") + } +} + +@_spi(ForOpenSwiftUIOnly) +@available(*, unavailable) +extension OpenSwiftUIGlue2: Sendable {} #endif diff --git a/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UIColor.swift b/Sources/OpenSwiftUI/Integration/Graphic/UIKit/UIKitConversions.swift similarity index 82% rename from Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UIColor.swift rename to Sources/OpenSwiftUI/Integration/Graphic/UIKit/UIKitConversions.swift index 91792db06..823777298 100644 --- a/Sources/OpenSwiftUI/Integration/Graphic/UIKit/OpenSwiftUI+UIColor.swift +++ b/Sources/OpenSwiftUI/Integration/Graphic/UIKit/UIKitConversions.swift @@ -1,10 +1,10 @@ // -// OpenSwiftUI+UIKit.swift +// UIKitConversions.swift // OpenSwiftUI // // Audited for iOS 18.0 // Status: WIP -// ID: 6DC24D5146AF4B80347A1025025F68EE (SwiftUI?) +// ID: 6DC24D5146AF4B80347A1025025F68EE (SwiftUI) #if canImport(UIKit) @@ -12,7 +12,7 @@ public import OpenSwiftUICore public import UIKit import COpenSwiftUI -// MARK: - Color + UIColor +// MARK: - UIColor Conversions @available(*, deprecated, message: "Use Color(uiColor:) when converting a UIColor, or create a standard Color directly") @available(macOS, unavailable) @@ -146,11 +146,13 @@ extension UIColor: ColorProvider { } } -public extension ColorScheme { +// MARK: - UIUserInterfaceStyle Conversions + +extension ColorScheme { /// Creates a color scheme from its user interface style equivalent. @available(macOS, unavailable) @available(watchOS, unavailable) - init?(_ uiUserInterfaceStyle: UIUserInterfaceStyle) { + public init?(_ uiUserInterfaceStyle: UIUserInterfaceStyle) { switch uiUserInterfaceStyle { case .unspecified: return nil case .light: self = .light @@ -160,11 +162,11 @@ public extension ColorScheme { } } -public extension UIUserInterfaceStyle { +extension UIUserInterfaceStyle { /// Creates a user interface style from its ColorScheme equivalent. @available(macOS, unavailable) @available(watchOS, unavailable) - init(_ colorScheme: ColorScheme?) { + public init(_ colorScheme: ColorScheme?) { switch colorScheme { case .light: self = .light case .dark: self = .dark @@ -172,4 +174,33 @@ public extension UIUserInterfaceStyle { } } } + +// MARK: - UIAccessibilityContrast Conversions [TODO] + +// ColorSchemeContrast +// UIAccessibilityContrast + +// MARK: - UIContentSizeCategory Conversions [WIP] + +extension DynamicTypeSize { + /// Create a Dynamic Type size from its `UIContentSizeCategory` equivalent. + public init?(_ uiSizeCategory: UIContentSizeCategory) { + switch uiSizeCategory { + case .extraSmall: self = .xSmall + case .small: self = .small + case .medium: self = .medium + case .large: self = .large + case .extraLarge: self = .xLarge + case .extraExtraLarge: self = .xxLarge + case .extraExtraExtraLarge: self = .xxxLarge + case .accessibilityMedium: self = .accessibility1 + case .accessibilityLarge: self = .accessibility2 + case .accessibilityExtraLarge: self = .accessibility3 + case .accessibilityExtraExtraLarge: self = .accessibility4 + case .accessibilityExtraExtraExtraLarge: self = .accessibility5 + default: return nil + } + } +} + #endif diff --git a/Sources/OpenSwiftUICore/CoreGlue/CoreGlue.swift b/Sources/OpenSwiftUICore/CoreGlue/CoreGlue.swift index fa9872c04..677d72acc 100644 --- a/Sources/OpenSwiftUICore/CoreGlue/CoreGlue.swift +++ b/Sources/OpenSwiftUICore/CoreGlue/CoreGlue.swift @@ -3,11 +3,12 @@ // OpenSwiftUICore // // Audited for iOS 18.0 -// Status: Empty +// Status: WIP #if canImport(Darwin) public import Foundation +public import CoreText package import OpenGraphShims import OpenSwiftUI_SPI @@ -43,5 +44,83 @@ extension CoreGlue { } } } - + +@_spi(ForOpenSwiftUIOnly) +@objc(OpenSwiftUICoreGlue2) +open class CoreGlue2: NSObject { + package static var shared: CoreGlue2 = _initializeCoreGlue2() as! CoreGlue2 + + open func initializeTestApp() { + preconditionFailure("") + } + + open func isStatusBarHidden() -> Bool? { + preconditionFailure("") + } + + open func configureDefaultEnvironment(_: inout EnvironmentValues) { + preconditionFailure("") + } + + open func makeRootView(base: AnyView, rootFocusScope: Namespace.ID) -> AnyView { + preconditionFailure("") + } + + open var systemDefaultDynamicTypeSize: DynamicTypeSize { + preconditionFailure("") + } + + open var codableAttachmentCellType: CoreGlue2.CodableAttachmentCellTypeResult { + preconditionFailure("") + } + + open func linkURL(_ parameters: LinkURLParameters) -> URL? { + preconditionFailure("") + } + + package func linkURL(at point: CGPoint, in size: CGSize, stringDrawing: ResolvedStyledText.StringDrawing) -> URL? { + linkURL(LinkURLParameters(point: point, size: size, stringDrawing: stringDrawing)) + } + + open func transformingEquivalentAttributes(_ attributedString: AttributedString) -> AttributedString { + preconditionFailure("") + } + + @objc(makeSummarySymbolHostIsOn:font:foregroundColor:) + open func makeSummarySymbolHost(isOn: Bool, font: CTFont, foregroundColor: CGColor) -> AnyObject { + preconditionFailure("") + } +} + +extension CoreGlue2 { + public struct CodableAttachmentCellTypeResult { + package var value: (any ProtobufMessage.Type)? + + package init(_ value: (any ProtobufMessage.Type)?) { + self.value = value + } + } + + public struct LinkURLParameters { + package var point: CGPoint + package var size: CGSize + package var stringDrawing: ResolvedStyledText.StringDrawing + + package init(point: CGPoint, size: CGSize, stringDrawing: ResolvedStyledText.StringDrawing) { + self.point = point + self.size = size + self.stringDrawing = stringDrawing + } + } +} + +@available(*, unavailable) +extension CoreGlue2: Sendable {} + +@available(*, unavailable) +extension CoreGlue2.CodableAttachmentCellTypeResult: Sendable {} + +@available(*, unavailable) +extension CoreGlue2.LinkURLParameters: Sendable {} + #endif diff --git a/Sources/OpenSwiftUICore/Text/DynamicTypeSize.swift b/Sources/OpenSwiftUICore/Text/DynamicTypeSize.swift index 74b98ba09..814b33130 100644 --- a/Sources/OpenSwiftUICore/Text/DynamicTypeSize.swift +++ b/Sources/OpenSwiftUICore/Text/DynamicTypeSize.swift @@ -58,8 +58,11 @@ public enum DynamicTypeSize: Hashable, Comparable, CaseIterable, Sendable { } package static var systemDefault: DynamicTypeSize { + #if canImport(Darwin) + CoreGlue2.shared.systemDefaultDynamicTypeSize + #else .large - // CoreGlue2.shared.systemDefaultDynamicTypeSize + #endif } } diff --git a/Sources/OpenSwiftUICore/Text/ResolvedStyledText.swift b/Sources/OpenSwiftUICore/Text/ResolvedStyledText.swift new file mode 100644 index 000000000..632412e2c --- /dev/null +++ b/Sources/OpenSwiftUICore/Text/ResolvedStyledText.swift @@ -0,0 +1,6 @@ +// FIXME: +package class ResolvedStyledText {} + +extension ResolvedStyledText { + package class StringDrawing {} +} diff --git a/Sources/OpenSwiftUI_SPI/Util/CoreGlue.h b/Sources/OpenSwiftUI_SPI/Util/CoreGlue.h index 1f619fbf9..58be1bd82 100644 --- a/Sources/OpenSwiftUI_SPI/Util/CoreGlue.h +++ b/Sources/OpenSwiftUI_SPI/Util/CoreGlue.h @@ -13,6 +13,7 @@ #if OPENSWIFTUI_TARGET_OS_DARWIN id _initializeCoreGlue(); +id _initializeCoreGlue2(); #endif diff --git a/Sources/OpenSwiftUI_SPI/Util/CoreGlue.m b/Sources/OpenSwiftUI_SPI/Util/CoreGlue.m index d45a71d03..1c4cb243c 100644 --- a/Sources/OpenSwiftUI_SPI/Util/CoreGlue.m +++ b/Sources/OpenSwiftUI_SPI/Util/CoreGlue.m @@ -13,22 +13,24 @@ void abort_report_np(const char*, ...); void* OpenSwiftUILibrary(); -void * getSwiftUIGlueClassSymbolLoc(); +void * getOpenSwiftUIGlueClassSymbolLoc(); +void * getOpenSwiftUIGlue2ClassSymbolLoc(); id _initializeCoreGlue() { - void *location = getSwiftUIGlueClassSymbolLoc(); + void *location = getOpenSwiftUIGlueClassSymbolLoc(); Class (*classFunc)(void) = (Class (*)(void))location; Class glueClass = classFunc(); return [[glueClass alloc] init]; } -void* OpenSwiftUILibrary() { - // Since we are staticlly linking OpenSwiftUI and OpenSwiftUICore into the final binary, - // we can just use dlopen(NULL, RTLD_LAZY) to get the current macho binary handle - return dlopen(NULL, RTLD_LAZY); +id _initializeCoreGlue2() { + void *location = getOpenSwiftUIGlue2ClassSymbolLoc(); + Class (*classFunc)(void) = (Class (*)(void))location; + Class glue2Class = classFunc(); + return [[glue2Class alloc] init]; } -void *getSwiftUIGlueClassSymbolLoc() { +void *getOpenSwiftUIGlueClassSymbolLoc() { static void *ptr; if (ptr == NULL) { @try { @@ -43,4 +45,26 @@ id _initializeCoreGlue() { return ptr; } +void *getOpenSwiftUIGlue2ClassSymbolLoc() { + static void *ptr; + if (ptr == NULL) { + @try { + ptr = dlsym(OpenSwiftUILibrary(), "OpenSwiftUIGlue2Class"); + } @finally { + } + } + if (ptr == NULL) { + const char *error = dlerror(); + abort_report_np("%s", error); + } + return ptr; +} + + +void* OpenSwiftUILibrary() { + // Since we are staticlly linking OpenSwiftUI and OpenSwiftUICore into the final binary, + // we can just use dlopen(NULL, RTLD_LAZY) to get the current macho binary handle + return dlopen(NULL, RTLD_LAZY); +} + #endif diff --git a/Tests/OpenSwiftUICoreTests/Util/TimerUtilsTests.swift b/Tests/OpenSwiftUICoreTests/Util/TimerUtilsTests.swift index 5da35bcec..5d7abb8e6 100644 --- a/Tests/OpenSwiftUICoreTests/Util/TimerUtilsTests.swift +++ b/Tests/OpenSwiftUICoreTests/Util/TimerUtilsTests.swift @@ -86,7 +86,8 @@ final class TimerUtilsXCTests: XCTestCase { } XCTAssertTrue(timer.isValid) XCTAssertFalse(callbackExecuted) - await fulfillment(of: [expectation], timeout: 0.3) + // 0.3s will sometimes fail for macOS + 2021 platform + await fulfillment(of: [expectation], timeout: 1) XCTAssertTrue(callbackExecuted) let elapsedTime = Date().timeIntervalSince(startTime)