From bf0b102c6c0407066f8a3fb1fa7aa1eadb2a0b48 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 2 Feb 2026 15:02:32 -0500 Subject: [PATCH 1/6] [WIP] `CoreTransferable` attachments --- Package.swift | 11 +++ Sources/Overlays/CMakeLists.txt | 1 + .../Attachments/Attachment+Transferable.swift | 81 +++++++++++++++++++ .../_AttachableTransferableWrapper.swift | 67 +++++++++++++++ .../_Testing_CoreTransferable/CMakeLists.txt | 25 ++++++ .../ReexportTesting.swift | 11 +++ .../CoreTransferable.swiftoverlay | 3 + Tests/TestingTests/AttachmentTests.swift | 65 ++++++++++++++- .../shared/AvailabilityDefinitions.cmake | 1 + 9 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift create mode 100644 Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift create mode 100644 Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt create mode 100644 Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift create mode 100644 Sources/Testing/Testing.swiftcrossimport/CoreTransferable.swiftoverlay diff --git a/Package.swift b/Package.swift index 87ca077d0..c9b131cca 100644 --- a/Package.swift +++ b/Package.swift @@ -162,6 +162,7 @@ let package = Package( "_Testing_AppKit", "_Testing_CoreGraphics", "_Testing_CoreImage", + "_Testing_CoreTransferable", "_Testing_Foundation", "_Testing_UIKit", "_Testing_WinSDK", @@ -264,6 +265,15 @@ let package = Package( exclude: ["CMakeLists.txt"], swiftSettings: .packageSettings + .enableLibraryEvolution() + .moduleABIName("_Testing_CoreImage") ), + .target( + name: "_Testing_CoreTransferable", + dependencies: [ + "Testing", + ], + path: "Sources/Overlays/_Testing_CoreTransferable", + exclude: ["CMakeLists.txt"], + swiftSettings: .packageSettings + .enableLibraryEvolution() + .moduleABIName("_Testing_CoreTransferable") + ), .target( name: "_Testing_Foundation", dependencies: [ @@ -428,6 +438,7 @@ extension Array where Element == PackageDescription.SwiftSetting { .enableExperimentalFeature("AvailabilityMacro=_regexAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"), .enableExperimentalFeature("AvailabilityMacro=_swiftVersionAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0"), .enableExperimentalFeature("AvailabilityMacro=_typedThrowsAPI:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"), + .enableExperimentalFeature("AvailabilityMacro=_transferableAPI:macOS 15.2, iOS 18.2, watchOS 11.2, tvOS 18.2, visionOS 2.2"), .enableExperimentalFeature("AvailabilityMacro=_castingWithNonCopyableGenerics:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0"), .enableExperimentalFeature("AvailabilityMacro=_distantFuture:macOS 99.0, iOS 99.0, watchOS 99.0, tvOS 99.0, visionOS 99.0"), diff --git a/Sources/Overlays/CMakeLists.txt b/Sources/Overlays/CMakeLists.txt index 434b4d3ec..2a646dfd3 100644 --- a/Sources/Overlays/CMakeLists.txt +++ b/Sources/Overlays/CMakeLists.txt @@ -9,6 +9,7 @@ add_subdirectory(_Testing_AppKit) add_subdirectory(_Testing_CoreGraphics) add_subdirectory(_Testing_CoreImage) +add_subdirectory(_Testing_CoreTransferable) add_subdirectory(_Testing_Foundation) add_subdirectory(_Testing_UIKit) add_subdirectory(_Testing_WinSDK) diff --git a/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift b/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift new file mode 100644 index 000000000..c6eb677b1 --- /dev/null +++ b/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift @@ -0,0 +1,81 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#if canImport(CoreTransferable) +public import Testing +public import CoreTransferable + +public import UniformTypeIdentifiers + +@_spi(Experimental) +@available(_transferableAPI, *) +extension Attachment { + /// Initialize an instance of this type that encloses the given transferable + /// value. + /// + /// - Parameters: + /// - transferableValue: The value that will be attached to the output of + /// the test run. + /// - contentType: The content type with which to export `transferableValue`. + /// If this argument is `nil`, the testing library calls [`exportedContentTypes(_:)`](https://developer.apple.com/documentation/coretransferable/transferable/exportedcontenttypes(_:)) + /// on `transferableValue` and uses the first type the function returns + /// that conforms to [`UTType.data`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/data). + /// - preferredName: The preferred name of the attachment to use when saving + /// it. If `nil`, the testing library attempts to generate a reasonable + /// filename for the attached value. + /// - sourceLocation: The source location of the call to this initializer. + /// This value is used when recording issues associated with the + /// attachment. + /// + /// - Throws: Any error that occurs while exporting `transferableValue`. + /// + /// Use this initializer to create an instance of ``Attachment`` from a value + /// that conforms to the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable) + /// protocol. + /// + /// ```swift + /// let menu = FoodTruck.menu + /// let attachment = try await Attachment(exporting: menu, as: .pdf) + /// Attachment.record(attachment) + /// ``` + /// + /// When you call this initializer and pass it a transferable value, it + /// calls [`exported(as:)`](https://developer.apple.com/documentation/coretransferable/transferable/exported(as:)) + /// on that value. This operation may take some time, so this initializer + /// suspends the calling task until it is complete. + public init( + exporting transferableValue: T, + as contentType: UTType? = nil, + named preferredName: String? = nil, + sourceLocation: SourceLocation = #_sourceLocation + ) async throws where T: Transferable, AttachableValue == _AttachableTransferableWrapper { + let transferableWrapper = try await _AttachableTransferableWrapper(exporting: transferableValue, as: contentType) + self.init(transferableWrapper, named: preferredName, sourceLocation: sourceLocation) + } +} + +// MARK: - + +/// A type describing errors that can occur when attaching a transferable value. +enum TransferableAttachmentError: Error { + /// The developer did not pass a content type and the value did not list any + /// that conform to `UTType.data`. + case suitableContentTypeNotFound +} + +extension TransferableAttachmentError: CustomStringConvertible { + var description: String { + switch self { + case .suitableContentTypeNotFound: + "The value does not list any exported content types that conform to 'UTType.data'." + } + } +} +#endif diff --git a/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift b/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift new file mode 100644 index 000000000..97c90eda7 --- /dev/null +++ b/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift @@ -0,0 +1,67 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +#if canImport(CoreTransferable) +public import Testing +public import CoreTransferable + +private import Foundation +import UniformTypeIdentifiers + +/// A wrapper type representing transferable values that can be attached +/// indirectly. +/// +/// You do not need to use this type directly. Instead, initialize an instance +/// of ``Attachment`` using an instance of a type conforming to the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable) +/// protocol. +@_spi(Experimental) +@available(_transferableAPI, *) +public struct _AttachableTransferableWrapper: Sendable where T: Transferable { + /// The transferable value. + private var _transferableValue: T + + /// The content type used to export the transferable value. + private var _contentType: UTType + + /// The exported form of the transferable value. + private var _bytes: Data + + init(exporting transferableValue: T, as contentType: UTType?) async throws { + let contentType = contentType ?? transferableValue.exportedContentTypes() + .first { $0.conforms(to: .data) } + guard let contentType else { + throw TransferableAttachmentError.suitableContentTypeNotFound + } + + _transferableValue = transferableValue + _contentType = contentType + _bytes = try await transferableValue.exported(as: contentType) + } +} + +// MARK: - + +@_spi(Experimental) +@available(_transferableAPI, *) +extension _AttachableTransferableWrapper: AttachableWrapper { + public var wrappedValue: T { + _transferableValue + } + + public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { + try _bytes.withUnsafeBytes(body) + } + + public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { + let baseName = _transferableValue.suggestedFilename ?? suggestedName + return (baseName as NSString).appendingPathExtension(for: _contentType) + } +} +#endif diff --git a/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt b/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt new file mode 100644 index 000000000..0de219b45 --- /dev/null +++ b/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt @@ -0,0 +1,25 @@ +# This source file is part of the Swift.org open source project +# +# Copyright (c) 2026 Apple Inc. and the Swift project authors +# Licensed under Apache License v2.0 with Runtime Library Exception +# +# See https://swift.org/LICENSE.txt for license information +# See https://swift.org/CONTRIBUTORS.txt for Swift project authors + +if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") +include(ModuleABIName) + add_library(_Testing_CoreTransferable + Attachments/_AttachableTransferableWrapper.swift + Attachments/Attachment+Transferable.swift + ReexportTesting.swift) + + target_link_libraries(_Testing_CoreTransferable PUBLIC + Testing + _Testing_CoreGraphics) + + target_compile_options(_Testing_CoreTransferable PRIVATE + -enable-library-evolution + -emit-module-interface -emit-module-interface-path $/_Testing_CoreTransferable.swiftinterface) + + _swift_testing_install_target(_Testing_CoreTransferable) +endif() diff --git a/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift b/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift new file mode 100644 index 000000000..ae413c1c3 --- /dev/null +++ b/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift @@ -0,0 +1,11 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2026 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +@_exported @_spi(Experimental) @_spi(ForToolsIntegrationOnly) public import Testing diff --git a/Sources/Testing/Testing.swiftcrossimport/CoreTransferable.swiftoverlay b/Sources/Testing/Testing.swiftcrossimport/CoreTransferable.swiftoverlay new file mode 100644 index 000000000..727419b3b --- /dev/null +++ b/Sources/Testing/Testing.swiftcrossimport/CoreTransferable.swiftoverlay @@ -0,0 +1,3 @@ +version: 1 +modules: +- name: _Testing_CoreTransferable diff --git a/Tests/TestingTests/AttachmentTests.swift b/Tests/TestingTests/AttachmentTests.swift index 854ecd133..0316f18e1 100644 --- a/Tests/TestingTests/AttachmentTests.swift +++ b/Tests/TestingTests/AttachmentTests.swift @@ -26,6 +26,10 @@ import CoreGraphics import CoreImage import _Testing_CoreImage #endif +#if canImport(CoreTransferable) && canImport(_Testing_CoreTransferable) +import CoreTransferable +@_spi(Experimental) import _Testing_CoreTransferable +#endif #if canImport(UIKit) && canImport(_Testing_UIKit) import UIKit import _Testing_UIKit @@ -233,7 +237,7 @@ struct AttachmentTests { #expect((attachment.attachableValue as Any) is AnyAttachable.Wrapped) #expect(attachment.sourceLocation.fileID == #fileID) - valueAttached() + valueAttached() } await Test { @@ -452,6 +456,45 @@ struct AttachmentTests { } } #endif + +#if canImport(CoreTransferable) && canImport(_Testing_CoreTransferable) + @available(_transferableAPI, *) + @Test("Attach Transferable-conformant value") + func transferable() async throws { + let value = MyTransferable() + let attachment = try await Attachment(exporting: value, as: .plainText) + #expect(value == attachment.attachableValue) + try attachment.withUnsafeBytes { bytes in + #expect(Array(bytes) == Array(MyTransferable.stringValue.utf8)) + } + } + + @available(_transferableAPI, *) + @Test("Attach Transferable-conformant value with a nonsensical type") + func transferableWithNonsensicalType() async throws { + let value = MyTransferable() + await #expect(throws: (any Error).self) { + _ = try await Attachment(exporting: value, as: .gif) + } + } + + @available(_transferableAPI, *) + @Test("Preferred name of Transferable-conformant value") + func transferablePreferredName() async throws { + let value = MyTransferable() + let attachment = try await Attachment(exporting: value) + #expect(attachment.preferredName == "untitled.txt") + } + + @available(_transferableAPI, *) + @Test("Attach Transferable-conformant value with no available type") + func transferableWithNoAvailableType() async throws { + let value = MyBadTransferable() + await #expect(throws: (any Error).self) { + _ = try await Attachment(exporting: value) + } + } +#endif } extension AttachmentTests { @@ -976,6 +1019,26 @@ struct MySendableAttachableWithDefaultByteCount: Attachable, Sendable { } } +#if canImport(CoreTransferable) && canImport(_Testing_CoreTransferable) +struct MyTransferable: Transferable, Equatable { + static let stringValue = "This isn't even my exported form!" + + static var transferRepresentation: some TransferRepresentation { + DataRepresentation(exportedContentType: .plainText) { instance in + Data(Self.stringValue.utf8) + } + } +} + +struct MyBadTransferable: Transferable, Equatable { + static var transferRepresentation: some TransferRepresentation { + DataRepresentation(exportedContentType: .directory) { _ in + throw MyError() + } + } +} +#endif + #if canImport(Foundation) && canImport(_Testing_Foundation) struct MyCodableAttachable: Codable, Attachable, Sendable { var string: String diff --git a/cmake/modules/shared/AvailabilityDefinitions.cmake b/cmake/modules/shared/AvailabilityDefinitions.cmake index e6b716657..67390f910 100644 --- a/cmake/modules/shared/AvailabilityDefinitions.cmake +++ b/cmake/modules/shared/AvailabilityDefinitions.cmake @@ -17,5 +17,6 @@ add_compile_options( "SHELL:$<$:-Xfrontend -define-availability -Xfrontend \"_regexAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0\">" "SHELL:$<$:-Xfrontend -define-availability -Xfrontend \"_swiftVersionAPI:macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0\">" "SHELL:$<$:-Xfrontend -define-availability -Xfrontend \"_typedThrowsAPI:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0\">" + "SHELL:$<$:-Xfrontend -define-availability -Xfrontend \"_transferableAPI:macOS 15.2, iOS 18.2, watchOS 11.2, tvOS 18.2, visionOS 2.2\">" "SHELL:$<$:-Xfrontend -define-availability -Xfrontend \"_castingWithNonCopyableGenerics:macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0\">" "SHELL:$<$:-Xfrontend -define-availability -Xfrontend \"_distantFuture:macOS 99.0, iOS 99.0, watchOS 99.0, tvOS 99.0, visionOS 99.0\">") From a56a4a56c627474de56a78c95503dd039149e926 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Fri, 3 Apr 2026 13:21:04 -0500 Subject: [PATCH 2/6] Fixup Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index b56775ea2..b73f4dca1 100644 --- a/Package.swift +++ b/Package.swift @@ -264,7 +264,7 @@ let package = Package( ], path: "Sources/Overlays/_Testing_CoreTransferable", exclude: ["CMakeLists.txt"], - swiftSettings: .packageSettings + .enableLibraryEvolution() + .moduleABIName("_Testing_CoreTransferable") + swiftSettings: .packageSettings() + .enableLibraryEvolution() + .moduleABIName("_Testing_CoreTransferable") ), .target( name: "_Testing_Foundation", From 87fc46ed17b4dfb61826b4b78b0fc05272dae3c0 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 22 Apr 2026 10:48:06 -0400 Subject: [PATCH 3/6] Incorporate feedback --- Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt b/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt index 0de219b45..2752a5c3f 100644 --- a/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt +++ b/Sources/Overlays/_Testing_CoreTransferable/CMakeLists.txt @@ -7,15 +7,14 @@ # See https://swift.org/CONTRIBUTORS.txt for Swift project authors if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") -include(ModuleABIName) + include(ModuleABIName) add_library(_Testing_CoreTransferable Attachments/_AttachableTransferableWrapper.swift Attachments/Attachment+Transferable.swift ReexportTesting.swift) target_link_libraries(_Testing_CoreTransferable PUBLIC - Testing - _Testing_CoreGraphics) + Testing) target_compile_options(_Testing_CoreTransferable PRIVATE -enable-library-evolution From dfa0fff2c8bed6526ad9b8161a751ccbed87cade Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 22 Apr 2026 10:52:06 -0400 Subject: [PATCH 4/6] Promote to API --- .../Attachments/Attachment+Transferable.swift | 1 - .../Attachments/_AttachableTransferableWrapper.swift | 2 -- .../Overlays/_Testing_CoreTransferable/ReexportTesting.swift | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift b/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift index c6eb677b1..4be19f5eb 100644 --- a/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift +++ b/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift @@ -14,7 +14,6 @@ public import CoreTransferable public import UniformTypeIdentifiers -@_spi(Experimental) @available(_transferableAPI, *) extension Attachment { /// Initialize an instance of this type that encloses the given transferable diff --git a/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift b/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift index 97c90eda7..4b935ac35 100644 --- a/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift +++ b/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift @@ -21,7 +21,6 @@ import UniformTypeIdentifiers /// You do not need to use this type directly. Instead, initialize an instance /// of ``Attachment`` using an instance of a type conforming to the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable) /// protocol. -@_spi(Experimental) @available(_transferableAPI, *) public struct _AttachableTransferableWrapper: Sendable where T: Transferable { /// The transferable value. @@ -48,7 +47,6 @@ public struct _AttachableTransferableWrapper: Sendable where T: Transferable // MARK: - -@_spi(Experimental) @available(_transferableAPI, *) extension _AttachableTransferableWrapper: AttachableWrapper { public var wrappedValue: T { diff --git a/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift b/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift index ae413c1c3..20cbda91d 100644 --- a/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift +++ b/Sources/Overlays/_Testing_CoreTransferable/ReexportTesting.swift @@ -8,4 +8,4 @@ // See https://swift.org/CONTRIBUTORS.txt for Swift project authors // -@_exported @_spi(Experimental) @_spi(ForToolsIntegrationOnly) public import Testing +@_exported public import Testing From b621007e14f34a32a7ef35bb3fba89c3ce16af09 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Wed, 22 Apr 2026 17:16:17 -0400 Subject: [PATCH 5/6] API availability --- .../Attachments/Attachment+Transferable.swift | 7 +++++++ .../_AttachableTransferableWrapper.swift | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift b/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift index 4be19f5eb..1a8ed6431 100644 --- a/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift +++ b/Sources/Overlays/_Testing_CoreTransferable/Attachments/Attachment+Transferable.swift @@ -14,6 +14,9 @@ public import CoreTransferable public import UniformTypeIdentifiers +/// @Metadata { +/// @Available(Swift, introduced: 6.4) +/// } @available(_transferableAPI, *) extension Attachment { /// Initialize an instance of this type that encloses the given transferable @@ -49,6 +52,10 @@ extension Attachment { /// calls [`exported(as:)`](https://developer.apple.com/documentation/coretransferable/transferable/exported(as:)) /// on that value. This operation may take some time, so this initializer /// suspends the calling task until it is complete. + /// + /// @Metadata { + /// @Available(Swift, introduced: 6.4) + /// } public init( exporting transferableValue: T, as contentType: UTType? = nil, diff --git a/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift b/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift index 4b935ac35..d26855681 100644 --- a/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift +++ b/Sources/Overlays/_Testing_CoreTransferable/Attachments/_AttachableTransferableWrapper.swift @@ -21,6 +21,10 @@ import UniformTypeIdentifiers /// You do not need to use this type directly. Instead, initialize an instance /// of ``Attachment`` using an instance of a type conforming to the [`Transferable`](https://developer.apple.com/documentation/coretransferable/transferable) /// protocol. +/// +/// @Metadata { +/// @Available(Swift, introduced: 6.4) +/// } @available(_transferableAPI, *) public struct _AttachableTransferableWrapper: Sendable where T: Transferable { /// The transferable value. @@ -47,16 +51,28 @@ public struct _AttachableTransferableWrapper: Sendable where T: Transferable // MARK: - +/// @Metadata { +/// @Available(Swift, introduced: 6.4) +/// } @available(_transferableAPI, *) extension _AttachableTransferableWrapper: AttachableWrapper { + /// @Metadata { + /// @Available(Swift, introduced: 6.4) + /// } public var wrappedValue: T { _transferableValue } + /// @Metadata { + /// @Available(Swift, introduced: 6.4) + /// } public func withUnsafeBytes(for attachment: borrowing Attachment, _ body: (UnsafeRawBufferPointer) throws -> R) throws -> R { try _bytes.withUnsafeBytes(body) } + /// @Metadata { + /// @Available(Swift, introduced: 6.4) + /// } public borrowing func preferredName(for attachment: borrowing Attachment, basedOn suggestedName: String) -> String { let baseName = _transferableValue.suggestedFilename ?? suggestedName return (baseName as NSString).appendingPathExtension(for: _contentType) From 0d60c9d0394ac7fd45afb12838695bbf83014149 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Thu, 23 Apr 2026 17:02:31 -0400 Subject: [PATCH 6/6] Add DocC --- Sources/Testing/Testing.docc/Attachments.md | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/Sources/Testing/Testing.docc/Attachments.md b/Sources/Testing/Testing.docc/Attachments.md index f28c8be74..018576d95 100644 --- a/Sources/Testing/Testing.docc/Attachments.md +++ b/Sources/Testing/Testing.docc/Attachments.md @@ -76,6 +76,44 @@ extension SalesReport: Encodable, Attachable {} your test target imports the [Foundation](https://developer.apple.com/documentation/foundation) module. +### Attach transferable values + +If you have a value you want to save as an attachment that conforms to +[`Transferable`](https://developer.apple.com/documentation/CoreTransferable/Transferable), +you can create an instance of ``Attachment`` from it when you import the +[Core Transferable](https://developer.apple.com/documentation/coretransferable) +module. + +```swift +import Testing +import CoreTransferable + +struct SalesReport { ... } +extension SalesReport: Encodable, Attachable {} + +@Test func `sales report adds up`() async throws { + let salesReport = await generateSalesReport() + try salesReport.validate() + let attachment = try await Attachment(exporting: salesReport) + Attachment.record(attachment) +} +``` + +- Important: The testing library provides this ``Attachment`` initializer only + if your test target imports the [Core Transferable](https://developer.apple.com/documentation/coretransferable) + module. + +When you create an attachment from a transferable value, the testing library +calls the value's [`exported(as:)`](https://developer.apple.com/documentation/coretransferable/transferable/exported(as:)) +function. By default, the testing library calls the value's [`exportedContentTypes(_:)`](https://developer.apple.com/documentation/coretransferable/transferable/exportedcontenttypes(_:)) +function and uses the first returned content type that conforms to [`UTType.data`](https://developer.apple.com/documentation/uniformtypeidentifiers/uttype-swift.struct/data). +If you want to use a different format for the attachment, you can pass another +content type supported by the attachable value when you create the attachment. + +```swift +let attachment = try await Attachment(exporting: salesReport, as: .pdf) +``` + ### Attach files and directories If you have a file you want to save as an attachment, you can attach it using