diff --git a/Sources/OpenSwiftUI/App/AppGraph.swift b/Sources/OpenSwiftUI/App/AppGraph.swift index 416b71083..5c2625890 100644 --- a/Sources/OpenSwiftUI/App/AppGraph.swift +++ b/Sources/OpenSwiftUI/App/AppGraph.swift @@ -26,7 +26,7 @@ package final class AppGraph: GraphHost { } private lazy var launchProfileOptions = LaunchProfileOptions( - rawValue: EnvironmentHelper.int32(for: "OPENSWIFTUI_PROFILE_LAUNCH") + rawValue: EnvironmentHelper.int32(for: "OPENSWIFTUI_PROFILE_LAUNCH") ?? 0 ) var didCollectLaunchProfile: Bool = false diff --git a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift index 48d16cb83..e92d5f8b2 100644 --- a/Sources/OpenSwiftUICore/Graph/GraphInputs.swift +++ b/Sources/OpenSwiftUICore/Graph/GraphInputs.swift @@ -144,7 +144,7 @@ public struct _GraphInputs { package var animationsDisabled: Bool { get { options.contains(.animationsDisabled) } - set { options.formUnion(.animationsDisabled) } + set { options.setValue(newValue, for: .animationsDisabled) } } package var needsStableDisplayListIDs: Bool { diff --git a/Sources/OpenSwiftUICore/Util/EnvironmentHelper.swift b/Sources/OpenSwiftUICore/Util/EnvironmentHelper.swift index 51f51ca1b..3d2309dc8 100644 --- a/Sources/OpenSwiftUICore/Util/EnvironmentHelper.swift +++ b/Sources/OpenSwiftUICore/Util/EnvironmentHelper.swift @@ -17,10 +17,10 @@ import WASILibc package enum EnvironmentHelper { @_transparent - package static func int32(for key: String) -> Int32 { + package static func int32(for key: String) -> Int32? { key.withCString { string in guard let env = getenv(string) else { - return 0 + return nil } return atoi(env) } diff --git a/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift b/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift index e7406a698..f18428a86 100644 --- a/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift +++ b/Sources/OpenSwiftUICore/View/Debug/ViewDebug.swift @@ -3,13 +3,17 @@ // OpenSwiftUICore // // Audited for iOS 18.0 -// Status: WIP +// Status: Blocked by TreeElement +// ID: 5A14269649C60F846422EA0FA4C5E535 (SwiftUI) +// ID: 43DA1754B0518AF1D72B90677BF266DB (SwiftUICore) public import Foundation import OpenGraphShims import OpenSwiftUI_SPI +/// Namespace for view debug information. public enum _ViewDebug { + /// All debuggable view properties. public enum Property: UInt32, Hashable { case type case value @@ -21,7 +25,8 @@ public enum _ViewDebug { case layoutComputer case displayList } - + + /// Bitmask of requested view debug properties. public struct Properties: OptionSet { public let rawValue: UInt32 public init(rawValue: UInt32) { @@ -32,7 +37,7 @@ public enum _ViewDebug { package init(_ property: Property) { self.init(rawValue: 1 << property.rawValue) } - + public static let type = Properties(.type) public static let value = Properties(.value) public static let transform = Properties(.transform) @@ -47,6 +52,7 @@ public enum _ViewDebug { package static var properties = Properties() + /// View debug data for a view and all its child views. public struct Data { package var data: [Property: Any] package var childData: [_ViewDebug.Data] @@ -72,80 +78,120 @@ extension _ViewDebug.Data: Sendable {} @available(*, unavailable) extension _ViewDebug: Sendable {} -// TODO -// MARK: View and ViewModifier - -extension View { - @inline(__always) - nonisolated - package static func makeDebuggableView( - view: _GraphValue, - inputs: _ViewInputs - ) -> _ViewOutputs { - preconditionFailure("TODO") +extension _ViewDebug { + package static func initialize(inputs: inout _ViewInputs) { + if !isInitialized { + if let debugValue = EnvironmentHelper.int32(for: "OPENSWIFTUI_VIEW_DEBUG") { + properties = Properties(rawValue: UInt32(bitPattern: debugValue)) + } + isInitialized = true + } + if !properties.isEmpty { + Subgraph.setShouldRecordTree() + } } - @inline(__always) - nonisolated - package static func makeDebuggableViewList( - view: _GraphValue, - inputs: _ViewListInputs - ) -> _ViewListOutputs { - OGSubgraph.beginTreeElement(value: view.value, flags: 1) - defer { OGSubgraph.endTreeElement(value: view.value) } - return _makeViewList(view: view, inputs: inputs) + fileprivate static func reallyWrap(_ outputs: inout _ViewOutputs, value: _GraphValue, inputs: UnsafePointer<_ViewInputs>) { + var debugProperiets = outputs.preferences.debugProperties.union(inputs.pointee.changedDebugProperties) + outputs.preferences.debugProperties = [] + if debugProperiets.contains(.layoutComputer) { + debugProperiets.setValue(outputs.layoutComputer != nil, for: .layoutComputer) + } + guard debugProperiets.subtracting(.displayList) != [] else { + return + } + guard Subgraph.shouldRecordTree else { + return + } + if debugProperiets.contains(.transform) { + Subgraph.addTreeValue(inputs.pointee.transform, forKey: "transfrom", flags: 0) + } + if debugProperiets.contains(.position) { + Subgraph.addTreeValue(inputs.pointee.position, forKey: "position", flags: 0) + } + if debugProperiets.contains(.size) { + Subgraph.addTreeValue(inputs.pointee.size, forKey: "size", flags: 0) + } + if debugProperiets.contains(.environment) { + Subgraph.addTreeValue(inputs.pointee.environment, forKey: "environment", flags: 0) + } + if debugProperiets.contains(.phase) { + Subgraph.addTreeValue(inputs.pointee.base.phase, forKey: "phase", flags: 0) + } + if debugProperiets.contains(.layoutComputer) { + Subgraph.addTreeValue(outputs.layoutComputer!, forKey: "layoutComputer", flags: 0) + } } } -extension ViewModifier { - package static func makeDebuggableView( +// MARK: View and ViewModifier + +extension ViewModifier { + @inline(__always) + nonisolated package static func makeDebuggableView( modifier: _GraphValue, inputs: _ViewInputs, body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs ) -> _ViewOutputs { - preconditionFailure("TODO") + Subgraph.beginTreeElement(value: modifier.value, flags: 0) + var outputs = _makeView( + modifier: modifier, + inputs: inputs.withoutChangedDebugProperties, + body: body + ) + if Subgraph.shouldRecordTree { + withUnsafePointer(to: inputs) { pointer in + _ViewDebug.reallyWrap(&outputs, value: modifier, inputs: pointer) + } + } + Subgraph.endTreeElement(value: modifier.value) + return outputs } - static func makeDebuggableViewList( + @inline(__always) + nonisolated package static func makeDebuggableViewList( modifier: _GraphValue, inputs: _ViewListInputs, body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs ) -> _ViewListOutputs { - OGSubgraph.beginTreeElement(value: modifier.value, flags: 1) - defer { OGSubgraph.endTreeElement(value: modifier.value) } + Subgraph.beginTreeElement(value: modifier.value, flags: 1) + defer { Subgraph.endTreeElement(value: modifier.value) } return _makeViewList(modifier: modifier, inputs: inputs, body: body) } } -// MARK: _ViewDebug - -extension _ViewDebug { - public static func serializedData(_ viewDebugData: [_ViewDebug.Data]) -> Foundation.Data? { - let encoder = JSONEncoder() - encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan") - do { - let data = try encoder.encode(viewDebugData) - return data - } catch { - let dic = ["error": error.localizedDescription] - return try? encoder.encode(dic) +extension View { + @inline(__always) + nonisolated package static func makeDebuggableView( + view: _GraphValue, + inputs: _ViewInputs + ) -> _ViewOutputs { + Subgraph.beginTreeElement(value: view.value, flags: 0) + var outputs = _makeView( + view: view, + inputs: inputs.withoutChangedDebugProperties + ) + if Subgraph.shouldRecordTree { + withUnsafePointer(to: inputs) { pointer in + _ViewDebug.reallyWrap(&outputs, value: view, inputs: pointer) + } } + Subgraph.endTreeElement(value: view.value) + return outputs + } + + @inline(__always) + nonisolated package static func makeDebuggableViewList( + view: _GraphValue, + inputs: _ViewListInputs + ) -> _ViewListOutputs { + Subgraph.beginTreeElement(value: view.value, flags: 1) + defer { Subgraph.endTreeElement(value: view.value) } + return _makeViewList(view: view, inputs: inputs) } } extension _ViewDebug { - @inline(__always) - static func instantiateIfNeeded() { - if !isInitialized { - let debugValue = UInt32(bitPattern: EnvironmentHelper.int32(for: "OPENSWIFTUI_VIEW_DEBUG")) - properties = Properties(rawValue: debugValue) - isInitialized = true - } - if !properties.isEmpty { - OGSubgraph.setShouldRecordTree() - } - } - // Fix -werror issue // @available(*, deprecated, message: "To be refactored into View.makeDebuggableView") @inline(__always) @@ -154,85 +200,116 @@ extension _ViewDebug { inputs: _ViewInputs, body: (_ view: _GraphValue, _ inputs: _ViewInputs) -> _ViewOutputs ) -> _ViewOutputs { - var inputs = inputs - OGSubgraph.beginTreeElement(value: view.value, flags: 0) - // FIXME -// var outputs = inputs.withEmptyChangedDebugPropertiesInputs { inputs in -// body(view, inputs) -// } - inputs.changedDebugProperties = [] - var outputs = body(view, inputs) - - if OGSubgraph.shouldRecordTree { - _ViewDebug.reallyWrap(&outputs, value: view, inputs: &inputs) - OGSubgraph.endTreeElement(value: view.value) + Subgraph.beginTreeElement(value: view.value, flags: 0) + var outputs = body(view, inputs.withoutChangedDebugProperties) + if Subgraph.shouldRecordTree { + withUnsafePointer(to: inputs) { pointer in + _ViewDebug.reallyWrap(&outputs, value: view, inputs: pointer) + } } + OGSubgraph.endTreeElement(value: view.value) return outputs } +} + +// MARK: - ViewDebug + Debug Data + +// FIXME +extension Subgraph { + func treeRoot() -> Int? { nil } +} + +extension _ViewDebug { + package static func makeDebugData(subgraph: Subgraph) -> [_ViewDebug.Data] { + var result: [_ViewDebug.Data] = [] + if let rootElement = subgraph.treeRoot() { + appendDebugData(from: rootElement, to: &result) + } + return result + } - private static func reallyWrap(_: inout _ViewOutputs, value: _GraphValue, inputs _: UnsafePointer<_ViewInputs>) { - // TODO + private static func appendDebugData(from element: Int/*AGTreeElement*/ , to result: inout [_ViewDebug.Data]) { + preconditionFailure("TODO") } +} - fileprivate static func appendDebugData(from: Int/*AGTreeElement*/ , to: [_ViewDebug.Data]) {} +extension _ViewDebug { + public static func serializedData(_ viewDebugData: [_ViewDebug.Data]) -> Foundation.Data? { + let encoder = JSONEncoder() + encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: "inf", negativeInfinity: "-inf", nan: "nan") + do { + let data = try encoder.encode(viewDebugData) + return data + } catch { + let dic = ["error": error.localizedDescription] + return try? encoder.encode(dic) + } + } } // MARK: _ViewDebug.Data extension _ViewDebug.Data: Encodable { - public func encode(to encoder: Encoder) throws { - var container: KeyedEncodingContainer<_ViewDebug.Data.CodingKeys> = encoder.container(keyedBy: _ViewDebug.Data.CodingKeys.self) - try container.encode(serializedProperties(), forKey: .properties) - try container.encode(childData, forKey: .children) - } - enum CodingKeys: CodingKey { case properties case children } - + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(serializedProperties(), forKey: .properties) + try container.encode(childData, forKey: .children) + } + private func serializedProperties() -> [SerializedProperty] { data.compactMap { key, value -> SerializedProperty? in - if key == .value { - if let attribute = serializedAttribute(for: value, label: nil, reflectionDepth: 6) { - return SerializedProperty(id: key.rawValue, attribute: attribute) - } else { - return nil - } - } else if key == .type { - let type = value as? Any.Type ?? type(of: value) - let attribute = SerializedAttribute(type: type) - return SerializedProperty(id: 0, attribute: attribute) - } else { - if let attribute = serializedAttribute(for: value, label: nil, reflectionDepth: 4) { - return SerializedProperty(id: key.rawValue, attribute: attribute) - } else { - return nil - } + let attribute: SerializedAttribute? = switch key { + case .type: SerializedAttribute(type: value as? Any.Type ?? type(of: value)) + case .value: serializedAttribute(for: value, label: nil, reflectionDepth: 6) + default: serializedAttribute(for: value, label: nil, reflectionDepth: 4) } + guard let attribute else { return nil } + return SerializedProperty(id: key.rawValue, attribute: attribute) } } - - // TODO - // Mirror API + private func serializedAttribute(for value: Any, label: String?, reflectionDepth depth: Int) -> SerializedAttribute? { -// let unwrapped = unwrapped(value) - - return nil - -// let mirror = Mirror(reflecting: value) -// mirror.displayStyle = .tuple - + guard let unwrappedValue = unwrapped(value) else { + return nil + } + if unwrappedValue is Encodable || unwrappedValue is CustomViewDebugValueConvertible || depth == 0 { + return SerializedAttribute(value: unwrappedValue, serializeValue: true, label: label, subattributes: nil) + } else if let mirror = effectiveMirror(for: unwrappedValue) { + guard !mirror.children.isEmpty else { + return SerializedAttribute(value: unwrappedValue, serializeValue: true, label: label, subattributes: nil) + } + let depth = depth - 1 + let subattributes = mirror.children.compactMap { child in + serializedAttribute(for: child.value, label: child.label, reflectionDepth: depth) + } + return SerializedAttribute(value: unwrappedValue, serializeValue: false, label: label, subattributes: subattributes) + } else { + return SerializedAttribute(value: unwrappedValue, serializeValue: false, label: label, subattributes: nil) + } } private func unwrapped(_ value: Any) -> Any? { - if let value = value as? ValueWrapper { - return value.wrappedValue + if let valueWrapper = value as? ValueWrapper { + return valueWrapper.wrappedValue } else { - return nil + return value } } + private func effectiveMirror(for value: Any) -> Mirror? { + if case let customized as CustomViewDebugReflectable = value { + customized.customViewDebugMirror + } else if case let customized as CustomReflectable = value { + customized.customMirror + } else { + Mirror(reflecting: value) + } + } } // MARK: _ViewDebug.Data.SerializedProperty @@ -258,50 +335,7 @@ extension _ViewDebug.Data { // MARK: _ViewDebug.Data.SerializedAttribute extension _ViewDebug.Data { - // Size: 0x60 private struct SerializedAttribute: Encodable { - // TODO: - static func serialize(value _: Any) -> Any? { - // Mirror API - nil - } - - init(type anyType: Any.Type) { - name = nil - type = String(reflecting: anyType) - readableType = OGTypeID(anyType).description - flags = [ - conformsToProtocol(anyType, _OpenSwiftUI_viewProtocolDescriptor()) ? .view : [], - conformsToProtocol(anyType, _OpenSwiftUI_viewModifierProtocolDescriptor()) ? .viewModifier : [], - ] - value = nil - subattributes = nil - } - - init(value inputValue: Any, serializeValue: Bool, label: String?, subattributes inputSubattributes: [SerializedAttribute]) { - name = label - let anyType = Swift.type(of: inputValue) - type = String(reflecting: anyType) - readableType = OGTypeID(anyType).description - flags = [ - conformsToProtocol(anyType, _OpenSwiftUI_viewProtocolDescriptor()) ? .view : [], - conformsToProtocol(anyType, _OpenSwiftUI_viewModifierProtocolDescriptor()) ? .viewModifier : [], - ] - if serializeValue { - value = SerializedAttribute.serialize(value: inputValue) - } else { - value = nil - } - subattributes = inputSubattributes - } - - struct Flags: OptionSet, Encodable { - let rawValue: Int - - static let view = Flags(rawValue: 1 << 0) - static let viewModifier = Flags(rawValue: 1 << 1) - } - let name: String? let type: String let readableType: String @@ -309,6 +343,13 @@ extension _ViewDebug.Data { let value: Any? let subattributes: [SerializedAttribute]? + struct Flags: OptionSet, Encodable { + let rawValue: Int + + static let view = Flags(rawValue: 1 << 0) + static let viewModifier = Flags(rawValue: 1 << 1) + } + enum CodingKeys: CodingKey { case name case type @@ -329,15 +370,76 @@ extension _ViewDebug.Data { } try container.encodeIfPresent(subattributes, forKey: .subattributes) } + + static func serialize(value: Any) -> Any? { + let viewDebugValue: Any + if let customValue = value as? CustomViewDebugValueConvertible { + viewDebugValue = customValue.viewDebugValue + } else { + viewDebugValue = value + } + if let encodable = viewDebugValue as? Encodable { + return encodable + } else if let customDebugStringConvertible = viewDebugValue as? CustomDebugStringConvertible { + return customDebugStringConvertible.debugDescription + } else { + let mirror = Mirror(reflecting: viewDebugValue) + if let displayStyle = mirror.displayStyle, displayStyle == .enum { + return String(describing: viewDebugValue) + } else { + return nil + } + } + } + + init(type anyType: Any.Type) { + self.name = nil + self.type = String(reflecting: anyType) + self.readableType = OGTypeID(anyType).description + self.flags = [ + conformsToProtocol(anyType, _OpenSwiftUI_viewProtocolDescriptor()) ? .view : [], + conformsToProtocol(anyType, _OpenSwiftUI_viewModifierProtocolDescriptor()) ? .viewModifier : [], + ] + self.value = nil + self.subattributes = nil + } + + init(value: Any, serializeValue: Bool, label: String?, subattributes: [SerializedAttribute]?) { + self.name = label + let anyType = Swift.type(of: value) + self.type = String(reflecting: anyType) + self.readableType = OGTypeID(anyType).description + self.flags = [ + conformsToProtocol(anyType, _OpenSwiftUI_viewProtocolDescriptor()) ? .view : [], + conformsToProtocol(anyType, _OpenSwiftUI_viewModifierProtocolDescriptor()) ? .viewModifier : [], + ] + self.value = serializeValue ? SerializedAttribute.serialize(value: value) : nil + self.subattributes = subattributes + } } } -private protocol ValueWrapper { +package protocol CustomViewDebugReflectable { + var customViewDebugMirror: Mirror? { get } +} + +package protocol CustomViewDebugValueConvertible { + var viewDebugValue: Any { get } +} + +@_spi(ForOpenSwiftUIOnly) +extension ViewTransform.Item: Encodable { + package func encode(to encoder: any Encoder) throws { + preconditionFailure("TODO") + } +} + +package protocol ValueWrapper { var wrappedValue: Any? { get } } -extension Optional { - var wrappedValue: Any? { +extension Optional: ValueWrapper { + package var wrappedValue: Any? { if case let .some(wrapped) = self { return wrapped } else { @@ -345,3 +447,11 @@ extension Optional { } } } + +#if canImport(Darwin) +@objc +package protocol XcodeViewDebugDataProvider { + @objc + func makeViewDebugData() -> Foundation.Data? +} +#endif diff --git a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift index e563b3c45..658b0f52c 100644 --- a/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift +++ b/Sources/OpenSwiftUICore/View/Graph/ViewGraph.swift @@ -217,7 +217,7 @@ package final class ViewGraph: GraphHost { if let preferenceBridge { preferenceBridge.wrapInputs(&inputs) } - _ViewDebug.instantiateIfNeeded() // FIXME + _ViewDebug.initialize(inputs: &inputs) if _VariableFrameDurationIsSupported() { if !inputs.base.options.contains(.supportsVariableFrameDuration) { inputs.base.options.formUnion(.supportsVariableFrameDuration) diff --git a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift index e57a40843..f9c528008 100644 --- a/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift +++ b/Sources/OpenSwiftUICore/View/Input/ViewInputs.swift @@ -28,12 +28,12 @@ public struct _ViewInputs { set { base.customInputs = newValue } } - package subscript(input: T.Type) -> T.Value where T : ViewInput { + package subscript(input: T.Type) -> T.Value where T: ViewInput { get { base[input] } set { base[input] = newValue } } - package subscript(input: T.Type) -> T.Value where T : ViewInput, T.Value : GraphReusable { + package subscript(input: T.Type) -> T.Value where T: ViewInput, T.Value: GraphReusable { get { base[input] } set { base[input] = newValue } } @@ -217,6 +217,15 @@ public struct _ViewInputs { @available(*, unavailable) extension _ViewInputs: Sendable {} +extension _ViewInputs { + @inline(__always) + var withoutChangedDebugProperties: _ViewInputs { + var copy = self + copy.changedDebugProperties = [] + return copy + } +} + // FIXME: TO BE REMOVED diff --git a/Sources/OpenSwiftUICore/View/View.swift b/Sources/OpenSwiftUICore/View/View.swift index 4117036b4..b9ac4e17d 100644 --- a/Sources/OpenSwiftUICore/View/View.swift +++ b/Sources/OpenSwiftUICore/View/View.swift @@ -49,10 +49,16 @@ public protocol View { /// implementation of the required ``View/body-swift.property`` property. associatedtype Body: View + /// Instantiates the view using `view` as its source value, and + /// `inputs` as its input values. Returns the view's output values. + /// This should never be called directly, instead use the + /// makeDebuggableView() shim function. static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs static func _makeViewList(view: _GraphValue, inputs: _ViewListInputs) -> _ViewListOutputs + /// The number of views that `_makeViewList()` would produce, or + /// nil if unknown. static func _viewListCount(inputs: _ViewListCountInputs) -> Int? /// The content and behavior of the view. @@ -77,10 +83,6 @@ public protocol View { } extension View { - /// Instantiates the view using `view` as its source value, and - /// `inputs` as its input values. Returns the view's output values. - /// This should never be called directly, instead use the - /// makeDebuggableView() shim function. public static func _makeView(view: _GraphValue, inputs: _ViewInputs) -> _ViewOutputs { makeView(view: view, inputs: inputs) } diff --git a/Tests/OpenSwiftUICoreTests/View/Debug/ViewDebugTests.swift b/Tests/OpenSwiftUICoreTests/View/Debug/ViewDebugTests.swift index b466689a5..f3c20e99d 100644 --- a/Tests/OpenSwiftUICoreTests/View/Debug/ViewDebugTests.swift +++ b/Tests/OpenSwiftUICoreTests/View/Debug/ViewDebugTests.swift @@ -5,30 +5,44 @@ // Created by Kyle on 2023/10/6. // -@testable import OpenSwiftUICore +import OpenSwiftUICore +import OpenGraphShims import Testing import Foundation struct ViewDebugTests { - @Test(.disabled("Skip the test until we finish the implementation of _ViewDebug")) - func type() throws { + private func checkJSONEqual(data: Data, expected expectedData: Data) -> Bool { + let json = try? JSONSerialization.jsonObject(with: data) + let expectedJSON = try? JSONSerialization.jsonObject(with: expectedData) + guard let json = json as? [[String: AnyHashable]], let expectedJSON = expectedJSON as? [[String: AnyHashable]] else { + return false + } + return json == expectedJSON + } + + @Test(.enabled(if: attributeGraphEnabled ,"Only enable the test when AG is enabled")) + func serializeData() throws { var rawData = _ViewDebug.Data() rawData.data = [.type: CGSize.self] let data = try #require(_ViewDebug.serializedData([rawData])) - let content = String(decoding: data, as: UTF8.self) - #expect(content == #""" - [{"properties":[{"id":0,"attribute":{"type":"__C.CGSize","flags":0,"readableType":""}}],"children":[]}] - """#) + #expect(checkJSONEqual( + data: data, + expected: #""" + [{"properties":[{"id":0,"attribute":{"type":"__C.CGSize","flags":0,"readableType":"CGSize"}}],"children":[]}] + """#.data(using: .utf8)! + )) } - @Test(.disabled("Skip the test until we finish the implementation of _ViewDebug")) + @Test(.enabled(if: attributeGraphEnabled ,"Only enable the test when AG is enabled")) func size() throws { var rawData = _ViewDebug.Data() rawData.data = [.size: CGSize(width: 20, height: 20)] let data = try #require(_ViewDebug.serializedData([rawData])) - let content = String(decoding: data, as: UTF8.self) - #expect(content == #""" - [{"properties":[],"children":[]}] - """#) + #expect(checkJSONEqual( + data: data, + expected: #""" + [{"properties":[{"id":4,"attribute":{"value":[20,20],"type":"__C.CGSize","flags":0,"readableType":"CGSize"}}],"children":[]}] + """#.data(using: .utf8)! + )) } }