├── .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
--------------------------------------------------------------------------------