diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 719b259d1..27815d448 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,11 @@ jobs: strategy: matrix: xcode: - - "14.3.1" + - 15.4 + - '16.0' - name: macOS 13 (Xcode ${{ matrix.xcode }}) - runs-on: macos-13 + name: macOS + runs-on: macos-14 steps: - uses: actions/checkout@v3 - name: Select Xcode ${{ matrix.xcode }} diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift index b2d473d38..fefe1e6f7 100644 --- a/Sources/SnapshotTesting/AssertSnapshot.swift +++ b/Sources/SnapshotTesting/AssertSnapshot.swift @@ -1,5 +1,9 @@ import XCTest +#if canImport(Testing) + import Testing +#endif + /// Enhances failure messages with a command line diff tool expression that can be copied and pasted /// into a terminal. @available( @@ -9,12 +13,33 @@ import XCTest "Use 'withSnapshotTesting' to customize the diff tool. See the documentation for more information." ) public var diffTool: SnapshotTestingConfiguration.DiffTool { - get { _diffTool } + get { + _diffTool + } set { _diffTool = newValue } } @_spi(Internals) -public var _diffTool: SnapshotTestingConfiguration.DiffTool = .default +public var _diffTool: SnapshotTestingConfiguration.DiffTool { + get { + #if canImport(Testing) + if let test = Test.current { + for trait in test.traits.reversed() { + if let diffTool = (trait as? _SnapshotsTestTrait)?.configuration.diffTool { + return diffTool + } + } + } + #endif + return __diffTool + } + set { + __diffTool = newValue + } +} + +@_spi(Internals) +public var __diffTool: SnapshotTestingConfiguration.DiffTool = .default /// Whether or not to record all new references. @available( @@ -28,7 +53,26 @@ public var isRecording: Bool { } @_spi(Internals) -public var _record: SnapshotTestingConfiguration.Record = { +public var _record: SnapshotTestingConfiguration.Record { + get { + #if canImport(Testing) + if let test = Test.current { + for trait in test.traits.reversed() { + if let record = (trait as? _SnapshotsTestTrait)?.configuration.record { + return record + } + } + } + #endif + return __record + } + set { + __record = newValue + } +} + +@_spi(Internals) +public var __record: SnapshotTestingConfiguration.Record = { if let value = ProcessInfo.processInfo.environment["SNAPSHOT_TESTING_RECORD"], let record = SnapshotTestingConfiguration.Record(rawValue: value) { @@ -311,7 +355,9 @@ public func verifySnapshot( func recordSnapshot() throws { try snapshotting.diffing.toData(diffable).write(to: snapshotFileUrl) #if !os(Linux) && !os(Windows) - if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { + if !isSwiftTesting, + ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") + { XCTContext.runActivity(named: "Attached Recorded Snapshot") { activity in let attachment = XCTAttachment(contentsOfFile: snapshotFileUrl) activity.add(attachment) @@ -373,7 +419,9 @@ public func verifySnapshot( if !attachments.isEmpty { #if !os(Linux) && !os(Windows) - if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS") { + if ProcessInfo.processInfo.environment.keys.contains("__XCODE_BUILT_PRODUCTS_DIR_PATHS"), + !isSwiftTesting + { XCTContext.runActivity(named: "Attached Failure Diff") { activity in attachments.forEach { activity.add($0) diff --git a/Sources/SnapshotTesting/Documentation.docc/Articles/IntegratingWithTestFrameworks.md b/Sources/SnapshotTesting/Documentation.docc/Articles/IntegratingWithTestFrameworks.md index 2f7020772..b11b3437a 100644 --- a/Sources/SnapshotTesting/Documentation.docc/Articles/IntegratingWithTestFrameworks.md +++ b/Sources/SnapshotTesting/Documentation.docc/Articles/IntegratingWithTestFrameworks.md @@ -67,21 +67,14 @@ class FeatureTests: XCTestCase { This will override the `diffTool` and `record` properties for each test function. -Swift's new testing framework does not currently have a public API for this kind of customization. -There is an experimental feature, called `CustomExecutionTrait`, that does gives us this ability, -and the library provides such a trait called ``Testing/Trait/snapshots(diffTool:record:)``. It can -be attached to any `@Test` or `@Suite` to configure snapshot testing: +Swift's new testing framework also allows for this kind of configuration, both for a single test +and an entire test suite. This is done via what are known as "test traits": ```swift -@_spi(Experimental) import SnapshotTesting +import SnapshotTesting @Suite(.snapshots(record: .all, diffTool: .ksdiff)) struct FeatureTests { … } ``` - -> Important: You must import SnapshotTesting with the `@_spi(Experimental)` attribute to get access -to this functionality because Swift Testing's own `CustomExecutionTrait` is hidden behind the same -SPI flag. This means this API is subject to change in the future, but hopefully Apple will -publicize this tool soon. diff --git a/Sources/SnapshotTesting/Documentation.docc/Articles/MigrationGuides/MigratingTo1.17.md b/Sources/SnapshotTesting/Documentation.docc/Articles/MigrationGuides/MigratingTo1.17.md index 489516307..feefdcae3 100644 --- a/Sources/SnapshotTesting/Documentation.docc/Articles/MigrationGuides/MigratingTo1.17.md +++ b/Sources/SnapshotTesting/Documentation.docc/Articles/MigrationGuides/MigratingTo1.17.md @@ -151,16 +151,12 @@ in an XCTest context or a Swift Testing context, and will determine if it should `Issue.record` to trigger a test failure. For the most part you can write tests for Swift Testing exactly as you would for XCTest. However, -there is one major difference. Swift Testing does not (yet) have a substitute for `invokeTest`, -which we used alongside `withSnapshotTesting` to customize snapshotting for a full test class. - -There is an experimental version of this tool in Swift Testing, called `CustomExecutionTrait`, and -this library provides such a trait called ``Testing/Trait/snapshots(diffTool:record:)``. It allows -you to customize snapshots for a `@Test` or `@Suite`, but to get access to it you must perform an -`@_spi(Experimental)` import of snapshot testing: +there is one major difference. In order to override a snapshot's +[configuration]() for a particular test or an entire suite you +must use what are known as "test traits": ```swift -@_spi(Experimental) import SnapshotTesting +import SnapshotTesting @Suite(.snapshots(record: .all, diffTool: .ksdiff)) struct FeatureTests { @@ -169,7 +165,4 @@ struct FeatureTests { ``` That will override the `diffTool` and `record` options for the entire `FeatureTests` suite. - -> Important: As evident by the usage of `@_spi(Experimental)` this API is subject to change. As -soon as the Swift Testing library finalizes its API for `CustomExecutionTrait` we will update -the library accordingly and remove the `@_spi` annotation. +These traits can also be used on individual `@Test`s too. diff --git a/Sources/SnapshotTesting/Internal/RecordIssue.swift b/Sources/SnapshotTesting/Internal/RecordIssue.swift index 01c700df8..214761180 100644 --- a/Sources/SnapshotTesting/Internal/RecordIssue.swift +++ b/Sources/SnapshotTesting/Internal/RecordIssue.swift @@ -4,6 +4,14 @@ import XCTest import Testing #endif +var isSwiftTesting: Bool { + #if canImport(Testing) + return Test.current != nil + #else + return false + #endif +} + @_spi(Internals) public func recordIssue( _ message: @autoclosure () -> String, diff --git a/Sources/SnapshotTesting/SnapshotsTestTrait.swift b/Sources/SnapshotTesting/SnapshotsTestTrait.swift index 6b4b82f77..4b53db67c 100644 --- a/Sources/SnapshotTesting/SnapshotsTestTrait.swift +++ b/Sources/SnapshotTesting/SnapshotsTestTrait.swift @@ -1,7 +1,6 @@ #if canImport(Testing) - @_spi(Experimental) import Testing + import Testing - @_spi(Experimental) extension Trait where Self == _SnapshotsTestTrait { /// Configure snapshot testing in a suite or test. /// @@ -31,22 +30,21 @@ } /// A type representing the configuration of snapshot testing. - @_spi(Experimental) - public struct _SnapshotsTestTrait: CustomExecutionTrait, SuiteTrait, TestTrait { + public struct _SnapshotsTestTrait: SuiteTrait, TestTrait { public let isRecursive = true let configuration: SnapshotTestingConfiguration - public func execute( - _ function: @escaping () async throws -> Void, - for test: Test, - testCase: Test.Case? - ) async throws { - try await withSnapshotTesting( - record: configuration.record, - diffTool: configuration.diffTool - ) { - try await function() - } - } + // public func execute( + // _ function: @escaping () async throws -> Void, + // for test: Test, + // testCase: Test.Case? + // ) async throws { + // try await withSnapshotTesting( + // record: configuration.record, + // diffTool: configuration.diffTool + // ) { + // try await function() + // } + // } } #endif diff --git a/Sources/SnapshotTesting/Snapshotting/CGPath.swift b/Sources/SnapshotTesting/Snapshotting/CGPath.swift index cd48ec40e..65470605c 100644 --- a/Sources/SnapshotTesting/Snapshotting/CGPath.swift +++ b/Sources/SnapshotTesting/Snapshotting/CGPath.swift @@ -153,6 +153,7 @@ private let defaultNumberFormatter: NumberFormatter = { let numberFormatter = NumberFormatter() + numberFormatter.decimalSeparator = "." numberFormatter.minimumFractionDigits = 1 numberFormatter.maximumFractionDigits = 3 return numberFormatter diff --git a/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift b/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift index c506bb329..b84a59bf3 100644 --- a/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift +++ b/Sources/SnapshotTesting/Snapshotting/NSBezierPath.swift @@ -105,6 +105,7 @@ private let defaultNumberFormatter: NumberFormatter = { let numberFormatter = NumberFormatter() + numberFormatter.decimalSeparator = "." numberFormatter.minimumFractionDigits = 1 numberFormatter.maximumFractionDigits = 3 return numberFormatter diff --git a/Tests/InlineSnapshotTestingTests/AssertInlineSnapshotSwiftTests.swift b/Tests/InlineSnapshotTestingTests/AssertInlineSnapshotSwiftTests.swift index 0a193452c..9a5d324e3 100644 --- a/Tests/InlineSnapshotTestingTests/AssertInlineSnapshotSwiftTests.swift +++ b/Tests/InlineSnapshotTestingTests/AssertInlineSnapshotSwiftTests.swift @@ -2,7 +2,7 @@ import Testing import Foundation import InlineSnapshotTesting - @_spi(Experimental) import SnapshotTesting + import SnapshotTesting @Suite( .snapshots( @@ -21,6 +21,28 @@ } } + @Test func inlineSnapshotFailure() { + withKnownIssue { + assertInlineSnapshot(of: ["Hello", "World"], as: .dump) { + """ + ▿ 2 elements + - "Hello" + + """ + } + } matching: { issue in + issue.description == """ + Issue recorded: Snapshot did not match. Difference: … + + @@ −1,3 +1,4 @@ +  ▿ 2 elements +   - "Hello" + + - "World" +   + """ + } + } + @Test func inlineSnapshot_NamedTrailingClosure() { assertInlineSnapshot( of: ["Hello", "World"], as: .dump, diff --git a/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift b/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift new file mode 100644 index 000000000..aee524aff --- /dev/null +++ b/Tests/SnapshotTestingTests/AssertSnapshotSwiftTests.swift @@ -0,0 +1,18 @@ +#if canImport(Testing) + import Testing + import Foundation + import SnapshotTesting + + @Suite( + .snapshots( + record: .missing + ) + ) + struct AssertSnapshotTests { + @Test func dump() { + struct User { let id: Int, name: String, bio: String } + let user = User(id: 1, name: "Blobby", bio: "Blobbed around the world.") + assertSnapshot(of: user, as: .dump) + } + } +#endif diff --git a/Tests/SnapshotTestingTests/SnapshotsTraitTests.swift b/Tests/SnapshotTestingTests/SnapshotsTraitTests.swift index 0d2a685d2..3ac726003 100644 --- a/Tests/SnapshotTestingTests/SnapshotsTraitTests.swift +++ b/Tests/SnapshotTestingTests/SnapshotsTraitTests.swift @@ -1,13 +1,12 @@ #if compiler(>=6) && canImport(Testing) - @_spi(Experimental) import Testing - @_spi(Experimental) @_spi(Internals) import SnapshotTesting + import Testing + @_spi(Internals) import SnapshotTesting struct SnapshotsTraitTests { @Test(.snapshots(diffTool: "ksdiff")) func testDiffTool() { #expect( - SnapshotTestingConfiguration.current? - .diffTool?(currentFilePath: "old.png", failedFilePath: "new.png") + _diffTool(currentFilePath: "old.png", failedFilePath: "new.png") == "ksdiff old.png new.png" ) } @@ -17,8 +16,7 @@ @Test(.snapshots(diffTool: "difftool")) func testDiffToolOverride() { #expect( - SnapshotTestingConfiguration.current? - .diffTool?(currentFilePath: "old.png", failedFilePath: "new.png") + _diffTool(currentFilePath: "old.png", failedFilePath: "new.png") == "difftool old.png new.png" ) } @@ -28,11 +26,10 @@ @Test func config() { #expect( - SnapshotTestingConfiguration.current? - .diffTool?(currentFilePath: "old.png", failedFilePath: "new.png") + _diffTool(currentFilePath: "old.png", failedFilePath: "new.png") == "ksdiff old.png new.png" ) - #expect(SnapshotTestingConfiguration.current?.record == .all) + #expect(_record == .all) } @Suite(.snapshots(record: .failed, diffTool: "diff")) @@ -40,11 +37,10 @@ @Test func config() { #expect( - SnapshotTestingConfiguration.current? - .diffTool?(currentFilePath: "old.png", failedFilePath: "new.png") + _diffTool(currentFilePath: "old.png", failedFilePath: "new.png") == "diff old.png new.png" ) - #expect(SnapshotTestingConfiguration.current?.record == .failed) + #expect(_record == .failed) } } } diff --git a/Tests/SnapshotTestingTests/SwiftTestingTests.swift b/Tests/SnapshotTestingTests/SwiftTestingTests.swift new file mode 100644 index 000000000..d7fba7afc --- /dev/null +++ b/Tests/SnapshotTestingTests/SwiftTestingTests.swift @@ -0,0 +1,26 @@ +#if compiler(>=6) && canImport(Testing) + import Testing + import SnapshotTesting + + @Suite(.snapshots(diffTool: "ksdiff")) + struct SwiftTestingTests { + @Test func testSnapshot() { + assertSnapshot(of: ["Hello", "World"], as: .dump) + } + + @Test func testSnapshotFailure() { + withKnownIssue { + assertSnapshot(of: ["Goodbye", "World"], as: .dump) + } matching: { issue in + issue.description.hasSuffix( + """ + @@ −1,4 +1,4 @@ +  ▿ 2 elements + − - "Hello" + + - "Goodbye" +   - "World" + """) + } + } + } +#endif diff --git a/Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.1.txt b/Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.1.txt new file mode 100644 index 000000000..0e3f0406c --- /dev/null +++ b/Tests/SnapshotTestingTests/__Snapshots__/AssertSnapshotSwiftTests/dump.1.txt @@ -0,0 +1,4 @@ +▿ User + - bio: "Blobbed around the world." + - id: 1 + - name: "Blobby" diff --git a/Tests/SnapshotTestingTests/__Snapshots__/SwiftTestingTests/testSnapshot.1.txt b/Tests/SnapshotTestingTests/__Snapshots__/SwiftTestingTests/testSnapshot.1.txt new file mode 100644 index 000000000..3b0fb714f --- /dev/null +++ b/Tests/SnapshotTestingTests/__Snapshots__/SwiftTestingTests/testSnapshot.1.txt @@ -0,0 +1,3 @@ +▿ 2 elements + - "Hello" + - "World" diff --git a/Tests/SnapshotTestingTests/__Snapshots__/SwiftTestingTests/testSnapshotFailure.1.txt b/Tests/SnapshotTestingTests/__Snapshots__/SwiftTestingTests/testSnapshotFailure.1.txt new file mode 100644 index 000000000..3b0fb714f --- /dev/null +++ b/Tests/SnapshotTestingTests/__Snapshots__/SwiftTestingTests/testSnapshotFailure.1.txt @@ -0,0 +1,3 @@ +▿ 2 elements + - "Hello" + - "World"