diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..a55a1ca --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,35 @@ +# This workflow will build a Swift project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift + +name: Swift + +on: + push: + branches: [ "main" ] + pull_request: + branches: '**' + +env: + LCOV_PATH: .build/artifacts/info.lcov + BUILD_FOLDER: .build/debug/ParameterizedTestingPackageTests.xctest/Contents/MacOS/ParameterizedTestingPackageTests + PROFDATA_FOLDER: .build/debug/codecov/default.profdata + +jobs: + build: + + runs-on: macos-12 + + steps: + - uses: actions/checkout@v3 + + - name: Build + run: swift build + + - name: Run tests + run: | + swift test --enable-code-coverage + xcrun llvm-cov report $BUILD_FOLDER -instr-profile $PROFDATA_FOLDER + xcrun llvm-cov export -format="lcov" $BUILD_FOLDER -instr-profile $PROFDATA_FOLDER > $LCOV_PATH + + - name: Determine coverage + uses: codecov/codecov-action@v3.1.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2654ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.build +.swiftpm diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..17e9ef5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Cameron Cooke + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..636657e --- /dev/null +++ b/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", + "state" : { + "revision" : "f29e2014f6230cf7d5138fc899da51c7f513d467", + "version" : "1.10.0" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..8123c37 --- /dev/null +++ b/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version: 5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ParameterizedTesting", + platforms: [ + .iOS(.v14), + .macOS(.v10_15), + ], + products: [ + .library( + name: "ParameterizedTesting", + targets: ["ParameterizedTesting"] + ), + ], + dependencies: [ + .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.10.0"), + ], + targets: [ + .target( + name: "ParameterizedTesting", + dependencies: [] + ), + .testTarget( + name: "ExampleTests", + dependencies: [ + "ParameterizedTesting", + .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), + ], + exclude: ["__Snapshots__"] + ) + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..2031834 --- /dev/null +++ b/README.md @@ -0,0 +1,164 @@ +# ParameterizedTesting + +![Run Tests](https://github.com/cameroncooke/SwiftParameterizedTesting/workflows/Swift/badge.svg) +[![License](https://img.shields.io/badge/license-mit-brightgreen.svg)](https://en.wikipedia.org/wiki/MIT_License) +[![codecov](https://codecov.io/gh/cameroncooke/SwiftParameterizedTesting/branch/main/graph/badge.svg?token=MPBFPN7OLI)](https://codecov.io/gh/cameroncooke/SwiftParameterizedTesting) + +ParameterizedTesting is a Swift library for executing parameterized tests using XCTest for iOS. + +## What are Parameterized tests? + +A parameterized test is a test that runs over and over again using different values. + +## Are there specific use-cases in mind? + +Yes, this kind of test automation is especially helpful when snapshot testing where you want to ensure you have a snapshot representation for each configuration of a view where they are many permutations, but this can also be used for logic testing. + +## Won't I just end up with a single test failing in Xcode if any of the permutations fail? + +No, this is where the magic happens, `ParameterizedTesting` will dynamically create individual run-time tests for each permutation so that you know exactly which combination of values failed. When you run the test suite the each test will appear in the Xcode test navigator. + +## Any warnings? + +Yes, please use this library carefully! It's very easy to end up with 1000s of run-time tests with just a few lines of code. Please be aware that the size of the test suite will grow exponentially for each additional set of values. + +```swift + override class func values() -> ([WeatherData.Weather], [Int]) { + ( + [.raining, .sunny, .cloudy, .snowing], + [12, 34, 3, 22, 0] + ) + } +``` + +Above is a simple set of test values, two arrays of 4 and 5 values respectfully. This test alone will generate `4 * 5 = 20` tests. + +Now let's look at a larger test dataset: + +```swift + override class func values() -> ( + [String], + [Int], + [String], + [String], + [Double], + [String], + [String], + [String], + [Bool] + ) { + ( + [ + "raining", + "sunny", + "cloudy", + "snowing", + ], + [ + 12, + 34, + 3, + 22, + 0, + ], + [ + "apples", + "oranges", + ], + [ + "red", + "blue", + ], + [ + 12.99, + 18.50, + ], + [ + "GB", + "EU", + "FR", + "US", + ], + [ + "large", + "small", + ], + [ + "red", + "blue", + ], + [ + true, + false, + ] + ) + } +``` + +Above is a larger set of test values, 9 arrays of 4, 5, 2, 2, 2, 4, 2, 2, 2 values respectfully. This test will generate `4 * 5 * 2 * 2 * 2 * 4 * 2 * 2 * 2 = 5120` tests! + +It's important that you really consider the value of the parameterized tests and use wisely. Even though can test every combination doesn't mean you should and in general you shouldn't. + +## Example usage + +To use, in your test target create a new Swift file and subclass one of the `ParameterizedTestCase` base classes. Say you want to create test permutations from two sets of data you would use the `ParameterizedTestCase2` base class as shown in the below example, you can use up to 9 datasets in total. Just use corresponding class name i.e. `ParameterizedTestCase9` + +```swift +final class MySnapshotTests: ParameterizedTestCase2 { + override class var defaultTestSuite: XCTestSuite { + customTestSuite(self) + } + + // MARK: - Internal - + + override class func values() -> ([Weather], [Int]) { + ( + [.raining, .sunny, .cloudy, .snowing], + [12, 34, 3, 22, 0] + ) + } + + override func testAllCombinations( + _ weather: Weather, + _ temperature: CelsiusTemperature, + _ expectedResult: Void? + ) { + let view = WeatherView( + viewModel: WeatherView.ViewModel( + weather: weather, + temperature: temperature, + ) + ) + + assertSnapshot( + matching: view, + testName: "\(weather)_\(temperature)" + ) + } +} +``` + +These classes use generics which you must define the types of when defining the class. In the above example the types of each dataset are defined as ``. The Void generic pamemter is a placeholder for an expected value which is only needed when creating logic tests. For snapshot tests it's not needed so here we seet this to void. + +It's important that you override `defaultTestSuite` and paste in the following code: + +```swift + override class var defaultTestSuite: XCTestSuite { + customTestSuite(self) + } +``` + +This is needed to workaround an issue when creating the run-time tests. + +Next just override the `testAllCombinations()` method, this will be autocompleted for you if using Xcode with the parameters already correctly typed. In your method just add the test logic that performs whichever test action you want with the injected values. + + +More fully worked examples including logic tests can be found in [Tests/ExampleTests](Tests/ExampleTests) + +## Credits + +This library is in part derived from https://github.com/approvals/ApprovalTests.Swift + +## License + +This library is released under the MIT license. See [LICENSE](LICENSE) for details. diff --git a/Sources/ParameterizedTesting/ParameterizedTestCaseKey.swift b/Sources/ParameterizedTesting/ParameterizedTestCaseKey.swift new file mode 100644 index 0000000..a174442 --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestCaseKey.swift @@ -0,0 +1,19 @@ +// +// ParameterizedTestCaseKey.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import Foundation + +public enum ParameterizedTestCaseKey { + static var value1 = "value1" + static var value2 = "value2" + static var value3 = "value3" + static var value4 = "value4" + static var value5 = "value5" + static var value6 = "value6" + static var value7 = "value7" + static var value8 = "value8" + static var value9 = "value9" + static var expectedValue = "expectedValue" +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestHandler.swift b/Sources/ParameterizedTesting/ParameterizedTestHandler.swift new file mode 100644 index 0000000..43e355f --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestHandler.swift @@ -0,0 +1,257 @@ +// +// ParameterizedTestHandler.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +public enum ParameterizedTestHandler { + private enum UNUSED { + case unused + } + + private static var unused: [UNUSED] { [.unused] } + + // MARK: - Public - + + public static func allCombinations( + _ params1: [IN1], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1) -> Void + ) { + let handlerWithAllParameters: (IN1, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED) -> Void = { + _ = $1 + _ = $2 + _ = $3 + _ = $4 + _ = $5 + _ = $6 + _ = $7 + _ = $8 + handler($0) + } + + allCombinations( + params1, + unused, unused, unused, unused, unused, unused, unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED) -> Void = { + _ = $2 + _ = $3 + _ = $4 + _ = $5 + _ = $6 + _ = $7 + _ = $8 + handler($0, $1) + } + + allCombinations( + params1, params2, + unused, unused, unused, unused, unused, unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2, IN3) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, IN3, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED) -> Void = { + _ = $3 + _ = $4 + _ = $5 + _ = $6 + _ = $7 + _ = $8 + handler($0, $1, $2) + } + + allCombinations( + params1, params2, params3, + unused, unused, unused, unused, unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + _ params4: [IN4], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2, IN3, IN4) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, IN3, IN4, UNUSED, UNUSED, UNUSED, UNUSED, UNUSED) -> Void = { + _ = $4 + _ = $5 + _ = $6 + _ = $7 + _ = $8 + handler($0, $1, $2, $3) + } + + allCombinations( + params1, params2, params3, params4, + unused, unused, unused, unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + _ params4: [IN4], + _ params5: [IN5], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2, IN3, IN4, IN5) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, IN3, IN4, IN5, UNUSED, UNUSED, UNUSED, UNUSED) -> Void = { + _ = $5 + _ = $6 + _ = $7 + _ = $8 + handler($0, $1, $2, $3, $4) + } + + allCombinations( + params1, params2, params3, params4, params5, + unused, unused, unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + _ params4: [IN4], + _ params5: [IN5], + _ params6: [IN6], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2, IN3, IN4, IN5, IN6) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, IN3, IN4, IN5, IN6, UNUSED, UNUSED, UNUSED) -> Void = { + _ = $6 + _ = $7 + _ = $8 + handler($0, $1, $2, $3, $4, $5) + } + + allCombinations( + params1, params2, params3, params4, params5, params6, + unused, unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + _ params4: [IN4], + _ params5: [IN5], + _ params6: [IN6], + _ params7: [IN7], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2, IN3, IN4, IN5, IN6, IN7) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, IN3, IN4, IN5, IN6, IN7, UNUSED, UNUSED) -> Void = { + _ = $7 + _ = $8 + handler($0, $1, $2, $3, $4, $5, $6) + } + + allCombinations( + params1, params2, params3, params4, params5, params6, params7, + unused, unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + _ params4: [IN4], + _ params5: [IN5], + _ params6: [IN6], + _ params7: [IN7], + _ params8: [IN8], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: @escaping (IN1, IN2, IN3, IN4, IN5, IN6, IN7, IN8) -> Void + ) { + let handlerWithAllParameters: (IN1, IN2, IN3, IN4, IN5, IN6, IN7, IN8, UNUSED) -> Void = { + _ = $8 + handler($0, $1, $2, $3, $4, $5, $6, $7) + } + + allCombinations( + params1, params2, params3, params4, params5, params6, params7, params8, + unused, + file: file, line: line, + handlerWithAllParameters + ) + } + + public static func allCombinations( + _ params1: [IN1], + _ params2: [IN2], + _ params3: [IN3], + _ params4: [IN4], + _ params5: [IN5], + _ params6: [IN6], + _ params7: [IN7], + _ params8: [IN8], + _ params9: [IN9], + file: StaticString = #filePath, + line: UInt = #line, + _ handler: (IN1, IN2, IN3, IN4, IN5, IN6, IN7, IN8, IN9) -> Void + ) { + for in1 in params1 { + for in2 in params2 { + for in3 in params3 { + for in4 in params4 { + for in5 in params5 { + for in6 in params6 { + for in7 in params7 { + for in8 in params8 { + for in9 in params9 { + handler(in1, in2, in3, in4, in5, in6, in7, in8, in9) + } + } + } + } + } + } + } + } + } + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase2.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase2.swift new file mode 100644 index 0000000..d78157b --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase2.swift @@ -0,0 +1,88 @@ +// +// ParameterizedTestsCase2.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase2: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + { value1, value2 in + + let selector = ParameterizedTestCase2.registerTestMethod( + name: "\(value1)_\(value2)".lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations(_ value1: IN1, _ value2: IN2, _ expectedResult: OUT?) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase3.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase3.swift new file mode 100644 index 0000000..6487d0f --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase3.swift @@ -0,0 +1,94 @@ +// +// ParameterizedTestsCase3.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase3: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + { value1, value2, value3 in + + let selector = ParameterizedTestCase3.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)".lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations(_ value1: IN1, _ value2: IN2, _ value3: IN3, _ expectedResult: OUT?) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase4.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase4.swift new file mode 100644 index 0000000..ec84662 --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase4.swift @@ -0,0 +1,107 @@ +// +// ParameterizedTestsCase4.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase4: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3, params4) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count * params4.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + params4, + { value1, value2, value3, value4 in + + let selector = ParameterizedTestCase4.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)_\(value4)".lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + test.setValue(value: value4, forKey: &ParameterizedTestCaseKey.value4) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3], [IN4]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations( + _ value1: IN1, + _ value2: IN2, + _ value3: IN3, + _ value4: IN4, + _ expectedResult: OUT? + ) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getValue4() -> IN4? { + getValue(forKey: &ParameterizedTestCaseKey.value4) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3(), + let value4 = getValue4() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, value4, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase5.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase5.swift new file mode 100644 index 0000000..e561851 --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase5.swift @@ -0,0 +1,114 @@ +// +// ParameterizedTestsCase5.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase5: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3, params4, params5) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count * params4.count * params5.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + params4, + params5, + { value1, value2, value3, value4, value5 in + + let selector = ParameterizedTestCase5.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)_\(value4)_\(value5)".lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + test.setValue(value: value4, forKey: &ParameterizedTestCaseKey.value4) + test.setValue(value: value5, forKey: &ParameterizedTestCaseKey.value5) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3], [IN4], [IN5]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations( + _ value1: IN1, + _ value2: IN2, + _ value3: IN3, + _ value4: IN4, + _ value5: IN5, + _ expectedResult: OUT? + ) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getValue4() -> IN4? { + getValue(forKey: &ParameterizedTestCaseKey.value4) + } + + func getValue5() -> IN5? { + getValue(forKey: &ParameterizedTestCaseKey.value5) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3(), let value4 = getValue4(), + let value5 = getValue5() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, value4, value5, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase6.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase6.swift new file mode 100644 index 0000000..edb693e --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase6.swift @@ -0,0 +1,122 @@ +// +// ParameterizedTestsCase6.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase6: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3, params4, params5, params6) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count * params4.count * params5.count * params6 + .count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + params4, + params5, + params6, + { value1, value2, value3, value4, value5, value6 in + + let selector = ParameterizedTestCase6.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)_\(value4)_\(value5)_\(value6)".lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + test.setValue(value: value4, forKey: &ParameterizedTestCaseKey.value4) + test.setValue(value: value5, forKey: &ParameterizedTestCaseKey.value5) + test.setValue(value: value6, forKey: &ParameterizedTestCaseKey.value6) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3], [IN4], [IN5], [IN6]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations( + _ value1: IN1, + _ value2: IN2, + _ value3: IN3, + _ value4: IN4, + _ value5: IN5, + _ value6: IN6, + _ expectedResult: OUT? + ) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getValue4() -> IN4? { + getValue(forKey: &ParameterizedTestCaseKey.value4) + } + + func getValue5() -> IN5? { + getValue(forKey: &ParameterizedTestCaseKey.value5) + } + + func getValue6() -> IN6? { + getValue(forKey: &ParameterizedTestCaseKey.value6) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3(), let value4 = getValue4(), + let value5 = getValue5(), let value6 = getValue6() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, value4, value5, value6, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase7.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase7.swift new file mode 100644 index 0000000..6d54c79 --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase7.swift @@ -0,0 +1,129 @@ +// +// ParameterizedTestsCase7.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase7: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3, params4, params5, params6, params7) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count * params4.count * params5.count * params6 + .count * params7.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + params4, + params5, + params6, + params7, + { value1, value2, value3, value4, value5, value6, value7 in + + let selector = ParameterizedTestCase7.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)_\(value4)_\(value5)_\(value6)_\(value7)".lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + test.setValue(value: value4, forKey: &ParameterizedTestCaseKey.value4) + test.setValue(value: value5, forKey: &ParameterizedTestCaseKey.value5) + test.setValue(value: value6, forKey: &ParameterizedTestCaseKey.value6) + test.setValue(value: value7, forKey: &ParameterizedTestCaseKey.value7) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3], [IN4], [IN5], [IN6], [IN7]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations( + _ value1: IN1, + _ value2: IN2, + _ value3: IN3, + _ value4: IN4, + _ value5: IN5, + _ value6: IN6, + _ value7: IN7, + _ expectedResult: OUT? + ) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getValue4() -> IN4? { + getValue(forKey: &ParameterizedTestCaseKey.value4) + } + + func getValue5() -> IN5? { + getValue(forKey: &ParameterizedTestCaseKey.value5) + } + + func getValue6() -> IN6? { + getValue(forKey: &ParameterizedTestCaseKey.value6) + } + + func getValue7() -> IN7? { + getValue(forKey: &ParameterizedTestCaseKey.value7) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3(), let value4 = getValue4(), + let value5 = getValue5(), let value6 = getValue6(), let value7 = getValue7() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, value4, value5, value6, value7, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase8.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase8.swift new file mode 100644 index 0000000..247c83d --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase8.swift @@ -0,0 +1,138 @@ +// +// ParameterizedTestsCase8.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase8: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3, params4, params5, params6, params7, params8) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count * params4.count * params5.count * params6 + .count * params7.count * params8.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + params4, + params5, + params6, + params7, + params8, + { value1, value2, value3, value4, value5, value6, value7, value8 in + + let selector = ParameterizedTestCase8.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)_\(value4)_\(value5)_\(value6)_\(value7)_\(value8)" + .lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + test.setValue(value: value4, forKey: &ParameterizedTestCaseKey.value4) + test.setValue(value: value5, forKey: &ParameterizedTestCaseKey.value5) + test.setValue(value: value6, forKey: &ParameterizedTestCaseKey.value6) + test.setValue(value: value7, forKey: &ParameterizedTestCaseKey.value7) + test.setValue(value: value8, forKey: &ParameterizedTestCaseKey.value8) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3], [IN4], [IN5], [IN6], [IN7], [IN8]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations( + _ value1: IN1, + _ value2: IN2, + _ value3: IN3, + _ value4: IN4, + _ value5: IN5, + _ value6: IN6, + _ value7: IN7, + _ value8: IN8, + _ expectedResult: OUT? + ) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getValue4() -> IN4? { + getValue(forKey: &ParameterizedTestCaseKey.value4) + } + + func getValue5() -> IN5? { + getValue(forKey: &ParameterizedTestCaseKey.value5) + } + + func getValue6() -> IN6? { + getValue(forKey: &ParameterizedTestCaseKey.value6) + } + + func getValue7() -> IN7? { + getValue(forKey: &ParameterizedTestCaseKey.value7) + } + + func getValue8() -> IN8? { + getValue(forKey: &ParameterizedTestCaseKey.value8) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3(), let value4 = getValue4(), + let value5 = getValue5(), let value6 = getValue6(), let value7 = getValue7(), + let value8 = getValue8() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, value4, value5, value6, value7, value8, expectedValue) + } +} diff --git a/Sources/ParameterizedTesting/ParameterizedTestsCase9.swift b/Sources/ParameterizedTesting/ParameterizedTestsCase9.swift new file mode 100644 index 0000000..c3598aa --- /dev/null +++ b/Sources/ParameterizedTesting/ParameterizedTestsCase9.swift @@ -0,0 +1,149 @@ +// +// ParameterizedTestsCase9.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest + +public class ParameterizedTestCase9: XCTestCase { + // MARK: - Public - + + public class func customTestSuite(_ subclassType: (some XCTestCase).Type) -> XCTestSuite { + let suite = XCTestSuite(forTestCaseClass: Self.self) + let (params1, params2, params3, params4, params5, params6, params7, params8, params9) = values() + + var counter = 0 + let totalCombinations = params1.count * params2.count * params3.count * params4.count * params5.count * params6 + .count * params7.count * params8.count * params9.count + let expectedValues = expectedValues() + + ParameterizedTestHandler.allCombinations( + params1, + params2, + params3, + params4, + params5, + params6, + params7, + params8, + params9, + { value1, value2, value3, value4, value5, value6, value7, value8, value9 in + + let selector = ParameterizedTestCase9.registerTestMethod( + name: "\(value1)_\(value2)_\(value3)_\(value4)_\(value5)_\(value6)_\(value7)_\(value8)_\(value9)" + .lowercased(), + testMethod: #selector(self.internalHandler) + ) + + let test = subclassType.init(selector: selector) + test.setValue(value: value1, forKey: &ParameterizedTestCaseKey.value1) + test.setValue(value: value2, forKey: &ParameterizedTestCaseKey.value2) + test.setValue(value: value3, forKey: &ParameterizedTestCaseKey.value3) + test.setValue(value: value4, forKey: &ParameterizedTestCaseKey.value4) + test.setValue(value: value5, forKey: &ParameterizedTestCaseKey.value5) + test.setValue(value: value6, forKey: &ParameterizedTestCaseKey.value6) + test.setValue(value: value7, forKey: &ParameterizedTestCaseKey.value7) + test.setValue(value: value8, forKey: &ParameterizedTestCaseKey.value8) + test.setValue(value: value9, forKey: &ParameterizedTestCaseKey.value9) + + if let expectedValues { + if expectedValues.count == totalCombinations { + let expectedValue = expectedValues[counter] + test.setValue(value: expectedValue, forKey: &ParameterizedTestCaseKey.expectedValue) + + } else { + preconditionFailure( + "The number of expected values (\(expectedValues.count)) does not satisfy the total number of all combinations of values (\(totalCombinations))." + ) + } + } + + suite.addTest(test) + counter += 1 + } + ) + + return suite + } + + public class func values() -> ([IN1], [IN2], [IN3], [IN4], [IN5], [IN6], [IN7], [IN8], [IN9]) { + fatalError("Not implemented") + } + + public class func expectedValues() -> [OUT]? { + nil + } + + public func testAllCombinations( + _ value1: IN1, + _ value2: IN2, + _ value3: IN3, + _ value4: IN4, + _ value5: IN5, + _ value6: IN6, + _ value7: IN7, + _ value8: IN8, + _ value9: IN9, + _ expectedResult: OUT? + ) { + fatalError("Not implemented") + } + + // MARK: - Internal - + + func getValue1() -> IN1? { + getValue(forKey: &ParameterizedTestCaseKey.value1) + } + + func getValue2() -> IN2? { + getValue(forKey: &ParameterizedTestCaseKey.value2) + } + + func getValue3() -> IN3? { + getValue(forKey: &ParameterizedTestCaseKey.value3) + } + + func getValue4() -> IN4? { + getValue(forKey: &ParameterizedTestCaseKey.value4) + } + + func getValue5() -> IN5? { + getValue(forKey: &ParameterizedTestCaseKey.value5) + } + + func getValue6() -> IN6? { + getValue(forKey: &ParameterizedTestCaseKey.value6) + } + + func getValue7() -> IN7? { + getValue(forKey: &ParameterizedTestCaseKey.value7) + } + + func getValue8() -> IN8? { + getValue(forKey: &ParameterizedTestCaseKey.value8) + } + + func getValue9() -> IN9? { + getValue(forKey: &ParameterizedTestCaseKey.value9) + } + + func getExpectedValue() -> OUT? { + getValue(forKey: &ParameterizedTestCaseKey.expectedValue) + } + + @objc + func internalHandler() { + guard let value1 = getValue1(), let value2 = getValue2(), let value3 = getValue3(), let value4 = getValue4(), + let value5 = getValue5(), let value6 = getValue6(), let value7 = getValue7(), let value8 = getValue8(), + let value9 = getValue9() else { + preconditionFailure("Params not set") + } + + let expectedValue = getExpectedValue() + testAllCombinations(value1, value2, value3, value4, value5, value6, value7, value8, value9, expectedValue) + } + + public override func tearDown() { + print("dfdsf") + } +} diff --git a/Sources/ParameterizedTesting/XCTest+Extension.swift b/Sources/ParameterizedTesting/XCTest+Extension.swift new file mode 100644 index 0000000..7033e2b --- /dev/null +++ b/Sources/ParameterizedTesting/XCTest+Extension.swift @@ -0,0 +1,68 @@ +// +// XCTest+Extension.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import Foundation +import ObjectiveC +import XCTest + +extension XCTestCase { + class func registerTestMethod(name: String, testMethod: Selector) -> Selector { + let selector = makeSelector(with: name) + + if let existingMethod = class_getInstanceMethod(self, selector) { + + let mthd = class_replaceMethod( + self, + selector, + method_getImplementation(existingMethod), + method_getTypeEncoding(existingMethod) + ) + precondition(mthd != nil, "Could not update test method.") + + } else { + + let method = class_getInstanceMethod(self, testMethod) + let success = class_addMethod( + self, + selector, + method_getImplementation(method!), + method_getTypeEncoding(method!) + ) + precondition(success, "Could not create test method.") + } + + return selector + } + + func setValue(value: some Any, forKey key: UnsafeMutablePointer) { + + if let value: AnyObject = getValue(forKey: key) { + objc_removeAssociatedObjects(value) + } + + let object = self as AnyObject + objc_setAssociatedObject( + object, + key, + value, + .OBJC_ASSOCIATION_RETAIN + ) + } + + func getValue(forKey key: UnsafeMutablePointer) -> T? { + let object = self as AnyObject + return objc_getAssociatedObject( + object, + key + ) as? T + } + + private static func makeSelector(with name: String) -> Selector { + // TODO: Remove special characters and spaces + // (technically they will work as we're using selectors but just to be on safe side) + let selector = sel_registerName("test_\(name)") + return selector + } +} diff --git a/Tests/ExampleTests/ExampleExpectedValuesTests.swift b/Tests/ExampleTests/ExampleExpectedValuesTests.swift new file mode 100644 index 0000000..896a61a --- /dev/null +++ b/Tests/ExampleTests/ExampleExpectedValuesTests.swift @@ -0,0 +1,88 @@ +// +// ExampleExpectedValuesTests.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import XCTest +@testable import ParameterizedTesting + +final class ExampleExpectedValuesTests: ParameterizedTestCase2 { + override class var defaultTestSuite: XCTestSuite { + customTestSuite(self) + } + + // MARK: - Internal - + + override class func values() -> ([WeatherData.Weather], [Int]) { + ( + [.raining, .sunny, .cloudy, .snowing], + [12, 34, 3, 22, 0] + ) + } + + override class func expectedValues() -> [String] { + [ + "It's raining and a mild 12 degrees celsius", + "It's raining and a hot 34 degrees celsius", + "It's raining and a cold 3 degrees celsius", + "It's raining and a comfortable 22 degrees celsius", + "It's raining and a freezing 0 degrees celsius", + + "It's sunny and a mild 12 degrees celsius", + "It's sunny and a hot 34 degrees celsius", + "It's sunny and a cold 3 degrees celsius", + "It's sunny and a comfortable 22 degrees celsius", + "It's sunny and a freezing 0 degrees celsius", + + "It's cloudy and a mild 12 degrees celsius", + "It's cloudy and a hot 34 degrees celsius", + "It's cloudy and a cold 3 degrees celsius", + "It's cloudy and a comfortable 22 degrees celsius", + "It's cloudy and a freezing 0 degrees celsius", + + "It's snowing and a mild 12 degrees celsius", + "It's snowing and a hot 34 degrees celsius", + "It's snowing and a cold 3 degrees celsius", + "It's snowing and a comfortable 22 degrees celsius", + "It's snowing and a freezing 0 degrees celsius", + ] + } + + override func testAllCombinations( + _ weather: WeatherData.Weather, + _ temperature: Int, + _ expectedResult: String? = nil + ) { + let sut = WeatherData(weather: weather, temperature: temperature) + XCTAssertEqual(sut.summary, expectedResult) + } +} + +// MARK: Fakes + +struct WeatherData { + enum Weather: String, Hashable, CaseIterable { + case raining + case sunny + case cloudy + case snowing + } + + let weather: Weather + let temperature: Int + + var summary: String { + "It's \(weather.rawValue) and a \(adjective) \(temperature) degrees celsius" + } + + private var adjective: String { + switch temperature { + case ...2: return "freezing" + case 3 ... 10: return "cold" + case 11 ... 19: return "mild" + case 20 ... 25: return "comfortable" + case 25...: return "hot" + default: return "" + } + } +} diff --git a/Tests/ExampleTests/ExampleSnapshotTests.swift b/Tests/ExampleTests/ExampleSnapshotTests.swift new file mode 100644 index 0000000..db4aebd --- /dev/null +++ b/Tests/ExampleTests/ExampleSnapshotTests.swift @@ -0,0 +1,128 @@ +// +// ExampleSnapshotTests.swift +// Copyright © 2022 Cameron Cooke. All rights reserved. +// + +import SnapshotTesting +import SwiftUI +import XCTest +@testable import ParameterizedTesting + +final class ExampleSnapshotTests: ParameterizedTestCase3 { + override class var defaultTestSuite: XCTestSuite { + customTestSuite(self) + } + + // MARK: - Internal - + + override class func values() -> ([Weather], [Int], [Theme]) { + ( + [.raining, .sunny, .cloudy, .snowing], + [12, 34, 3, 22, 0], + [.light, .dark] + ) + } + + override func testAllCombinations( + _ weather: Weather, + _ temperature: Int, + _ theme: Theme, + _ expectedResult: Void? + ) { + let view = WeatherView( + viewModel: WeatherView.ViewModel( + weather: weather, + temperature: temperature, + theme: theme + ) + ) + +#if os(iOS) || os(tvOS) + assertSnapshot( + matching: view, + as: .image( + precision: 0.9, + perceptualPrecision: 0.98, + layout: .fixed(width: 400, height: 45) + ), + testName: "\(weather)_\(temperature)_\(theme)_iOS" + ) +#else + let viewController = NSHostingController(rootView: view) + + XCTExpectFailure("Might fail as reference images were created on different OS environment") + + assertSnapshot( + matching: viewController, + as: .image( + precision: 0.9, + perceptualPrecision: 0.98, + size: CGSize(width: 400, height: 45) + ), + testName: "\(weather)_\(temperature)_\(theme)_macOS" + ) +#endif + } +} + +// MARK: Fakes + +typealias Weather = WeatherView.ViewModel.Weather +typealias Theme = WeatherView.ViewModel.Theme + +struct WeatherView: View { + struct ViewModel { + enum Weather: String, Hashable, CaseIterable { + case raining + case sunny + case cloudy + case snowing + } + + enum Theme: Hashable, CaseIterable { + case light + case dark + } + + let weather: Weather + let temperature: Int + let theme: Theme + + var summary: String { + "It's \(weather.rawValue) and a \(adjective) \(temperature) degrees celsius" + } + + private var adjective: String { + switch temperature { + case ...2: return "freezing" + case 3 ... 10: return "cold" + case 11 ... 19: return "mild" + case 20 ... 25: return "comfortable" + case 25...: return "hot" + default: return "" + } + } + + var weatherEmoji: String { + switch weather { + case .raining: return "🌧️" + case .sunny: return "☀️" + case .cloudy: return "☁️" + case .snowing: return "🌨️" + } + } + } + + let viewModel: ViewModel + + var body: some View { + VStack { + Text(viewModel.weatherEmoji) + .font(.headline) + Spacer(minLength: 4) + Text(viewModel.summary) + .foregroundColor(viewModel.theme == Theme.light ? Color.black : Color.white) + .background(viewModel.theme == Theme.light ? Color.gray : Color.black) + } + } +} diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_dark_iOS.1.png new file mode 100644 index 0000000..8711f2a Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_dark_macOS.1.png new file mode 100644 index 0000000..9deb250 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_light_iOS.1.png new file mode 100644 index 0000000..7d76473 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_light_macOS.1.png new file mode 100644 index 0000000..93f415d Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_0_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_dark_iOS.1.png new file mode 100644 index 0000000..c607d11 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_dark_macOS.1.png new file mode 100644 index 0000000..9254b88 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_light_iOS.1.png new file mode 100644 index 0000000..1d0cc8c Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_light_macOS.1.png new file mode 100644 index 0000000..26c1c2f Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_12_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_dark_iOS.1.png new file mode 100644 index 0000000..ea27e8b Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_dark_macOS.1.png new file mode 100644 index 0000000..b6bf1e8 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_light_iOS.1.png new file mode 100644 index 0000000..896a37a Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_light_macOS.1.png new file mode 100644 index 0000000..8f4769d Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_22_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_dark_iOS.1.png new file mode 100644 index 0000000..ca7fd48 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_dark_macOS.1.png new file mode 100644 index 0000000..70d4c22 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_light_iOS.1.png new file mode 100644 index 0000000..f3e3f6c Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_light_macOS.1.png new file mode 100644 index 0000000..ec2f604 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_34_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_dark_iOS.1.png new file mode 100644 index 0000000..02901cc Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_dark_macOS.1.png new file mode 100644 index 0000000..67dd9ff Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_light_iOS.1.png new file mode 100644 index 0000000..e3b394d Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_light_macOS.1.png new file mode 100644 index 0000000..94150d7 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/cloudy_3_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_dark_iOS.1.png new file mode 100644 index 0000000..6d8e5ff Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_dark_macOS.1.png new file mode 100644 index 0000000..df205a8 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_light_iOS.1.png new file mode 100644 index 0000000..e15f6dd Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_light_macOS.1.png new file mode 100644 index 0000000..a3d0a34 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_0_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_dark_iOS.1.png new file mode 100644 index 0000000..92b801e Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_dark_macOS.1.png new file mode 100644 index 0000000..b048605 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_light_iOS.1.png new file mode 100644 index 0000000..c70e6b9 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_light_macOS.1.png new file mode 100644 index 0000000..13bc9dd Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_12_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_dark_iOS.1.png new file mode 100644 index 0000000..d84e24d Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_dark_macOS.1.png new file mode 100644 index 0000000..c90bebf Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_light_iOS.1.png new file mode 100644 index 0000000..8e8241e Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_light_macOS.1.png new file mode 100644 index 0000000..fecb382 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_22_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_dark_iOS.1.png new file mode 100644 index 0000000..8a13dfb Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_dark_macOS.1.png new file mode 100644 index 0000000..429f0ec Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_light_iOS.1.png new file mode 100644 index 0000000..72517a4 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_light_macOS.1.png new file mode 100644 index 0000000..9eb8aca Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_34_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_dark_iOS.1.png new file mode 100644 index 0000000..18c6619 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_dark_macOS.1.png new file mode 100644 index 0000000..b341af2 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_light_iOS.1.png new file mode 100644 index 0000000..8d1d668 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_light_macOS.1.png new file mode 100644 index 0000000..80aaf06 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/raining_3_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_dark_iOS.1.png new file mode 100644 index 0000000..624ac20 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_dark_macOS.1.png new file mode 100644 index 0000000..82831c1 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_light_iOS.1.png new file mode 100644 index 0000000..774feb0 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_light_macOS.1.png new file mode 100644 index 0000000..2019f67 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_0_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_dark_iOS.1.png new file mode 100644 index 0000000..42c08a0 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_dark_macOS.1.png new file mode 100644 index 0000000..702a65e Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_light_iOS.1.png new file mode 100644 index 0000000..29b0917 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_light_macOS.1.png new file mode 100644 index 0000000..04871f7 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_12_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_dark_iOS.1.png new file mode 100644 index 0000000..29a5dbe Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_dark_macOS.1.png new file mode 100644 index 0000000..f8cfb51 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_light_iOS.1.png new file mode 100644 index 0000000..3f11011 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_light_macOS.1.png new file mode 100644 index 0000000..8094afd Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_22_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_dark_iOS.1.png new file mode 100644 index 0000000..65e1d3e Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_dark_macOS.1.png new file mode 100644 index 0000000..65fa200 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_light_iOS.1.png new file mode 100644 index 0000000..30f97c1 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_light_macOS.1.png new file mode 100644 index 0000000..8d74049 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_34_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_dark_iOS.1.png new file mode 100644 index 0000000..3a3f2b6 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_dark_macOS.1.png new file mode 100644 index 0000000..3eb4834 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_light_iOS.1.png new file mode 100644 index 0000000..419d09f Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_light_macOS.1.png new file mode 100644 index 0000000..adb1ab7 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/snowing_3_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_dark_iOS.1.png new file mode 100644 index 0000000..dda126c Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_dark_macOS.1.png new file mode 100644 index 0000000..591e552 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_light_iOS.1.png new file mode 100644 index 0000000..53f4f43 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_light_macOS.1.png new file mode 100644 index 0000000..9542a80 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_0_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_dark_iOS.1.png new file mode 100644 index 0000000..ed08c3c Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_dark_macOS.1.png new file mode 100644 index 0000000..5ec4758 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_light_iOS.1.png new file mode 100644 index 0000000..321aaa4 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_light_macOS.1.png new file mode 100644 index 0000000..0abb009 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_12_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_dark_iOS.1.png new file mode 100644 index 0000000..5cc4895 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_dark_macOS.1.png new file mode 100644 index 0000000..806c35d Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_light_iOS.1.png new file mode 100644 index 0000000..5f7682f Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_light_macOS.1.png new file mode 100644 index 0000000..8774533 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_22_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_dark_iOS.1.png new file mode 100644 index 0000000..92c1e80 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_dark_macOS.1.png new file mode 100644 index 0000000..5a96abc Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_light_iOS.1.png new file mode 100644 index 0000000..a54ed34 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_light_macOS.1.png new file mode 100644 index 0000000..e37f0bb Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_34_light_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_dark_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_dark_iOS.1.png new file mode 100644 index 0000000..6bb22a4 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_dark_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_dark_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_dark_macOS.1.png new file mode 100644 index 0000000..a2ab3ac Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_dark_macOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_light_iOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_light_iOS.1.png new file mode 100644 index 0000000..b276cd1 Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_light_iOS.1.png differ diff --git a/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_light_macOS.1.png b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_light_macOS.1.png new file mode 100644 index 0000000..127dc4c Binary files /dev/null and b/Tests/ExampleTests/__Snapshots__/ExampleSnapshotTests/sunny_3_light_macOS.1.png differ