├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── Documentation └── xc.png ├── LICENSE ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── ShapeBuilder │ ├── BuiltShape.swift │ ├── EitherShape.swift │ ├── EmptyShape.swift │ ├── InsettableShapeBuilder.swift │ └── ShapeBuilder.swift └── Tests └── ShapeBuilderTests ├── InsettableShapeBuilderTests.swift ├── ShapeBuilderTests.swift └── __Snapshots__ ├── InsettableShapeBuilderTests ├── test_conditional_false.1.png ├── test_conditional_false_inset_by.1.png ├── test_conditional_true.1.png ├── test_conditional_true_inset_by.1.png ├── test_optional_exists.1.png ├── test_optional_exists_inset_by.1.png ├── test_optional_nil.1.png └── test_optional_nil_inset_by.1.png └── ShapeBuilderTests ├── test_conditional_false.1.png ├── test_conditional_true.1.png ├── test_optional_exists.1.png └── test_optional_nil.1.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | .DS_Store -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Documentation/xc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Documentation/xc.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Daniel Peter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | release: 2 | swift run rocket ${version} 3 | 4 | .PHONY: release -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Logger", 6 | "repositoryURL": "https://github.com/shibapm/Logger", 7 | "state": { 8 | "branch": null, 9 | "revision": "53c3ecca5abe8cf46697e33901ee774236d94cce", 10 | "version": "0.2.3" 11 | } 12 | }, 13 | { 14 | "package": "PackageConfig", 15 | "repositoryURL": "https://github.com/shibapm/PackageConfig.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "bf90dc69fa0792894b08a0b74cf34029694ae486", 19 | "version": "0.13.0" 20 | } 21 | }, 22 | { 23 | "package": "Rocket", 24 | "repositoryURL": "https://github.com/shibapm/Rocket", 25 | "state": { 26 | "branch": null, 27 | "revision": "51a77ce5fa66c42715c14dcc542c01cd7a60fb27", 28 | "version": "1.2.0" 29 | } 30 | }, 31 | { 32 | "package": "SnapshotTesting", 33 | "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing", 34 | "state": { 35 | "branch": null, 36 | "revision": "f8a9c997c3c1dab4e216a8ec9014e23144cbab37", 37 | "version": "1.9.0" 38 | } 39 | }, 40 | { 41 | "package": "SwiftShell", 42 | "repositoryURL": "https://github.com/kareman/SwiftShell", 43 | "state": { 44 | "branch": null, 45 | "revision": "a6014fe94c3dbff0ad500e8da4f251a5d336530b", 46 | "version": "5.1.0-beta.1" 47 | } 48 | }, 49 | { 50 | "package": "Yams", 51 | "repositoryURL": "https://github.com/jpsim/Yams", 52 | "state": { 53 | "branch": null, 54 | "revision": "9ff1cc9327586db4e0c8f46f064b6a82ec1566fa", 55 | "version": "4.0.6" 56 | } 57 | } 58 | ] 59 | }, 60 | "version": 1 61 | } 62 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "ShapeBuilder", 6 | platforms: [ 7 | .iOS(.v13), 8 | .macOS(.v10_15) 9 | ], 10 | products: [ 11 | .library( 12 | name: "ShapeBuilder", 13 | targets: ["ShapeBuilder"] 14 | ), 15 | ], 16 | dependencies: [ 17 | .package(url: "https://github.com/shibapm/Rocket", from: "1.2.0"), // dev 18 | .package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing", .upToNextMajor(from: "1.9.0")) // dev 19 | ], 20 | targets: [ 21 | .target( 22 | name: "ShapeBuilder", 23 | dependencies: [] 24 | ), 25 | .testTarget(name: "ShapeBuilderTests", dependencies: ["ShapeBuilder", "SnapshotTesting"], exclude: ["__Snapshots__"]) // dev 26 | ], 27 | swiftLanguageVersions: [.v5] 28 | ) 29 | 30 | #if canImport(PackageConfig) 31 | import PackageConfig 32 | 33 | let config = PackageConfiguration( 34 | [ 35 | "rocket": [ 36 | "pre_release_checks": [ 37 | "clean_git" 38 | ] 39 | ] 40 | ] 41 | ) 42 | .write() 43 | #endif 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ShapeBuilder 2 | A result builder implementation that allows to define shape building closures and variables. 3 | 4 | ## Problem 5 | In SwiftUI, you can end up in a situation in which you would like to change a `Shape` property based on a view style. Imagine, you build a view that should either take a circular appearance or have its corners rounded given a certain corner radius. Probably you would end up with something along the lines of: 6 | 7 | ```swift 8 | struct MyFancyView: View { 9 | let isRound: Bool 10 | 11 | var body: some View { 12 | // Fancy content here 13 | .mask(maskingLayer) 14 | } 15 | 16 | var maskingLayer: some Shape { 17 | if isRound { 18 | return Circle() 19 | } else { 20 | return RoundedRectangle(cornerRadius: 10) 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | However, this code doesn't compile because maskingLayer declares an opaque return type, but has no return statements in its body from which to infer an underlying type. In other words: Swift expects that maskingLayer is always of the same type. 27 | 28 | ## Possible solutions 29 | ### Type erasure 30 | One way to solve this is to introduce a type-erased AnyShape helper and erase the returned maskingLayer to AnyShape. This approach is similar to SwiftUI's built-in, type-erasing AnyView. 31 | 32 | ```swift 33 | struct AnyShape: Shape { 34 | let _path: (CGRect) -> Path 35 | 36 | init(_ shape: S) { 37 | _path = shape.path(in:) 38 | } 39 | 40 | func path(in rect: CGRect) -> Path { 41 | _path(rect) 42 | } 43 | } 44 | 45 | struct MyFancyView: View { 46 | let isRound: Bool 47 | 48 | var body: some View { 49 | // Fancy content here 50 | .mask(maskingLayer) 51 | } 52 | 53 | var maskingLayer: some Shape { 54 | if isRound { 55 | return AnyShape(Circle()) 56 | } else { 57 | return AnyShape(RoundedRectangle(cornerRadius: 10)) 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | As you can see, this requires us to wrap our raw shapes in AnyShape type-erasing wrappers which isn't the most beautiful code you'll ever see, but it works, doesn't it? 🤷‍♂️ 64 | 65 | ## Result builders to the rescue 66 | A second approach to solving this, is to define a result builder similar to SwiftUI's own `@ViewBuilder`. This library implements `@ShapeBuilder` and `@InsettableShapeBuilder` result builders, allowing you to get rid of type-erasing shape wrappers views and even return statements. Mark your computed property or functions with the according result builder and you're good to go. 67 | 68 | ```swift 69 | struct MyFancyView: View { 70 | let isRound: Bool 71 | 72 | var body: some View { 73 | // Fancy content here 74 | .mask(maskingLayer) 75 | } 76 | 77 | @ShapeBuilder var maskingLayer: some Shape { 78 | if isRound { 79 | Circle() 80 | } else { 81 | RoundedRectangle(cornerRadius: 10) 82 | } 83 | } 84 | } 85 | ``` 86 | 87 | ### BuiltShape / BuiltInsettableShape 88 | Additionally, this library provides the `BuiltShape` and `BuiltInsettableShape` protocols which shares similiarities with SwiftUI's `View` protocol. They define a get-only `shape` computed property which is marked with a `@ShapeBuilder`/`@InsettableShapeBuilder` annotation. 89 | 90 | This allows you to define `BuiltShape`s which themselves are shapes and take the form of the shape property. 91 | 92 | ```swift 93 | struct MyFancyMask: BuiltShape { 94 | let isCircle: Bool 95 | 96 | var shape: some Shape { 97 | if isCircle { 98 | Circle() 99 | } else { 100 | RoundedRectangle(cornerRadius: 10) 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | ## Installation 107 | ### Swift Package 108 | If you want to add **ShapeBuilder** to your Swift packages, add it as a dependency to your `Package.swift`. 109 | 110 | ```swift 111 | dependencies: [ 112 | .package( 113 | url: "https://github.com/ohitsdaniel/ShapeBuilder.git", 114 | from: "0.1.0" 115 | ) 116 | ], 117 | targets: [ 118 | .target( 119 | name: "MyAwesomePackage", 120 | dependencies: [ 121 | "ShapeBuilder" 122 | ] 123 | ) 124 | ] 125 | ``` 126 | 127 | ### Xcode 128 |

129 | 130 | You can add **ShapeBuilder** to your project via Xcode. Open your project, click on **File → Swift Packages → Add Package Dependency…**, enter the repository url (https://github.com/ohitsdaniel/ShapeBuilder.git) and add the package products to your app target. 131 | 132 | ## License 133 | This library is released under the MIT license. See [LICENSE](LICENSE) for details. 134 | -------------------------------------------------------------------------------- /Sources/ShapeBuilder/BuiltShape.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | /// A convenience protocol wrapping a `@ShapeBuilder` 4 | /// 5 | /// Shares similarities with SwiftUI's `View` protocol. Defines a get-only `shape` computed property which is marked with `@ShapeBuilder`. 6 | /// 7 | /// **Example** 8 | /// ```swift 9 | /// struct MyFancyMask: BuiltShape { 10 | /// let isCircle: Bool 11 | /// 12 | /// var shape: some Shape { 13 | /// if isCircle { 14 | /// Circle() 15 | /// } else { 16 | /// RoundedRectangle(cornerRadius: 10) 17 | /// } 18 | /// } 19 | /// } 20 | /// ``` 21 | public protocol BuiltShape: Shape { 22 | associatedtype S: Shape 23 | 24 | @ShapeBuilder var shape: S { get } 25 | } 26 | 27 | public extension BuiltShape { 28 | func path(in rect: CGRect) -> Path { 29 | shape.path(in: rect) 30 | } 31 | } 32 | 33 | /// A convenience protocol wrapping a `@InsettableShapeBuilder` 34 | /// 35 | /// Shares similarities with SwiftUI's `View` protocol. Defines a get-only `shape` computed property which is marked with `@InsettableShapeBuilder`. 36 | /// 37 | /// **Example** 38 | /// ```swift 39 | /// struct MyFancyMask: BuiltInsettableShape { 40 | /// let isCircle: Bool 41 | /// 42 | /// var shape: some InsettableShape { 43 | /// if isCircle { 44 | /// Circle() 45 | /// } else { 46 | /// RoundedRectangle(cornerRadius: 10) 47 | /// } 48 | /// } 49 | /// } 50 | /// ``` 51 | public protocol BuiltInsettableShape: InsettableShape { 52 | associatedtype S: InsettableShape 53 | 54 | @InsettableShapeBuilder var shape: S { get } 55 | } 56 | 57 | public extension BuiltInsettableShape { 58 | func path(in rect: CGRect) -> Path { 59 | shape.path(in: rect) 60 | } 61 | 62 | func inset(by amount: CGFloat) -> S.InsetShape { 63 | shape.inset(by: amount) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/ShapeBuilder/EitherShape.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public enum EitherShape: Shape { 4 | case first(First) 5 | case second(Second) 6 | 7 | public func path(in rect: CGRect) -> Path { 8 | switch self { 9 | case let .first(first): 10 | return first.path(in: rect) 11 | case let .second(second): 12 | return second.path(in: rect) 13 | } 14 | } 15 | } 16 | 17 | public enum EitherInsettableShape: InsettableShape { 18 | case first(First) 19 | case second(Second) 20 | 21 | public func path(in rect: CGRect) -> Path { 22 | switch self { 23 | case let .first(first): 24 | return first.path(in: rect) 25 | case let .second(second): 26 | return second.path(in: rect) 27 | } 28 | } 29 | 30 | public func inset(by amount: CGFloat) -> EitherInsettableShape { 31 | switch self { 32 | case let .first(first): 33 | return .first(first.inset(by: amount)) 34 | case let .second(second): 35 | return .second(second.inset(by: amount)) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/ShapeBuilder/EmptyShape.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct EmptyShape: InsettableShape { 4 | public init() {} 5 | 6 | public func path(in rect: CGRect) -> Path { 7 | Path() 8 | } 9 | 10 | public func inset(by amount: CGFloat) -> some InsettableShape { 11 | self 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/ShapeBuilder/InsettableShapeBuilder.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | #if swift(>=5.4) 4 | @resultBuilder 5 | public enum InsettableShapeBuilder { 6 | public static func buildBlock(_ builder: S) -> some InsettableShape { 7 | builder 8 | } 9 | } 10 | #else 11 | @_functionBuilder 12 | public enum InsettableShapeBuilder { 13 | public static func buildBlock(_ builder: S) -> some InsettableShape { 14 | builder 15 | } 16 | } 17 | #endif 18 | 19 | public extension InsettableShapeBuilder { 20 | static func buildOptional(_ component: S?) -> EitherInsettableShape { 21 | component.flatMap(EitherInsettableShape.first) ?? .second(EmptyShape()) 22 | } 23 | 24 | static func buildEither( 25 | first component: First 26 | ) -> EitherInsettableShape { 27 | .first(component) 28 | } 29 | 30 | static func buildEither( 31 | second component: Second 32 | ) -> EitherInsettableShape { 33 | .second(component) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/ShapeBuilder/ShapeBuilder.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | #if swift(>=5.4) 4 | @resultBuilder 5 | public enum ShapeBuilder { 6 | public static func buildBlock(_ builder: S) -> some Shape { 7 | builder 8 | } 9 | } 10 | #else 11 | @_functionBuilder 12 | public enum ShapeBuilder { 13 | public static func buildBlock(_ builder: S) -> some Shape { 14 | builder 15 | } 16 | } 17 | #endif 18 | 19 | public extension ShapeBuilder { 20 | static func buildOptional(_ component: S?) -> EitherShape { 21 | component.flatMap(EitherShape.first) ?? EitherShape.second(EmptyShape()) 22 | } 23 | 24 | static func buildEither(first component: First) -> EitherShape { 25 | .first(component) 26 | } 27 | 28 | static func buildEither(second component: Second) -> EitherShape { 29 | .second(component) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/InsettableShapeBuilderTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ShapeBuilder 2 | import SnapshotTesting 3 | import SwiftUI 4 | import XCTest 5 | 6 | struct ConditionalTestInsettableShape: BuiltInsettableShape { 7 | let condition: Bool 8 | 9 | var shape: some InsettableShape { 10 | if condition { 11 | Circle() 12 | } else { 13 | Rectangle() 14 | } 15 | } 16 | } 17 | 18 | struct OptionalTestInsettableShape: BuiltInsettableShape { 19 | let value: Int? 20 | 21 | var shape: some InsettableShape { 22 | if let _ = value { 23 | Circle() 24 | } 25 | } 26 | } 27 | 28 | final class InsettableShapeBuilderTests: XCTestCase { 29 | // snapshots recorded on iPhone 12, iOS 14.5 30 | func test_conditional_true() { 31 | let sut = ConditionalTestInsettableShape(condition: true) 32 | .frame(width: 30, height: 30) 33 | 34 | assertSnapshot(matching: sut, as: .image) 35 | } 36 | 37 | func test_conditional_false() { 38 | let sut = ConditionalTestInsettableShape(condition: false) 39 | .frame(width: 30, height: 30) 40 | 41 | assertSnapshot(matching: sut, as: .image) 42 | } 43 | 44 | func test_optional_exists() { 45 | let sut = OptionalTestInsettableShape(value: 1) 46 | .frame(width: 30, height: 30) 47 | 48 | assertSnapshot(matching: sut, as: .image) 49 | } 50 | 51 | func test_optional_nil() { 52 | let sut = OptionalTestInsettableShape(value: nil) 53 | .frame(width: 30, height: 30) 54 | 55 | assertSnapshot(matching: sut, as: .image) 56 | } 57 | 58 | // MARK: - inset(by:) 59 | func test_conditional_true_inset_by() { 60 | let sut = ConditionalTestInsettableShape(condition: true) 61 | .inset(by: 5) 62 | .frame(width: 30, height: 30) 63 | 64 | assertSnapshot(matching: sut, as: .image) 65 | } 66 | 67 | func test_conditional_false_inset_by() { 68 | let sut = ConditionalTestInsettableShape(condition: false) 69 | .inset(by: 5) 70 | .frame(width: 30, height: 30) 71 | 72 | assertSnapshot(matching: sut, as: .image) 73 | } 74 | 75 | func test_optional_exists_inset_by() { 76 | let sut = OptionalTestInsettableShape(value: 1) 77 | .inset(by: 5) 78 | .frame(width: 30, height: 30) 79 | 80 | assertSnapshot(matching: sut, as: .image) 81 | } 82 | 83 | func test_optional_nil_inset_by() { 84 | let sut = OptionalTestInsettableShape(value: nil) 85 | .inset(by: 5) 86 | .frame(width: 30, height: 30) 87 | 88 | assertSnapshot(matching: sut, as: .image) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/ShapeBuilderTests.swift: -------------------------------------------------------------------------------- 1 | @testable import ShapeBuilder 2 | import SnapshotTesting 3 | import SwiftUI 4 | import XCTest 5 | 6 | struct ConditionalTestShape: BuiltShape { 7 | let condition: Bool 8 | 9 | var shape: some Shape { 10 | if condition { 11 | Circle() 12 | } else { 13 | Rectangle() 14 | } 15 | } 16 | } 17 | 18 | struct OptionalTestShape: BuiltShape { 19 | let value: Int? 20 | 21 | var shape: some Shape { 22 | if let _ = value { 23 | Circle() 24 | } 25 | } 26 | } 27 | 28 | final class ShapeBuilderTests: XCTestCase { 29 | // snapshots recorded on iPhone 12, iOS 14.5 30 | func test_conditional_true() { 31 | let sut = ConditionalTestShape(condition: true) 32 | .frame(width: 30, height: 30) 33 | 34 | assertSnapshot(matching: sut, as: .image) 35 | } 36 | 37 | func test_conditional_false() { 38 | let sut = ConditionalTestShape(condition: false) 39 | .frame(width: 30, height: 30) 40 | 41 | assertSnapshot(matching: sut, as: .image) 42 | } 43 | 44 | func test_optional_exists() { 45 | let sut = OptionalTestShape(value: 1) 46 | .frame(width: 30, height: 30) 47 | 48 | assertSnapshot(matching: sut, as: .image) 49 | } 50 | 51 | func test_optional_nil() { 52 | let sut = OptionalTestShape(value: nil) 53 | .frame(width: 30, height: 30) 54 | 55 | assertSnapshot(matching: sut, as: .image) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_false.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_false.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_false_inset_by.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_false_inset_by.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_true.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_true.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_true_inset_by.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_conditional_true_inset_by.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_exists.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_exists.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_exists_inset_by.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_exists_inset_by.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_nil.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_nil.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_nil_inset_by.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/InsettableShapeBuilderTests/test_optional_nil_inset_by.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_conditional_false.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_conditional_false.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_conditional_true.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_conditional_true.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_optional_exists.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_optional_exists.1.png -------------------------------------------------------------------------------- /Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_optional_nil.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohitsdaniel/ShapeBuilder/1fe7313f0afd121ba2e48e462e5aabc27178471b/Tests/ShapeBuilderTests/__Snapshots__/ShapeBuilderTests/test_optional_nil.1.png --------------------------------------------------------------------------------