├── Albums.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── LICENSE.md
├── Albums
├── Albums-Bridging-Header.h
├── NetworkSession.swift
├── NetworkDataHandler.swift
├── AlbumsApp.swift
├── NetworkImageSource.h
├── NetworkImageSource.m
├── AlbumsListRowModel.swift
├── NetworkImageSerialization.swift
├── NetworkJSONOperation.swift
├── NetworkImageOperation.swift
├── AlbumsListModel.swift
├── NetworkImageHandler.swift
├── NetworkJSONHandler.swift
├── AlbumsListView.swift
└── AlbumsListRowView.swift
├── AlbumsTests
├── AlbumsTests.swift
├── NetworkDataHandlerTests.swift
├── NetworkSessionTests.swift
├── AlbumsListRowModelTests.swift
├── AlbumsListModelTests.swift
├── NetworkImageSerializationTests.swift
├── NetworkJSONOperationTests.swift
├── NetworkImageOperationTests.swift
├── NetworkImageSourceTests.m
├── NetworkImageHandlerTests.swift
└── NetworkJSONHandlerTests.swift
└── README.md
/Albums.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Albums.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | TDD-Albums-II
2 |
3 | Copyright © 2021 North Bronson Software
4 |
5 | This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | 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.
10 |
--------------------------------------------------------------------------------
/Albums/Albums-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright © 2021 North Bronson Software
3 | //
4 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
5 | //
6 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 | //
8 | // 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.
9 | //
10 |
11 | #import "NetworkImageSource.h"
12 |
--------------------------------------------------------------------------------
/Albums/NetworkSession.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkSession.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol NetworkSessionURLSession {
17 | associatedtype URLSession : NetworkSessionURLSession
18 |
19 | static var shared: URLSession { get }
20 |
21 | func data(
22 | for: URLRequest,
23 | delegate: URLSessionTaskDelegate?
24 | ) async throws -> (
25 | Data,
26 | URLResponse
27 | )
28 | }
29 |
30 | extension URLSession : NetworkSessionURLSession {
31 |
32 | }
33 |
34 | struct NetworkSession {
35 |
36 | }
37 |
38 | extension NetworkSession {
39 | static func data(for request: URLRequest) async throws -> (
40 | Data,
41 | URLResponse
42 | ) {
43 | return try await URLSession.shared.data(
44 | for: request,
45 | delegate: nil
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Albums/NetworkDataHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkDataHandler.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | struct NetworkDataHandler {
17 |
18 | }
19 |
20 | extension NetworkDataHandler {
21 | static func data(
22 | with data: Data,
23 | response: URLResponse
24 | ) throws -> Data {
25 | guard
26 | let statusCode = (response as? HTTPURLResponse)?.statusCode,
27 | 200...299 ~= statusCode
28 | else {
29 | throw Self.Error(.statusCodeError)
30 | }
31 | return data
32 | }
33 | }
34 |
35 | extension NetworkDataHandler {
36 | struct Error : Swift.Error {
37 | enum Code {
38 | case statusCodeError
39 | }
40 |
41 | let code: Self.Code
42 | let underlying: Swift.Error?
43 |
44 | init(
45 | _ code: Self.Code,
46 | underlying: Swift.Error? = nil
47 | ) {
48 | self.code = code
49 | self.underlying = underlying
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Albums/AlbumsApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsApp.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import SwiftUI
15 |
16 | @main struct AlbumsApp: App {
17 | @StateObject private var model = ListModel()
18 | }
19 |
20 | extension AlbumsApp {
21 | private typealias JSONHandler = NetworkJSONHandler
22 | private typealias ImageHandler = NetworkImageHandler>
23 |
24 | private typealias JSONOperation = NetworkJSONOperation, JSONHandler>
25 | private typealias ImageOperation = NetworkImageOperation, ImageHandler>
26 |
27 | private typealias ListModel = AlbumsListModel
28 | private typealias ListRowModel = AlbumsListRowModel
29 |
30 | private typealias ListView = AlbumsListView
31 | }
32 |
33 | extension AlbumsApp {
34 | var body: some Scene {
35 | WindowGroup {
36 | ListView(
37 | model: self.model
38 | )
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/AlbumsTests/AlbumsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | func DataTestDouble() -> Data {
17 | return Data(UInt8.min...UInt8.max)
18 | }
19 |
20 | func HTTPURLResponseTestDouble(
21 | statusCode: Int = 200,
22 | headerFields: Dictionary? = nil
23 | ) -> HTTPURLResponse {
24 | return HTTPURLResponse(
25 | url: URLTestDouble(),
26 | statusCode: statusCode,
27 | httpVersion: "HTTP/1.1",
28 | headerFields: headerFields
29 | )!
30 | }
31 |
32 | func NSErrorTestDouble() -> NSError {
33 | return NSError(
34 | domain: "",
35 | code: 0
36 | )
37 | }
38 |
39 | func URLRequestTestDouble() -> URLRequest {
40 | return URLRequest(url: URLTestDouble())
41 | }
42 |
43 | func URLResponseTestDouble() -> URLResponse {
44 | return URLResponse(
45 | url: URLTestDouble(),
46 | mimeType: nil,
47 | expectedContentLength: 0,
48 | textEncodingName: nil
49 | )
50 | }
51 |
52 | func URLTestDouble() -> URL {
53 | return URL(string: "http://localhost/")!
54 | }
55 |
--------------------------------------------------------------------------------
/Albums/NetworkImageSource.h:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageSource.h
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | #import
15 | #import
16 |
17 | @interface NetworkImageSource : NSObject
18 |
19 | @end
20 |
21 | @interface NetworkImageSource (CreateImageSource)
22 |
23 | + (CGImageSourceRef _Nullable (*_Nonnull)(CFDataRef _Nonnull, CFDictionaryRef _Nullable))createImageSource;
24 |
25 | + (CGImageSourceRef _Nullable)createImageSourceWithData:(CFDataRef _Nonnull)data
26 | options:(CFDictionaryRef _Nullable)options CF_RETURNS_RETAINED;
27 |
28 | @end
29 |
30 | @interface NetworkImageSource (CreateImage)
31 |
32 | + (CGImageRef _Nullable (*_Nonnull)(CGImageSourceRef _Nonnull, size_t, CFDictionaryRef _Nullable))createImage;
33 |
34 | + (CGImageRef _Nullable)createImageWithImageSource:(CGImageSourceRef _Nonnull)imageSource
35 | atIndex:(size_t)index
36 | options:(CFDictionaryRef _Nullable)options CF_RETURNS_RETAINED;
37 |
38 | @end
39 |
--------------------------------------------------------------------------------
/Albums/NetworkImageSource.m:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageSource.m
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | #import "NetworkImageSource.h"
15 |
16 | @implementation NetworkImageSource
17 |
18 | @end
19 |
20 | @implementation NetworkImageSource (CreateImageSource)
21 |
22 | + (CGImageSourceRef _Nullable (*_Nonnull)(CFDataRef _Nonnull, CFDictionaryRef _Nullable))createImageSource {
23 | return CGImageSourceCreateWithData;
24 | }
25 |
26 | + (CGImageSourceRef)createImageSourceWithData:(CFDataRef)data
27 | options:(CFDictionaryRef)options {
28 | return [self createImageSource](data, options);
29 | }
30 |
31 | @end
32 |
33 | @implementation NetworkImageSource (CreateImage)
34 |
35 | + (CGImageRef _Nullable (*_Nonnull)(CGImageSourceRef _Nonnull, size_t, CFDictionaryRef _Nullable))createImage {
36 | return CGImageSourceCreateImageAtIndex;
37 | }
38 |
39 | + (CGImageRef)createImageWithImageSource:(CGImageSourceRef)imageSource
40 | atIndex:(size_t)index
41 | options:(CFDictionaryRef)options {
42 | return [self createImage](imageSource, index, options);
43 | }
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TDD-Albums-II
2 |
3 | Welcome! The TDD-Albums-II tutorial is a sequel to the original TDD-Albums library from 2015. The TDD-Albums library started as a hands-on tutorial for a few iPhone developers at eBay that were interested in learning Test-Driven Development. Our previous tutorial was written using Objective-C and UIKit. The TDD-Albums-II tutorial has been rewritten (from scratch) using the latest versions of Swift (5.5) and SwiftUI.
4 |
5 | Like our previous tutorial, we will cover topics familiar to iOS engineers, like networking and concurrency, while working (almost) totally from TDD. Our tutorial also introduces topics familiar to TDD engineers, like dependency-injection and test-double types, while working from Swift, SwiftUI, and a little Objective-C.
6 |
7 | TDD-Albums-II is appropriate for iOS engineers that have never attempted TDD (or never written a unit-test). Having said that, TDD-Albums-II is not intended as a tutorial to learn iOS engineering. It will be assumed that you have a strong competency in Swift and SwiftUI. You should also have a working knowledge of Objective-C.
8 |
9 | Open the [Wiki](https://github.com/vanvoorden/TDD-Albums-II/wiki) for step-by-step instructions.
10 |
11 | ## Requirements
12 |
13 | This project requires Xcode 13.1 or later.
14 |
15 | ## License
16 | TDD-Albums-II
17 |
18 | Copyright © 2021 North Bronson Software
19 |
20 | This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
21 |
22 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
23 |
24 | 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.
25 |
--------------------------------------------------------------------------------
/Albums/AlbumsListRowModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsListRowModel.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol AlbumsListRowModelImageOperation {
17 | associatedtype Image
18 |
19 | static func image(for: URLRequest) async throws -> Image
20 | }
21 |
22 | extension NetworkImageOperation : AlbumsListRowModelImageOperation where Session == NetworkSession, ImageHandler == NetworkImageHandler> {
23 |
24 | }
25 |
26 | @MainActor final class AlbumsListRowModel : ObservableObject {
27 | @Published private(set) var image: ImageOperation.Image?
28 |
29 | private let album: Album
30 |
31 | init(album: Album) {
32 | self.album = album
33 | }
34 | }
35 |
36 | extension AlbumsListRowModel {
37 | var artist: String {
38 | return self.album.artist
39 | }
40 | }
41 |
42 | extension AlbumsListRowModel {
43 | var name: String {
44 | return self.album.name
45 | }
46 | }
47 |
48 | extension AlbumsListRowModel {
49 | func requestImage() async throws {
50 | if let url = URL(string: self.album.image) {
51 | let request = URLRequest(url: url)
52 | let image = try await ImageOperation.image(for: request)
53 | self.image = image
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Albums/NetworkImageSerialization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageSerialization.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol NetworkImageSerializationImageSource {
17 | associatedtype ImageSource
18 | associatedtype Image
19 |
20 | static func createImageSource(
21 | with: CFData,
22 | options: CFDictionary?
23 | ) -> ImageSource?
24 |
25 | static func createImage(
26 | with: ImageSource,
27 | at: Int,
28 | options: CFDictionary?
29 | ) -> Image?
30 | }
31 |
32 | extension NetworkImageSource : NetworkImageSerializationImageSource {
33 |
34 | }
35 |
36 | struct NetworkImageSerialization {
37 | static func image(with data: Data) throws -> ImageSource.Image {
38 | guard
39 | let imageSource = ImageSource.createImageSource(
40 | with: data as CFData,
41 | options: nil
42 | )
43 | else {
44 | throw Self.Error(.imageSourceError)
45 | }
46 | guard
47 | let image = ImageSource.createImage(
48 | with: imageSource,
49 | at: 0,
50 | options: nil
51 | )
52 | else {
53 | throw Self.Error(.imageError)
54 | }
55 | return image
56 | }
57 | }
58 |
59 | extension NetworkImageSerialization {
60 | struct Error : Swift.Error {
61 | enum Code {
62 | case imageSourceError
63 | case imageError
64 | }
65 |
66 | let code: Self.Code
67 | let underlying: Swift.Error?
68 |
69 | init(
70 | _ code: Self.Code,
71 | underlying: Swift.Error? = nil
72 | ) {
73 | self.code = code
74 | self.underlying = underlying
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Albums/NetworkJSONOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkJSONOperation.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol NetworkJSONOperationSession {
17 | static func data(for: URLRequest) async throws -> (
18 | Data,
19 | URLResponse
20 | )
21 | }
22 |
23 | extension NetworkSession : NetworkJSONOperationSession where URLSession == Foundation.URLSession {
24 |
25 | }
26 |
27 | protocol NetworkJSONOperationJSONHandler {
28 | associatedtype JSON
29 |
30 | static func json(
31 | with: Data,
32 | response: URLResponse
33 | ) throws -> JSON
34 | }
35 |
36 | extension NetworkJSONHandler : NetworkJSONOperationJSONHandler where DataHandler == NetworkDataHandler, JSONSerialization == Foundation.JSONSerialization {
37 |
38 | }
39 |
40 | struct NetworkJSONOperation<
41 | Session : NetworkJSONOperationSession,
42 | JSONHandler : NetworkJSONOperationJSONHandler
43 | > {
44 |
45 | }
46 |
47 | extension NetworkJSONOperation {
48 | static func json(for request: URLRequest) async throws -> JSONHandler.JSON {
49 | let (
50 | data,
51 | response
52 | ) = try await { () -> (
53 | Data,
54 | URLResponse
55 | ) in
56 | do {
57 | return try await Session.data(for: request)
58 | } catch {
59 | throw Self.Error(
60 | .sessionError,
61 | underlying: error
62 | )
63 | }
64 | }()
65 |
66 | do {
67 | return try JSONHandler.json(
68 | with: data,
69 | response: response
70 | )
71 | } catch {
72 | throw Self.Error(
73 | .jsonHandlerError,
74 | underlying: error
75 | )
76 | }
77 | }
78 | }
79 |
80 | extension NetworkJSONOperation {
81 | struct Error : Swift.Error {
82 | enum Code {
83 | case sessionError
84 | case jsonHandlerError
85 | }
86 |
87 | let code: Self.Code
88 | let underlying: Swift.Error?
89 |
90 | init(
91 | _ code: Self.Code,
92 | underlying: Swift.Error? = nil
93 | ) {
94 | self.code = code
95 | self.underlying = underlying
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Albums/NetworkImageOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageOperation.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol NetworkImageOperationSession {
17 | static func data(for: URLRequest) async throws -> (
18 | Data,
19 | URLResponse
20 | )
21 | }
22 |
23 | extension NetworkSession : NetworkImageOperationSession where URLSession == Foundation.URLSession {
24 |
25 | }
26 |
27 | protocol NetworkImageOperationImageHandler {
28 | associatedtype Image
29 |
30 | static func image(
31 | with: Data,
32 | response: URLResponse
33 | ) throws -> Image
34 | }
35 |
36 | extension NetworkImageHandler : NetworkImageOperationImageHandler where DataHandler == NetworkDataHandler, ImageSerialization == NetworkImageSerialization {
37 |
38 | }
39 |
40 | struct NetworkImageOperation<
41 | Session : NetworkImageOperationSession,
42 | ImageHandler : NetworkImageOperationImageHandler
43 | > {
44 |
45 | }
46 |
47 | extension NetworkImageOperation {
48 | static func image(for request: URLRequest) async throws -> ImageHandler.Image {
49 | let (
50 | data,
51 | response
52 | ) = try await { () -> (
53 | Data,
54 | URLResponse
55 | ) in
56 | do {
57 | return try await Session.data(for: request)
58 | } catch {
59 | throw Self.Error(
60 | .sessionError,
61 | underlying: error
62 | )
63 | }
64 | }()
65 |
66 | do {
67 | return try ImageHandler.image(
68 | with: data,
69 | response: response
70 | )
71 | } catch {
72 | throw Self.Error(
73 | .imageHandlerError,
74 | underlying: error
75 | )
76 | }
77 | }
78 | }
79 |
80 | extension NetworkImageOperation {
81 | struct Error : Swift.Error {
82 | enum Code {
83 | case sessionError
84 | case imageHandlerError
85 | }
86 |
87 | let code: Self.Code
88 | let underlying: Swift.Error?
89 |
90 | init(
91 | _ code: Self.Code,
92 | underlying: Swift.Error? = nil
93 | ) {
94 | self.code = code
95 | self.underlying = underlying
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Albums/AlbumsListModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsListModel.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol AlbumsListModelJSONOperation {
17 | associatedtype JSON
18 |
19 | static func json(for: URLRequest) async throws -> JSON
20 | }
21 |
22 | extension NetworkJSONOperation : AlbumsListModelJSONOperation where Session == NetworkSession, JSONHandler == NetworkJSONHandler {
23 |
24 | }
25 |
26 | struct Album {
27 | let id: String
28 | let artist: String
29 | let name: String
30 | let image: String
31 | }
32 |
33 | extension Album : Hashable {
34 |
35 | }
36 |
37 | extension Album : Identifiable {
38 |
39 | }
40 |
41 | @MainActor final class AlbumsListModel : ObservableObject {
42 | @Published private(set) var albums = Array()
43 | }
44 |
45 | private func Albums(_ json: Any) -> Array {
46 | var albums = Array()
47 | if let array = ((json as? Dictionary)?["feed"] as? Dictionary)?["entry"] as? Array> {
48 | for dictionary in array {
49 | if let artist = ((dictionary["im:artist"] as? Dictionary)?["label"] as? String),
50 | let name = ((dictionary["im:name"] as? Dictionary)?["label"] as? String),
51 | let image = ((dictionary["im:image"] as? Array>)?[2]["label"] as? String),
52 | let id = (((dictionary["id"] as? Dictionary)?["attributes"] as? Dictionary)?["im:id"] as? String) {
53 | let album = Album(
54 | id: id,
55 | artist: artist,
56 | name: name,
57 | image: image
58 | )
59 | albums.append(album)
60 | }
61 | }
62 | }
63 | return albums
64 | }
65 |
66 | extension AlbumsListModel {
67 | func requestAlbums() async throws {
68 | if let url = URL(string: "https://itunes.apple.com/us/rss/topalbums/limit=100/json") {
69 | let request = URLRequest(url: url)
70 | let json = try await JSONOperation.json(for: request)
71 | self.albums = Albums(json)
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Albums/NetworkImageHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageHandler.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol NetworkImageHandlerDataHandler {
17 | static func data(
18 | with: Data,
19 | response: URLResponse
20 | ) throws -> Data
21 | }
22 |
23 | extension NetworkDataHandler : NetworkImageHandlerDataHandler {
24 |
25 | }
26 |
27 | protocol NetworkImageHandlerImageSerialization {
28 | associatedtype Image
29 |
30 | static func image(with: Data) throws -> Image
31 | }
32 |
33 | extension NetworkImageSerialization : NetworkImageHandlerImageSerialization where ImageSource == NetworkImageSource {
34 |
35 | }
36 |
37 | struct NetworkImageHandler<
38 | DataHandler : NetworkImageHandlerDataHandler,
39 | ImageSerialization : NetworkImageHandlerImageSerialization
40 | > {
41 |
42 | }
43 |
44 | extension NetworkImageHandler {
45 | static func image(
46 | with data: Data,
47 | response: URLResponse
48 | ) throws -> ImageSerialization.Image {
49 | guard
50 | let mimeType = response.mimeType?.lowercased(),
51 | mimeType == "image/png"
52 | else {
53 | throw Self.Error(.mimeTypeError)
54 | }
55 |
56 | let data = try { () -> Data in
57 | do {
58 | return try DataHandler.data(
59 | with: data,
60 | response: response
61 | )
62 | } catch {
63 | throw Self.Error(
64 | .dataHandlerError,
65 | underlying: error
66 | )
67 | }
68 | }()
69 |
70 | do {
71 | return try ImageSerialization.image(with: data)
72 | } catch {
73 | throw Self.Error(
74 | .imageSerializationError,
75 | underlying: error
76 | )
77 | }
78 | }
79 | }
80 |
81 | extension NetworkImageHandler {
82 | struct Error : Swift.Error {
83 | enum Code {
84 | case mimeTypeError
85 | case dataHandlerError
86 | case imageSerializationError
87 | }
88 |
89 | let code: Self.Code
90 | let underlying: Swift.Error?
91 |
92 | init(
93 | _ code: Self.Code,
94 | underlying: Swift.Error? = nil
95 | ) {
96 | self.code = code
97 | self.underlying = underlying
98 | }
99 | }
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/Albums/NetworkJSONHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkJSONHandler.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import Foundation
15 |
16 | protocol NetworkJSONHandlerDataHandler {
17 | static func data(
18 | with: Data,
19 | response: URLResponse
20 | ) throws -> Data
21 | }
22 |
23 | extension NetworkDataHandler : NetworkJSONHandlerDataHandler {
24 |
25 | }
26 |
27 | protocol NetworkJSONHandlerJSONSerialization {
28 | associatedtype JSON
29 |
30 | static func jsonObject(
31 | with: Data,
32 | options: JSONSerialization.ReadingOptions
33 | ) throws -> JSON
34 | }
35 |
36 | extension JSONSerialization : NetworkJSONHandlerJSONSerialization {
37 |
38 | }
39 |
40 | struct NetworkJSONHandler<
41 | DataHandler : NetworkJSONHandlerDataHandler,
42 | JSONSerialization : NetworkJSONHandlerJSONSerialization
43 | > {
44 |
45 | }
46 |
47 | extension NetworkJSONHandler {
48 | static func json(
49 | with data: Data,
50 | response: URLResponse
51 | ) throws -> JSONSerialization.JSON {
52 | guard
53 | let mimeType = response.mimeType?.lowercased(),
54 | mimeType == "text/javascript"
55 | else {
56 | throw Self.Error(.mimeTypeError)
57 | }
58 |
59 | let data = try { () -> Data in
60 | do {
61 | return try DataHandler.data(
62 | with: data,
63 | response: response
64 | )
65 | } catch {
66 | throw Self.Error(
67 | .dataHandlerError,
68 | underlying: error
69 | )
70 | }
71 | }()
72 |
73 | do {
74 | return try JSONSerialization.jsonObject(
75 | with: data,
76 | options: []
77 | )
78 | } catch {
79 | throw Self.Error(
80 | .jsonSerializationError,
81 | underlying: error
82 | )
83 | }
84 | }
85 | }
86 |
87 | extension NetworkJSONHandler {
88 | struct Error : Swift.Error {
89 | enum Code {
90 | case mimeTypeError
91 | case dataHandlerError
92 | case jsonSerializationError
93 | }
94 |
95 | let code: Self.Code
96 | let underlying: Swift.Error?
97 |
98 | init(
99 | _ code: Self.Code,
100 | underlying: Swift.Error? = nil
101 | ) {
102 | self.code = code
103 | self.underlying = underlying
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkDataHandlerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkDataHandlerTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkDataHandlerTestCase : XCTestCase {
17 |
18 | }
19 |
20 | extension NetworkDataHandlerTestCase {
21 | func testError() {
22 | XCTAssertThrowsError(
23 | try NetworkDataHandler.data(
24 | with: DataTestDouble(),
25 | response: URLResponseTestDouble()
26 | )
27 | ) { error in
28 | if let error = try? XCTUnwrap(error as? NetworkDataHandler.Error) {
29 | XCTAssertEqual(
30 | error.code,
31 | .statusCodeError
32 | )
33 | XCTAssertNil(error.underlying)
34 | }
35 | }
36 | }
37 | }
38 |
39 | extension NetworkDataHandlerTestCase {
40 | private static var errorCodes: Array {
41 | return Array(100...199) + Array(300...599)
42 | }
43 | }
44 |
45 | extension NetworkDataHandlerTestCase {
46 | func testErrorWithStatusCode() {
47 | for statusCode in Self.errorCodes {
48 | XCTAssertThrowsError(
49 | try NetworkDataHandler.data(
50 | with: DataTestDouble(),
51 | response: HTTPURLResponseTestDouble(statusCode: statusCode)
52 | ),
53 | "Status Code \(statusCode)"
54 | ) { error in
55 | if let error = try? XCTUnwrap(
56 | error as? NetworkDataHandler.Error,
57 | "Status Code \(statusCode)"
58 | ) {
59 | XCTAssertEqual(
60 | error.code,
61 | .statusCodeError,
62 | "Status Code \(statusCode)"
63 | )
64 | XCTAssertNil(
65 | error.underlying,
66 | "Status Code \(statusCode)"
67 | )
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
74 | extension NetworkDataHandlerTestCase {
75 | private static var successCodes: Array {
76 | return Array(200...299)
77 | }
78 | }
79 |
80 | extension NetworkDataHandlerTestCase {
81 | func testSuccess() {
82 | for statusCode in Self.successCodes {
83 | XCTAssertNoThrow(
84 | try {
85 | let data = try NetworkDataHandler.data(
86 | with: DataTestDouble(),
87 | response: HTTPURLResponseTestDouble(statusCode: statusCode)
88 | )
89 | XCTAssertEqual(
90 | data,
91 | DataTestDouble(),
92 | "Status Code \(statusCode)"
93 | )
94 | }(),
95 | "Status Code \(statusCode)"
96 | )
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkSessionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkSessionTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkSessionTestCase : XCTestCase {
17 | private typealias NetworkSessionTestDouble = NetworkSession
18 | }
19 |
20 | extension NetworkSessionTestCase {
21 | final private class URLSessionTestDouble : NetworkSessionURLSession {
22 | static let shared = URLSessionTestDouble()
23 |
24 | var parameterRequest: URLRequest?
25 | var parameterDelegate: URLSessionTaskDelegate?
26 | var returnData: Data?
27 | var returnResponse: URLResponse?
28 | let returnError = NSErrorTestDouble()
29 |
30 | func data(
31 | for request: URLRequest,
32 | delegate: URLSessionTaskDelegate?
33 | ) async throws -> (
34 | Data,
35 | URLResponse
36 | ) {
37 | self.parameterRequest = request
38 | self.parameterDelegate = delegate
39 | guard
40 | let returnData = self.returnData,
41 | let returnResponse = self.returnResponse
42 | else {
43 | throw self.returnError
44 | }
45 | return (
46 | returnData,
47 | returnResponse
48 | )
49 | }
50 | }
51 | }
52 |
53 | extension NetworkSessionTestCase {
54 | override func tearDown() {
55 | URLSessionTestDouble.shared.parameterRequest = nil
56 | URLSessionTestDouble.shared.parameterDelegate = nil
57 | URLSessionTestDouble.shared.returnData = nil
58 | URLSessionTestDouble.shared.returnResponse = nil
59 | }
60 | }
61 |
62 | extension NetworkSessionTestCase {
63 | func testError() async {
64 | URLSessionTestDouble.shared.returnData = nil
65 | URLSessionTestDouble.shared.returnResponse = nil
66 |
67 | do {
68 | _ = try await NetworkSessionTestDouble.data(for: URLRequestTestDouble())
69 | XCTFail()
70 | } catch {
71 | XCTAssertEqual(
72 | URLSessionTestDouble.shared.parameterRequest,
73 | URLRequestTestDouble()
74 | )
75 | XCTAssertNil(URLSessionTestDouble.shared.parameterDelegate)
76 |
77 | if let error = try? XCTUnwrap(error as NSError?) {
78 | XCTAssertIdentical(
79 | error,
80 | URLSessionTestDouble.shared.returnError
81 | )
82 | }
83 | }
84 | }
85 | }
86 |
87 | extension NetworkSessionTestCase {
88 | func testSuccess() async {
89 | URLSessionTestDouble.shared.returnData = DataTestDouble()
90 | URLSessionTestDouble.shared.returnResponse = URLResponseTestDouble()
91 |
92 | do {
93 | let (
94 | data,
95 | response
96 | ) = try await NetworkSessionTestDouble.data(for: URLRequestTestDouble())
97 |
98 | XCTAssertEqual(
99 | URLSessionTestDouble.shared.parameterRequest,
100 | URLRequestTestDouble()
101 | )
102 | XCTAssertNil(URLSessionTestDouble.shared.parameterDelegate)
103 |
104 | XCTAssertEqual(
105 | data,
106 | URLSessionTestDouble.shared.returnData
107 | )
108 | XCTAssertIdentical(
109 | response,
110 | URLSessionTestDouble.shared.returnResponse
111 | )
112 | } catch {
113 | XCTFail()
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Albums/AlbumsListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsListView.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import SwiftUI
15 |
16 | @MainActor protocol AlbumsListViewModel : ObservableObject {
17 | var albums: Array { get }
18 |
19 | func requestAlbums() async throws
20 | }
21 |
22 | extension AlbumsListModel : AlbumsListViewModel where JSONOperation == NetworkJSONOperation, NetworkJSONHandler> {
23 |
24 | }
25 |
26 | struct AlbumsListView : View {
27 | @ObservedObject private var model: ListViewModel
28 |
29 | init(model: ListViewModel) {
30 | self.model = model
31 | }
32 | }
33 |
34 | extension AlbumsListView {
35 | var body: some View {
36 | List(
37 | self.model.albums
38 | ) { album in
39 | AlbumsListRowView(
40 | model: ListRowViewModel(album: album)
41 | )
42 | }.listStyle(
43 | .plain
44 | ).task {
45 | do {
46 | try await self.model.requestAlbums()
47 | } catch {
48 | print(error)
49 | }
50 | }
51 | }
52 | }
53 |
54 | struct AlbumsListView_Previews: PreviewProvider {
55 |
56 | }
57 |
58 | extension AlbumsListView_Previews {
59 | private final class ListModel : AlbumsListViewModel {
60 | @Published private(set) var albums = Array()
61 |
62 | func requestAlbums() async throws {
63 | self.albums = [
64 | Album(
65 | id: "Rubber Soul",
66 | artist: "Beatles",
67 | name: "Rubber Soul",
68 | image: "http://localhost/rubber-soul.jpeg"
69 | ),
70 | Album(
71 | id: "Pet Sounds",
72 | artist: "Beach Boys",
73 | name: "Pet Sounds",
74 | image: "http://localhost/pet-sounds.jpeg"
75 | ),
76 | ]
77 | }
78 | }
79 | }
80 |
81 | extension AlbumsListView_Previews {
82 | private final class ListRowModel : AlbumsListRowViewModel {
83 | let artist: String
84 | let name: String
85 |
86 | @Published private(set) var image: CGImage?
87 |
88 | init(album: Album) {
89 | self.artist = album.artist
90 | self.name = album.name
91 | }
92 |
93 | func requestImage() async throws {
94 | if let context = CGContext(
95 | data: nil,
96 | width: 256,
97 | height: 256,
98 | bitsPerComponent: 8,
99 | bytesPerRow: 0,
100 | space: CGColorSpaceCreateDeviceRGB(),
101 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
102 | ) {
103 | context.setFillColor(
104 | red: 0.5,
105 | green: 0.5,
106 | blue: 0.5,
107 | alpha: 1
108 | )
109 | context.fill(
110 | CGRect(
111 | x: 0,
112 | y: 0,
113 | width: 256,
114 | height: 256
115 | )
116 | )
117 | self.image = context.makeImage()
118 | }
119 | }
120 | }
121 | }
122 |
123 | extension AlbumsListView_Previews {
124 | static var previews: some View {
125 | AlbumsListView(
126 | model: ListModel()
127 | )
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/AlbumsTests/AlbumsListRowModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsListRowModelTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class AlbumsListRowModelTestCase : XCTestCase {
17 | private typealias AlbumsListRowModelTestDouble = AlbumsListRowModel
18 | }
19 |
20 | extension AlbumsListRowModelTestCase {
21 | private struct ImageOperationTestDouble : AlbumsListRowModelImageOperation {
22 | static var parameterRequest: URLRequest?
23 | static var returnImage: NSObject?
24 | static let returnError = NSErrorTestDouble()
25 |
26 | static func image(for request: URLRequest) async throws -> NSObject {
27 | self.parameterRequest = request
28 | guard
29 | let returnImage = self.returnImage
30 | else {
31 | throw self.returnError
32 | }
33 | return returnImage
34 | }
35 | }
36 | }
37 |
38 | extension AlbumsListRowModelTestCase {
39 | override func tearDown() {
40 | ImageOperationTestDouble.parameterRequest = nil
41 | ImageOperationTestDouble.returnImage = nil
42 | }
43 | }
44 |
45 | extension AlbumsListRowModelTestCase {
46 | private static var album: Album {
47 | return Album(
48 | id: "id",
49 | artist: "artist",
50 | name: "name",
51 | image: "image"
52 | )
53 | }
54 | }
55 |
56 | extension AlbumsListRowModelTestCase {
57 | @MainActor func testError() async {
58 | ImageOperationTestDouble.returnImage = nil
59 |
60 | let model = AlbumsListRowModelTestDouble(album: Self.album)
61 |
62 | XCTAssertEqual(
63 | model.artist,
64 | Self.album.artist
65 | )
66 | XCTAssertEqual(
67 | model.name,
68 | Self.album.name
69 | )
70 |
71 | do {
72 | try await model.requestImage()
73 | XCTFail()
74 | } catch {
75 | XCTAssertEqual(
76 | ImageOperationTestDouble.parameterRequest,
77 | URLRequest(url: URL(string: Self.album.image)!)
78 | )
79 |
80 | XCTAssertNil(model.image)
81 |
82 | if let error = try? XCTUnwrap(error as NSError?) {
83 | XCTAssertIdentical(
84 | error,
85 | ImageOperationTestDouble.returnError
86 | )
87 | }
88 | }
89 | }
90 | }
91 |
92 | extension AlbumsListRowModelTestCase {
93 | @MainActor func testSuccess() async {
94 | ImageOperationTestDouble.returnImage = NSObject()
95 |
96 | let model = AlbumsListRowModelTestDouble(album: Self.album)
97 |
98 | var modelDidChange = false
99 | let modelWillChange = model.objectWillChange.sink() { _ in
100 | modelDidChange = true
101 | }
102 |
103 | var imageDidChange = false
104 | let imageWillChange = model.$image.sink() { _ in
105 | if modelDidChange {
106 | imageDidChange = true
107 | }
108 | }
109 |
110 | XCTAssertEqual(
111 | model.artist,
112 | Self.album.artist
113 | )
114 | XCTAssertEqual(
115 | model.name,
116 | Self.album.name
117 | )
118 |
119 | do {
120 | try await model.requestImage()
121 |
122 | XCTAssertTrue(imageDidChange)
123 |
124 | XCTAssertEqual(
125 | ImageOperationTestDouble.parameterRequest,
126 | URLRequest(url: URL(string: Self.album.image)!)
127 | )
128 |
129 | XCTAssertIdentical(
130 | model.image,
131 | ImageOperationTestDouble.returnImage
132 | )
133 | } catch {
134 | XCTFail()
135 | }
136 |
137 | modelWillChange.cancel()
138 | imageWillChange.cancel()
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Albums/AlbumsListRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsListRowView.swift
3 | // Albums
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import SwiftUI
15 |
16 | @MainActor protocol AlbumsListRowViewModel : ObservableObject {
17 | var artist: String { get }
18 | var name: String { get }
19 | var image: CGImage? { get }
20 |
21 | init(album: Album)
22 |
23 | func requestImage() async throws
24 | }
25 |
26 | extension AlbumsListRowModel : AlbumsListRowViewModel where ImageOperation == NetworkImageOperation, NetworkImageHandler>> {
27 |
28 | }
29 |
30 | struct AlbumsListRowView : View {
31 | @ObservedObject private var model: ListRowViewModel
32 |
33 | init(model: ListRowViewModel) {
34 | self.model = model
35 | }
36 | }
37 |
38 | extension AlbumsListRowView {
39 | var body: some View {
40 | HStack {
41 | if let cgImage = self.model.image {
42 | Image(
43 | decorative: cgImage,
44 | scale: 1.0,
45 | orientation: .up
46 | ).resizable(
47 | ).aspectRatio(
48 | contentMode: .fit
49 | ).frame(
50 | width: 128,
51 | height: 128,
52 | alignment: .topLeading
53 | )
54 | }
55 | VStack(
56 | alignment: .leading,
57 | spacing: 3
58 | ) {
59 | Text(
60 | self.model.artist
61 | ).foregroundColor(
62 | .primary
63 | ).font(
64 | .headline
65 | )
66 | Text(
67 | self.model.name
68 | ).foregroundColor(
69 | .secondary
70 | ).font(
71 | .subheadline
72 | )
73 | }
74 | }.task {
75 | do {
76 | try await self.model.requestImage()
77 | } catch {
78 | print(error)
79 | }
80 | }
81 | }
82 | }
83 |
84 | struct AlbumsListRowView_Previews: PreviewProvider {
85 |
86 | }
87 |
88 | extension AlbumsListRowView_Previews {
89 | private final class ListRowModel : AlbumsListRowViewModel {
90 | let artist: String
91 | let name: String
92 |
93 | @Published private(set) var image: CGImage?
94 |
95 | init(album: Album) {
96 | self.artist = album.artist
97 | self.name = album.name
98 | }
99 |
100 | func requestImage() async throws {
101 | if let context = CGContext(
102 | data: nil,
103 | width: 256,
104 | height: 256,
105 | bitsPerComponent: 8,
106 | bytesPerRow: 0,
107 | space: CGColorSpaceCreateDeviceRGB(),
108 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
109 | ) {
110 | context.setFillColor(
111 | red: 0.5,
112 | green: 0.5,
113 | blue: 0.5,
114 | alpha: 1
115 | )
116 | context.fill(
117 | CGRect(
118 | x: 0,
119 | y: 0,
120 | width: 256,
121 | height: 256
122 | )
123 | )
124 | self.image = context.makeImage()
125 | }
126 | }
127 | }
128 | }
129 |
130 | extension AlbumsListRowView_Previews {
131 | static var albums: Array {
132 | return [
133 | Album(
134 | id: "Rubber Soul",
135 | artist: "Beatles",
136 | name: "Rubber Soul",
137 | image: "http://localhost/rubber-soul.jpeg"
138 | ),
139 | Album(
140 | id: "Pet Sounds",
141 | artist: "Beach Boys",
142 | name: "Pet Sounds",
143 | image: "http://localhost/pet-sounds.jpeg"
144 | ),
145 | ]
146 | }
147 | }
148 |
149 | extension AlbumsListRowView_Previews {
150 | static var previews: some View {
151 | List(
152 | self.albums
153 | ) { album in
154 | AlbumsListRowView(
155 | model: ListRowModel(album: album)
156 | )
157 | }.listStyle(
158 | .plain
159 | )
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/AlbumsTests/AlbumsListModelTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AlbumsListModelTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class AlbumsListModelTestCase : XCTestCase {
17 | private typealias AlbumsListModelTestDouble = AlbumsListModel
18 | }
19 |
20 | extension AlbumsListModelTestCase {
21 | private struct JSONOperationTestDouble : AlbumsListModelJSONOperation {
22 | static var parameterRequest: URLRequest?
23 | static var returnJSON: Any?
24 | static let returnError = NSErrorTestDouble()
25 |
26 | static func json(for request: URLRequest) async throws -> Any {
27 | self.parameterRequest = request
28 | guard
29 | let returnJSON = self.returnJSON
30 | else {
31 | throw self.returnError
32 | }
33 | return returnJSON
34 | }
35 | }
36 | }
37 |
38 | extension AlbumsListModelTestCase {
39 | override func tearDown() {
40 | JSONOperationTestDouble.parameterRequest = nil
41 | JSONOperationTestDouble.returnJSON = nil
42 | }
43 | }
44 |
45 | extension AlbumsListModelTestCase {
46 | private static var request: URLRequest {
47 | return URLRequest(url: URL(string: "https://itunes.apple.com/us/rss/topalbums/limit=100/json")!)
48 | }
49 | }
50 |
51 | extension AlbumsListModelTestCase {
52 | private static var json: Any {
53 | let bundle = Bundle(identifier: "com.northbronson.AlbumsTests")!
54 | let url = bundle.url(
55 | forResource: "Albums",
56 | withExtension: "json"
57 | )!
58 | let data = try! Data(contentsOf: url)
59 | let json = try! JSONSerialization.jsonObject(
60 | with: data,
61 | options: []
62 | )
63 | return json
64 | }
65 | }
66 |
67 | private func Albums(_ json: Any) -> Array {
68 | var albums = Array()
69 | if let array = ((json as? Dictionary)?["feed"] as? Dictionary)?["entry"] as? Array> {
70 | for dictionary in array {
71 | if let artist = ((dictionary["im:artist"] as? Dictionary)?["label"] as? String),
72 | let name = ((dictionary["im:name"] as? Dictionary)?["label"] as? String),
73 | let image = ((dictionary["im:image"] as? Array>)?[2]["label"] as? String),
74 | let id = (((dictionary["id"] as? Dictionary)?["attributes"] as? Dictionary)?["im:id"] as? String) {
75 | let album = Album(
76 | id: id,
77 | artist: artist,
78 | name: name,
79 | image: image
80 | )
81 | albums.append(album)
82 | }
83 | }
84 | }
85 | return albums
86 | }
87 |
88 | extension AlbumsListModelTestCase {
89 | private static var albums: Array {
90 | return Albums(self.json)
91 | }
92 | }
93 |
94 | extension AlbumsListModelTestCase {
95 | @MainActor func testError() async {
96 | JSONOperationTestDouble.returnJSON = nil
97 |
98 | let model = AlbumsListModelTestDouble()
99 | do {
100 | try await model.requestAlbums()
101 | XCTFail()
102 | } catch {
103 | XCTAssertEqual(
104 | JSONOperationTestDouble.parameterRequest,
105 | Self.request
106 | )
107 |
108 | XCTAssertEqual(
109 | model.albums,
110 | []
111 | )
112 |
113 | if let error = try? XCTUnwrap(error as NSError?) {
114 | XCTAssertIdentical(
115 | error,
116 | JSONOperationTestDouble.returnError
117 | )
118 | }
119 | }
120 | }
121 | }
122 |
123 | extension AlbumsListModelTestCase {
124 | @MainActor func testSuccess() async {
125 | JSONOperationTestDouble.returnJSON = Self.json
126 |
127 | let model = AlbumsListModelTestDouble()
128 |
129 | var modelDidChange = false
130 | let modelWillChange = model.objectWillChange.sink() { _ in
131 | modelDidChange = true
132 | }
133 |
134 | var albumsDidChange = false
135 | let albumsWillChange = model.$albums.sink() { _ in
136 | if modelDidChange {
137 | albumsDidChange = true
138 | }
139 | }
140 |
141 | do {
142 | try await model.requestAlbums()
143 |
144 | XCTAssertTrue(albumsDidChange)
145 |
146 | XCTAssertEqual(
147 | JSONOperationTestDouble.parameterRequest,
148 | Self.request
149 | )
150 |
151 | XCTAssertEqual(
152 | model.albums,
153 | Self.albums
154 | )
155 | } catch {
156 | XCTFail()
157 | }
158 |
159 | modelWillChange.cancel()
160 | albumsWillChange.cancel()
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkImageSerializationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageSerializationTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkImageSerializationTestCase : XCTestCase {
17 | private typealias NetworkImageSerializationTestDouble = NetworkImageSerialization
18 | }
19 |
20 | extension NetworkImageSerializationTestCase {
21 | private struct ImageSourceTestDouble : NetworkImageSerializationImageSource {
22 | static var imageSourceParameterData: CFData?
23 | static var imageSourceParameterOptions: CFDictionary?
24 | static var imageSourceReturnImageSource: NSObject?
25 |
26 | static func createImageSource(
27 | with data: CFData,
28 | options: CFDictionary?
29 | ) -> NSObject? {
30 | self.imageSourceParameterData = data
31 | self.imageSourceParameterOptions = options
32 | return self.imageSourceReturnImageSource
33 | }
34 |
35 | static var imageParameterImageSource: NSObject?
36 | static var imageParameterIndex: Int?
37 | static var imageParameterOptions: CFDictionary?
38 | static var imageReturnImage: NSObject?
39 |
40 | static func createImage(
41 | with imageSource: NSObject,
42 | at index: Int,
43 | options: CFDictionary?
44 | ) -> NSObject? {
45 | self.imageParameterImageSource = imageSource
46 | self.imageParameterIndex = index
47 | self.imageParameterOptions = options
48 | return self.imageReturnImage
49 | }
50 | }
51 | }
52 |
53 | extension NetworkImageSerializationTestCase {
54 | override func tearDown() {
55 | ImageSourceTestDouble.imageSourceParameterData = nil
56 | ImageSourceTestDouble.imageSourceParameterOptions = nil
57 | ImageSourceTestDouble.imageSourceReturnImageSource = nil
58 |
59 | ImageSourceTestDouble.imageParameterImageSource = nil
60 | ImageSourceTestDouble.imageParameterIndex = nil
61 | ImageSourceTestDouble.imageParameterOptions = nil
62 | ImageSourceTestDouble.imageReturnImage = nil
63 | }
64 | }
65 |
66 | extension NetworkImageSerializationTestCase {
67 | func testImageSourceError() {
68 | ImageSourceTestDouble.imageSourceReturnImageSource = nil
69 |
70 | ImageSourceTestDouble.imageReturnImage = nil
71 |
72 | XCTAssertThrowsError(
73 | try NetworkImageSerializationTestDouble.image(with: DataTestDouble())
74 | ) { error in
75 | XCTAssertEqual(
76 | ImageSourceTestDouble.imageSourceParameterData as Data?,
77 | DataTestDouble()
78 | )
79 | XCTAssertNil(ImageSourceTestDouble.imageSourceParameterOptions)
80 |
81 | XCTAssertNil(ImageSourceTestDouble.imageParameterImageSource)
82 | XCTAssertNil(ImageSourceTestDouble.imageParameterIndex)
83 | XCTAssertNil(ImageSourceTestDouble.imageParameterOptions)
84 |
85 | if let error = try? XCTUnwrap(error as? NetworkImageSerializationTestDouble.Error) {
86 | XCTAssertEqual(
87 | error.code,
88 | .imageSourceError
89 | )
90 | XCTAssertNil(error.underlying)
91 | }
92 | }
93 | }
94 | }
95 |
96 | extension NetworkImageSerializationTestCase {
97 | func testImageError() {
98 | ImageSourceTestDouble.imageSourceReturnImageSource = NSObject()
99 |
100 | ImageSourceTestDouble.imageReturnImage = nil
101 |
102 | XCTAssertThrowsError(
103 | try NetworkImageSerializationTestDouble.image(with: DataTestDouble())
104 | ) { error in
105 | XCTAssertEqual(
106 | ImageSourceTestDouble.imageSourceParameterData as Data?,
107 | DataTestDouble()
108 | )
109 | XCTAssertNil(ImageSourceTestDouble.imageSourceParameterOptions)
110 |
111 | XCTAssertIdentical(
112 | ImageSourceTestDouble.imageParameterImageSource,
113 | ImageSourceTestDouble.imageSourceReturnImageSource
114 | )
115 | XCTAssertEqual(
116 | ImageSourceTestDouble.imageParameterIndex,
117 | 0
118 | )
119 | XCTAssertNil(ImageSourceTestDouble.imageSourceParameterOptions)
120 |
121 | if let error = try? XCTUnwrap(error as? NetworkImageSerializationTestDouble.Error) {
122 | XCTAssertEqual(
123 | error.code,
124 | .imageError
125 | )
126 | XCTAssertNil(error.underlying)
127 | }
128 | }
129 | }
130 | }
131 |
132 | extension NetworkImageSerializationTestCase {
133 | func testSuccess() {
134 | ImageSourceTestDouble.imageSourceReturnImageSource = NSObject()
135 |
136 | ImageSourceTestDouble.imageReturnImage = NSObject()
137 |
138 | XCTAssertNoThrow(
139 | try {
140 | let image = try NetworkImageSerializationTestDouble.image(with: DataTestDouble())
141 |
142 | XCTAssertEqual(
143 | ImageSourceTestDouble.imageSourceParameterData as Data?,
144 | DataTestDouble()
145 | )
146 | XCTAssertNil(ImageSourceTestDouble.imageSourceParameterOptions)
147 |
148 | XCTAssertIdentical(
149 | ImageSourceTestDouble.imageParameterImageSource,
150 | ImageSourceTestDouble.imageSourceReturnImageSource
151 | )
152 | XCTAssertEqual(
153 | ImageSourceTestDouble.imageParameterIndex,
154 | 0
155 | )
156 | XCTAssertNil(ImageSourceTestDouble.imageSourceParameterOptions)
157 |
158 | XCTAssertIdentical(
159 | image,
160 | ImageSourceTestDouble.imageReturnImage
161 | )
162 | }()
163 | )
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkJSONOperationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkJSONOperationTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkJSONOperationTestCase : XCTestCase {
17 | private typealias NetworkJSONOperationTestDouble = NetworkJSONOperation
18 | }
19 |
20 | extension NetworkJSONOperationTestCase {
21 | private struct SessionTestDouble : NetworkJSONOperationSession {
22 | static var parameterRequest: URLRequest?
23 | static var returnData: Data?
24 | static var returnResponse: URLResponse?
25 | static let returnError = NSErrorTestDouble()
26 |
27 | static func data(for request: URLRequest) async throws -> (
28 | Data,
29 | URLResponse
30 | ) {
31 | self.parameterRequest = request
32 | guard
33 | let returnData = self.returnData,
34 | let returnResponse = self.returnResponse
35 | else {
36 | throw self.returnError
37 | }
38 | return (
39 | returnData,
40 | returnResponse
41 | )
42 | }
43 | }
44 | }
45 |
46 | extension NetworkJSONOperationTestCase {
47 | private struct JSONHandlerTestDouble : NetworkJSONOperationJSONHandler {
48 | static var parameterData: Data?
49 | static var parameterResponse: URLResponse?
50 | static var returnJSON: NSObject?
51 | static let returnError = NSErrorTestDouble()
52 |
53 | static func json(
54 | with data: Data,
55 | response: URLResponse
56 | ) throws -> NSObject {
57 | self.parameterData = data
58 | self.parameterResponse = response
59 | guard
60 | let returnJSON = self.returnJSON
61 | else {
62 | throw self.returnError
63 | }
64 | return returnJSON
65 | }
66 | }
67 | }
68 |
69 | extension NetworkJSONOperationTestCase {
70 | override func tearDown() {
71 | SessionTestDouble.parameterRequest = nil
72 | SessionTestDouble.returnData = nil
73 | SessionTestDouble.returnResponse = nil
74 |
75 | JSONHandlerTestDouble.parameterData = nil
76 | JSONHandlerTestDouble.parameterResponse = nil
77 | JSONHandlerTestDouble.returnJSON = nil
78 | }
79 | }
80 |
81 | extension NetworkJSONOperationTestCase {
82 | func testSessionError() async {
83 | SessionTestDouble.returnData = nil
84 | SessionTestDouble.returnResponse = nil
85 |
86 | JSONHandlerTestDouble.returnJSON = nil
87 |
88 | do {
89 | _ = try await NetworkJSONOperationTestDouble.json(for: URLRequestTestDouble())
90 | XCTFail()
91 | } catch {
92 | XCTAssertEqual(
93 | SessionTestDouble.parameterRequest,
94 | URLRequestTestDouble()
95 | )
96 |
97 | XCTAssertNil(JSONHandlerTestDouble.parameterData)
98 | XCTAssertNil(JSONHandlerTestDouble.parameterResponse)
99 |
100 | if let error = try? XCTUnwrap(error as? NetworkJSONOperationTestDouble.Error) {
101 | XCTAssertEqual(
102 | error.code,
103 | .sessionError
104 | )
105 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
106 | XCTAssertIdentical(
107 | underlying,
108 | SessionTestDouble.returnError
109 | )
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
116 | extension NetworkJSONOperationTestCase {
117 | func testJSONHandlerError() async {
118 | SessionTestDouble.returnData = DataTestDouble()
119 | SessionTestDouble.returnResponse = HTTPURLResponseTestDouble()
120 |
121 | JSONHandlerTestDouble.returnJSON = nil
122 |
123 | do {
124 | _ = try await NetworkJSONOperationTestDouble.json(for: URLRequestTestDouble())
125 | XCTFail()
126 | } catch {
127 | XCTAssertEqual(
128 | SessionTestDouble.parameterRequest,
129 | URLRequestTestDouble()
130 | )
131 |
132 | XCTAssertEqual(
133 | JSONHandlerTestDouble.parameterData,
134 | SessionTestDouble.returnData
135 | )
136 | XCTAssertIdentical(
137 | JSONHandlerTestDouble.parameterResponse,
138 | SessionTestDouble.returnResponse
139 | )
140 |
141 | if let error = try? XCTUnwrap(error as? NetworkJSONOperationTestDouble.Error) {
142 | XCTAssertEqual(
143 | error.code,
144 | .jsonHandlerError
145 | )
146 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
147 | XCTAssertIdentical(
148 | underlying,
149 | JSONHandlerTestDouble.returnError
150 | )
151 | }
152 | }
153 | }
154 | }
155 | }
156 |
157 | extension NetworkJSONOperationTestCase {
158 | func testSuccess() async {
159 | SessionTestDouble.returnData = DataTestDouble()
160 | SessionTestDouble.returnResponse = HTTPURLResponseTestDouble()
161 |
162 | JSONHandlerTestDouble.returnJSON = NSObject()
163 |
164 | do {
165 | let json = try await NetworkJSONOperationTestDouble.json(for: URLRequestTestDouble())
166 |
167 | XCTAssertEqual(
168 | SessionTestDouble.parameterRequest,
169 | URLRequestTestDouble()
170 | )
171 |
172 | XCTAssertEqual(
173 | JSONHandlerTestDouble.parameterData,
174 | SessionTestDouble.returnData
175 | )
176 | XCTAssertIdentical(
177 | JSONHandlerTestDouble.parameterResponse,
178 | SessionTestDouble.returnResponse
179 | )
180 |
181 | XCTAssertIdentical(
182 | json,
183 | JSONHandlerTestDouble.returnJSON
184 | )
185 | } catch {
186 | XCTFail()
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkImageOperationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageOperationTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkImageOperationTestCase: XCTestCase {
17 | private typealias NetworkImageOperationTestDouble = NetworkImageOperation
18 | }
19 |
20 | extension NetworkImageOperationTestCase {
21 | private struct SessionTestDouble : NetworkImageOperationSession {
22 | static var parameterRequest: URLRequest?
23 | static var returnData: Data?
24 | static var returnResponse: URLResponse?
25 | static let returnError = NSErrorTestDouble()
26 |
27 | static func data(for request: URLRequest) async throws -> (
28 | Data,
29 | URLResponse
30 | ) {
31 | self.parameterRequest = request
32 | guard
33 | let returnData = self.returnData,
34 | let returnResponse = self.returnResponse
35 | else {
36 | throw self.returnError
37 | }
38 | return (
39 | returnData,
40 | returnResponse
41 | )
42 | }
43 | }
44 | }
45 |
46 | extension NetworkImageOperationTestCase {
47 | private struct ImageHandlerTestDouble : NetworkImageOperationImageHandler {
48 | static var parameterData: Data?
49 | static var parameterResponse: URLResponse?
50 | static var returnImage: NSObject?
51 | static let returnError = NSErrorTestDouble()
52 |
53 | static func image(
54 | with data: Data,
55 | response: URLResponse
56 | ) throws -> NSObject {
57 | self.parameterData = data
58 | self.parameterResponse = response
59 | guard
60 | let returnImage = self.returnImage
61 | else {
62 | throw self.returnError
63 | }
64 | return returnImage
65 | }
66 | }
67 | }
68 |
69 | extension NetworkImageOperationTestCase {
70 | override func tearDown() {
71 | SessionTestDouble.parameterRequest = nil
72 | SessionTestDouble.returnData = nil
73 | SessionTestDouble.returnResponse = nil
74 |
75 | ImageHandlerTestDouble.parameterData = nil
76 | ImageHandlerTestDouble.parameterResponse = nil
77 | ImageHandlerTestDouble.returnImage = nil
78 | }
79 | }
80 |
81 | extension NetworkImageOperationTestCase {
82 | func testSessionError() async {
83 | SessionTestDouble.returnData = nil
84 | SessionTestDouble.returnResponse = nil
85 |
86 | ImageHandlerTestDouble.returnImage = nil
87 |
88 | do {
89 | _ = try await NetworkImageOperationTestDouble.image(for: URLRequestTestDouble())
90 | XCTFail()
91 | } catch {
92 | XCTAssertEqual(
93 | SessionTestDouble.parameterRequest,
94 | URLRequestTestDouble()
95 | )
96 |
97 | XCTAssertNil(ImageHandlerTestDouble.parameterData)
98 | XCTAssertNil(ImageHandlerTestDouble.parameterResponse)
99 |
100 | if let error = try? XCTUnwrap(error as? NetworkImageOperationTestDouble.Error) {
101 | XCTAssertEqual(
102 | error.code,
103 | .sessionError
104 | )
105 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
106 | XCTAssertIdentical(
107 | underlying,
108 | SessionTestDouble.returnError
109 | )
110 | }
111 | }
112 | }
113 | }
114 | }
115 |
116 | extension NetworkImageOperationTestCase {
117 | func testImageHandlerError() async {
118 | SessionTestDouble.returnData = DataTestDouble()
119 | SessionTestDouble.returnResponse = HTTPURLResponseTestDouble()
120 |
121 | ImageHandlerTestDouble.returnImage = nil
122 |
123 | do {
124 | _ = try await NetworkImageOperationTestDouble.image(for: URLRequestTestDouble())
125 | XCTFail()
126 | } catch {
127 | XCTAssertEqual(
128 | SessionTestDouble.parameterRequest,
129 | URLRequestTestDouble()
130 | )
131 |
132 | XCTAssertEqual(
133 | ImageHandlerTestDouble.parameterData,
134 | SessionTestDouble.returnData
135 | )
136 | XCTAssertIdentical(
137 | ImageHandlerTestDouble.parameterResponse,
138 | SessionTestDouble.returnResponse
139 | )
140 |
141 | if let error = try? XCTUnwrap(error as? NetworkImageOperationTestDouble.Error) {
142 | XCTAssertEqual(
143 | error.code,
144 | .imageHandlerError
145 | )
146 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
147 | XCTAssertIdentical(
148 | underlying,
149 | ImageHandlerTestDouble.returnError
150 | )
151 | }
152 | }
153 | }
154 | }
155 | }
156 |
157 | extension NetworkImageOperationTestCase {
158 | func testSuccess() async {
159 | SessionTestDouble.returnData = DataTestDouble()
160 | SessionTestDouble.returnResponse = HTTPURLResponseTestDouble()
161 |
162 | ImageHandlerTestDouble.returnImage = NSObject()
163 |
164 | do {
165 | let image = try await NetworkImageOperationTestDouble.image(for: URLRequestTestDouble())
166 |
167 | XCTAssertEqual(
168 | SessionTestDouble.parameterRequest,
169 | URLRequestTestDouble()
170 | )
171 |
172 | XCTAssertEqual(
173 | ImageHandlerTestDouble.parameterData,
174 | SessionTestDouble.returnData
175 | )
176 | XCTAssertIdentical(
177 | ImageHandlerTestDouble.parameterResponse,
178 | SessionTestDouble.returnResponse
179 | )
180 |
181 | XCTAssertIdentical(
182 | image,
183 | ImageHandlerTestDouble.returnImage
184 | )
185 | } catch {
186 | XCTFail()
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkImageSourceTests.m:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageSourceTests.m
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | #import
15 |
16 | #import "NetworkImageSource.h"
17 |
18 | static CGImageSourceRef _Nullable NetworkImageSourceTestDoubleCreateImageSource(CFDataRef _Nonnull, CFDictionaryRef _Nullable) CF_RETURNS_RETAINED;
19 |
20 | static id NetworkImageSourceTestDoubleCreateImageSourceParameterData = 0;
21 | static id NetworkImageSourceTestDoubleCreateImageSourceParameterOptions = 0;
22 | static id NetworkImageSourceTestDoubleCreateImageSourceReturnImageSource = 0;
23 |
24 | CGImageSourceRef NetworkImageSourceTestDoubleCreateImageSource(CFDataRef data, CFDictionaryRef options) {
25 | NetworkImageSourceTestDoubleCreateImageSourceParameterData = (__bridge id)data;
26 | NetworkImageSourceTestDoubleCreateImageSourceParameterOptions = (__bridge id)options;
27 | return (__bridge_retained CGImageSourceRef)NetworkImageSourceTestDoubleCreateImageSourceReturnImageSource;
28 | }
29 |
30 | static CGImageRef _Nullable NetworkImageSourceTestDoubleCreateImage(CGImageSourceRef _Nonnull, size_t, CFDictionaryRef _Nullable) CF_RETURNS_RETAINED;
31 |
32 | static id NetworkImageSourceTestDoubleCreateImageParameterImageSource = 0;
33 | static size_t NetworkImageSourceTestDoubleCreateImageParameterIndex = 0;
34 | static id NetworkImageSourceTestDoubleCreateImageParameterOptions = 0;
35 | static id NetworkImageSourceTestDoubleCreateImageReturnImage = 0;
36 |
37 | CGImageRef NetworkImageSourceTestDoubleCreateImage(CGImageSourceRef imageSource, size_t index, CFDictionaryRef options) {
38 | NetworkImageSourceTestDoubleCreateImageParameterImageSource = (__bridge id)imageSource;
39 | NetworkImageSourceTestDoubleCreateImageParameterIndex = index;
40 | NetworkImageSourceTestDoubleCreateImageParameterOptions = (__bridge id)options;
41 | return (__bridge_retained CGImageRef)NetworkImageSourceTestDoubleCreateImageReturnImage;
42 | }
43 |
44 | @interface NetworkImageSourceTestDouble : NetworkImageSource
45 |
46 | @end
47 |
48 | @implementation NetworkImageSourceTestDouble
49 |
50 | @end
51 |
52 | @implementation NetworkImageSourceTestDouble (CreateImageSource)
53 |
54 | + (CGImageSourceRef _Nullable (*_Nonnull)(CFDataRef _Nonnull, CFDictionaryRef _Nullable))createImageSource {
55 | return NetworkImageSourceTestDoubleCreateImageSource;
56 | }
57 |
58 | @end
59 |
60 | @implementation NetworkImageSourceTestDouble (CreateImage)
61 |
62 | + (CGImageRef _Nullable (*_Nonnull)(CGImageSourceRef _Nonnull, size_t, CFDictionaryRef _Nullable))createImage {
63 | return NetworkImageSourceTestDoubleCreateImage;
64 | }
65 |
66 | @end
67 |
68 | @interface NetworkImageSourceTestCase : XCTestCase
69 |
70 | @end
71 |
72 | @implementation NetworkImageSourceTestCase
73 |
74 | @end
75 |
76 | @implementation NetworkImageSourceTestCase (TearDown)
77 |
78 | - (void)tearDown {
79 | NetworkImageSourceTestDoubleCreateImageSourceParameterData = 0;
80 | NetworkImageSourceTestDoubleCreateImageSourceParameterOptions = 0;
81 | NetworkImageSourceTestDoubleCreateImageSourceReturnImageSource = 0;
82 |
83 | NetworkImageSourceTestDoubleCreateImageParameterImageSource = 0;
84 | NetworkImageSourceTestDoubleCreateImageParameterIndex = 0;
85 | NetworkImageSourceTestDoubleCreateImageParameterOptions = 0;
86 | NetworkImageSourceTestDoubleCreateImageReturnImage = 0;
87 | }
88 |
89 | @end
90 |
91 | @implementation NetworkImageSourceTestCase (CreateImageSource)
92 |
93 | - (void)testCreateImageSource {
94 | XCTAssert([NetworkImageSource createImageSource] == CGImageSourceCreateWithData);
95 | }
96 |
97 | @end
98 |
99 | @implementation NetworkImageSourceTestCase (CreateImage)
100 |
101 | - (void)testCreateImage {
102 | XCTAssert([NetworkImageSource createImage] == CGImageSourceCreateImageAtIndex);
103 | }
104 |
105 | @end
106 |
107 | @implementation NetworkImageSourceTestCase (CreateImageSourceTestDouble)
108 |
109 | - (void)testCreateImageSourceTestDouble {
110 | NetworkImageSourceTestDoubleCreateImageSourceReturnImageSource = [[NSObject alloc] init];
111 |
112 | id data = [[NSObject alloc] init];
113 | id options = [[NSObject alloc] init];
114 |
115 | id imageSource = (__bridge_transfer id)[NetworkImageSourceTestDouble createImageSourceWithData:(__bridge CFDataRef)data
116 | options:(__bridge CFDictionaryRef)options];
117 |
118 | XCTAssert(NetworkImageSourceTestDoubleCreateImageSourceParameterData == data);
119 | XCTAssert(NetworkImageSourceTestDoubleCreateImageSourceParameterOptions == options);
120 |
121 | XCTAssert(imageSource == NetworkImageSourceTestDoubleCreateImageSourceReturnImageSource);
122 | }
123 |
124 | @end
125 |
126 | @implementation NetworkImageSourceTestCase (CreateImageTestDouble)
127 |
128 | - (void)testCreateImageTestDouble {
129 | NetworkImageSourceTestDoubleCreateImageReturnImage = [[NSObject alloc] init];
130 |
131 | id imageSource = [[NSObject alloc] init];
132 | size_t index = 1;
133 | id options = [[NSObject alloc] init];
134 |
135 | id image = (__bridge_transfer id)[NetworkImageSourceTestDouble createImageWithImageSource:(__bridge CGImageSourceRef)imageSource
136 | atIndex:index
137 | options:(__bridge CFDictionaryRef)options];
138 |
139 | XCTAssert(NetworkImageSourceTestDoubleCreateImageParameterImageSource == imageSource);
140 | XCTAssert(NetworkImageSourceTestDoubleCreateImageParameterIndex == index);
141 | XCTAssert(NetworkImageSourceTestDoubleCreateImageParameterOptions == options);
142 |
143 | XCTAssert(image == NetworkImageSourceTestDoubleCreateImageReturnImage);
144 | }
145 |
146 | @end
147 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkImageHandlerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkImageHandlerTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkImageHandlerTestCase : XCTestCase {
17 | private typealias NetworkImageHandlerTestDouble = NetworkImageHandler
18 | }
19 |
20 | extension NetworkImageHandlerTestCase {
21 | private struct DataHandlerTestDouble : NetworkImageHandlerDataHandler {
22 | static var parameterData: Data?
23 | static var parameterResponse: URLResponse?
24 | static var returnData: Data?
25 | static let returnError = NSErrorTestDouble()
26 |
27 | static func data(
28 | with data: Data,
29 | response: URLResponse
30 | ) throws -> Data {
31 | self.parameterData = data
32 | self.parameterResponse = response
33 | guard
34 | let returnData = self.returnData
35 | else {
36 | throw self.returnError
37 | }
38 | return returnData
39 | }
40 | }
41 | }
42 |
43 | extension NetworkImageHandlerTestCase {
44 | private struct ImageSerializationTestDouble : NetworkImageHandlerImageSerialization {
45 | static var parameterData: Data?
46 | static var returnImage: NSObject?
47 | static let returnError = NSErrorTestDouble()
48 |
49 | static func image(with data: Data) throws -> NSObject {
50 | self.parameterData = data
51 | guard
52 | let returnImage = self.returnImage
53 | else {
54 | throw self.returnError
55 | }
56 | return returnImage
57 | }
58 | }
59 | }
60 |
61 | extension NetworkImageHandlerTestCase {
62 | override func tearDown() {
63 | DataHandlerTestDouble.parameterData = nil
64 | DataHandlerTestDouble.parameterResponse = nil
65 | DataHandlerTestDouble.returnData = nil
66 |
67 | ImageSerializationTestDouble.parameterData = nil
68 | ImageSerializationTestDouble.returnImage = nil
69 | }
70 | }
71 |
72 | extension NetworkImageHandlerTestCase {
73 | func testMimeTypeError() {
74 | DataHandlerTestDouble.returnData = nil
75 |
76 | ImageSerializationTestDouble.returnImage = nil
77 |
78 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "TEXT/JAVASCRIPT"])
79 |
80 | XCTAssertThrowsError(
81 | try NetworkImageHandlerTestDouble.image(
82 | with: DataTestDouble(),
83 | response: response
84 | )
85 | ) { error in
86 | XCTAssertNil(DataHandlerTestDouble.parameterData)
87 | XCTAssertNil(DataHandlerTestDouble.parameterResponse)
88 |
89 | XCTAssertNil(ImageSerializationTestDouble.parameterData)
90 |
91 | if let error = try? XCTUnwrap(error as? NetworkImageHandlerTestDouble.Error) {
92 | XCTAssertEqual(
93 | error.code,
94 | .mimeTypeError
95 | )
96 | XCTAssertNil(error.underlying)
97 | }
98 | }
99 | }
100 | }
101 |
102 | extension NetworkImageHandlerTestCase {
103 | func testDataHandlerError() {
104 | DataHandlerTestDouble.returnData = nil
105 |
106 | ImageSerializationTestDouble.returnImage = nil
107 |
108 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "IMAGE/PNG"])
109 |
110 | XCTAssertThrowsError(
111 | try NetworkImageHandlerTestDouble.image(
112 | with: DataTestDouble(),
113 | response: response
114 | )
115 | ) { error in
116 | XCTAssertEqual(
117 | DataHandlerTestDouble.parameterData,
118 | DataTestDouble()
119 | )
120 | XCTAssertIdentical(
121 | DataHandlerTestDouble.parameterResponse,
122 | response
123 | )
124 |
125 | XCTAssertNil(ImageSerializationTestDouble.parameterData)
126 |
127 | if let error = try? XCTUnwrap(error as? NetworkImageHandlerTestDouble.Error) {
128 | XCTAssertEqual(
129 | error.code,
130 | .dataHandlerError
131 | )
132 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
133 | XCTAssertIdentical(
134 | underlying,
135 | DataHandlerTestDouble.returnError
136 | )
137 | }
138 | }
139 | }
140 | }
141 | }
142 |
143 | extension NetworkImageHandlerTestCase {
144 | func testImageSerializationError() {
145 | DataHandlerTestDouble.returnData = DataTestDouble()
146 |
147 | ImageSerializationTestDouble.returnImage = nil
148 |
149 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "IMAGE/PNG"])
150 |
151 | XCTAssertThrowsError(
152 | try NetworkImageHandlerTestDouble.image(
153 | with: DataTestDouble(),
154 | response: response
155 | )
156 | ) { error in
157 | XCTAssertEqual(
158 | DataHandlerTestDouble.parameterData,
159 | DataTestDouble()
160 | )
161 | XCTAssertIdentical(
162 | DataHandlerTestDouble.parameterResponse,
163 | response
164 | )
165 |
166 | XCTAssertEqual(
167 | ImageSerializationTestDouble.parameterData,
168 | DataHandlerTestDouble.returnData
169 | )
170 |
171 | if let error = try? XCTUnwrap(error as? NetworkImageHandlerTestDouble.Error) {
172 | XCTAssertEqual(
173 | error.code,
174 | .imageSerializationError
175 | )
176 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
177 | XCTAssertIdentical(
178 | underlying,
179 | ImageSerializationTestDouble.returnError
180 | )
181 | }
182 | }
183 | }
184 | }
185 | }
186 |
187 | extension NetworkImageHandlerTestCase {
188 | func testSuccess() {
189 | DataHandlerTestDouble.returnData = DataTestDouble()
190 |
191 | ImageSerializationTestDouble.returnImage = NSObject()
192 |
193 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "IMAGE/PNG"])
194 |
195 | XCTAssertNoThrow(
196 | try {
197 | let image = try NetworkImageHandlerTestDouble.image(
198 | with: DataTestDouble(),
199 | response: response
200 | )
201 |
202 | XCTAssertEqual(
203 | DataHandlerTestDouble.parameterData,
204 | DataTestDouble()
205 | )
206 | XCTAssertIdentical(
207 | DataHandlerTestDouble.parameterResponse,
208 | response
209 | )
210 |
211 | XCTAssertEqual(
212 | ImageSerializationTestDouble.parameterData,
213 | DataHandlerTestDouble.returnData
214 | )
215 |
216 | XCTAssertIdentical(
217 | image,
218 | ImageSerializationTestDouble.returnImage
219 | )
220 | }()
221 | )
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/AlbumsTests/NetworkJSONHandlerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkJSONHandlerTests.swift
3 | // AlbumsTests
4 | //
5 | // Copyright © 2021 North Bronson Software
6 | //
7 | // This Item is protected by copyright and/or related rights. You are free to use this Item in any way that is permitted by the copyright and related rights legislation that applies to your use. In addition, no permission is required from the rights-holder(s) for scholarly, educational, or non-commercial uses. For other uses, you need to obtain permission from the rights-holder(s).
8 | //
9 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10 | //
11 | // 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.
12 | //
13 |
14 | import XCTest
15 |
16 | final class NetworkJSONHandlerTestCase : XCTestCase {
17 |
18 | }
19 |
20 | extension NetworkJSONHandlerTestCase {
21 | private struct DataHandlerTestDouble : NetworkJSONHandlerDataHandler {
22 | static var parameterData: Data?
23 | static var parameterResponse: URLResponse?
24 | static var returnData: Data?
25 | static let returnError = NSErrorTestDouble()
26 |
27 | static func data(
28 | with data: Data,
29 | response: URLResponse
30 | ) throws -> Data {
31 | self.parameterData = data
32 | self.parameterResponse = response
33 | guard
34 | let returnData = self.returnData
35 | else {
36 | throw self.returnError
37 | }
38 | return returnData
39 | }
40 | }
41 | }
42 |
43 | extension NetworkJSONHandlerTestCase {
44 | private struct JSONSerializationTestDouble : NetworkJSONHandlerJSONSerialization {
45 | static var parameterData: Data?
46 | static var parameterOptions: JSONSerialization.ReadingOptions?
47 | static var returnJSON: NSObject?
48 | static let returnError = NSErrorTestDouble()
49 |
50 | static func jsonObject(
51 | with data: Data,
52 | options: JSONSerialization.ReadingOptions
53 | ) throws -> NSObject {
54 | self.parameterData = data
55 | self.parameterOptions = options
56 | guard
57 | let returnJSON = self.returnJSON
58 | else {
59 | throw self.returnError
60 | }
61 | return returnJSON
62 | }
63 | }
64 | }
65 |
66 | extension NetworkJSONHandlerTestCase {
67 | private typealias NetworkJSONHandlerTestDouble = NetworkJSONHandler
68 | }
69 |
70 | extension NetworkJSONHandlerTestCase {
71 | override func tearDown() {
72 | DataHandlerTestDouble.parameterData = nil
73 | DataHandlerTestDouble.parameterResponse = nil
74 | DataHandlerTestDouble.returnData = nil
75 |
76 | JSONSerializationTestDouble.parameterData = nil
77 | JSONSerializationTestDouble.parameterOptions = nil
78 | JSONSerializationTestDouble.returnJSON = nil
79 | }
80 | }
81 |
82 | extension NetworkJSONHandlerTestCase {
83 | func testMimeTypeError() {
84 | DataHandlerTestDouble.returnData = nil
85 |
86 | JSONSerializationTestDouble.returnJSON = nil
87 |
88 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "IMAGE/PNG"])
89 |
90 | XCTAssertThrowsError(
91 | try NetworkJSONHandlerTestDouble.json(
92 | with: DataTestDouble(),
93 | response: response
94 | )
95 | ) { error in
96 | XCTAssertNil(DataHandlerTestDouble.parameterData)
97 | XCTAssertNil(DataHandlerTestDouble.parameterResponse)
98 |
99 | XCTAssertNil(JSONSerializationTestDouble.parameterData)
100 | XCTAssertNil(JSONSerializationTestDouble.parameterOptions)
101 |
102 | if let error = try? XCTUnwrap(error as? NetworkJSONHandlerTestDouble.Error) {
103 | XCTAssertEqual(
104 | error.code,
105 | .mimeTypeError
106 | )
107 | XCTAssertNil(error.underlying)
108 | }
109 | }
110 | }
111 | }
112 |
113 | extension NetworkJSONHandlerTestCase {
114 | func testDataHandlerError() {
115 | DataHandlerTestDouble.returnData = nil
116 |
117 | JSONSerializationTestDouble.returnJSON = nil
118 |
119 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "TEXT/JAVASCRIPT"])
120 |
121 | XCTAssertThrowsError(
122 | try NetworkJSONHandlerTestDouble.json(
123 | with: DataTestDouble(),
124 | response: response
125 | )
126 | ) { error in
127 | XCTAssertEqual(
128 | DataHandlerTestDouble.parameterData,
129 | DataTestDouble()
130 | )
131 | XCTAssertIdentical(
132 | DataHandlerTestDouble.parameterResponse,
133 | response
134 | )
135 |
136 | XCTAssertNil(JSONSerializationTestDouble.parameterData)
137 | XCTAssertNil(JSONSerializationTestDouble.parameterOptions)
138 |
139 | if let error = try? XCTUnwrap(error as? NetworkJSONHandlerTestDouble.Error) {
140 | XCTAssertEqual(
141 | error.code,
142 | .dataHandlerError
143 | )
144 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
145 | XCTAssertIdentical(
146 | underlying,
147 | DataHandlerTestDouble.returnError
148 | )
149 | }
150 | }
151 | }
152 | }
153 | }
154 |
155 | extension NetworkJSONHandlerTestCase {
156 | func testJSONSerializationError() {
157 | DataHandlerTestDouble.returnData = DataTestDouble()
158 |
159 | JSONSerializationTestDouble.returnJSON = nil
160 |
161 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "TEXT/JAVASCRIPT"])
162 |
163 | XCTAssertThrowsError(
164 | try NetworkJSONHandlerTestDouble.json(
165 | with: DataTestDouble(),
166 | response: response
167 | )
168 | ) { error in
169 | XCTAssertEqual(
170 | DataHandlerTestDouble.parameterData,
171 | DataTestDouble()
172 | )
173 | XCTAssertIdentical(
174 | DataHandlerTestDouble.parameterResponse,
175 | response
176 | )
177 |
178 | XCTAssertEqual(
179 | JSONSerializationTestDouble.parameterData,
180 | DataHandlerTestDouble.returnData
181 | )
182 | XCTAssertEqual(
183 | JSONSerializationTestDouble.parameterOptions,
184 | []
185 | )
186 |
187 | if let error = try? XCTUnwrap(error as? NetworkJSONHandlerTestDouble.Error) {
188 | XCTAssertEqual(
189 | error.code,
190 | .jsonSerializationError
191 | )
192 | if let underlying = try? XCTUnwrap(error.underlying as NSError?) {
193 | XCTAssertIdentical(
194 | underlying,
195 | JSONSerializationTestDouble.returnError
196 | )
197 | }
198 | }
199 | }
200 | }
201 | }
202 |
203 | extension NetworkJSONHandlerTestCase {
204 | func testSuccess() {
205 | DataHandlerTestDouble.returnData = DataTestDouble()
206 |
207 | JSONSerializationTestDouble.returnJSON = NSObject()
208 |
209 | let response = HTTPURLResponseTestDouble(headerFields: ["CONTENT-TYPE": "TEXT/JAVASCRIPT"])
210 |
211 | XCTAssertNoThrow(
212 | try {
213 | let json = try NetworkJSONHandlerTestDouble.json(
214 | with: DataTestDouble(),
215 | response: response
216 | )
217 |
218 | XCTAssertEqual(
219 | DataHandlerTestDouble.parameterData,
220 | DataTestDouble()
221 | )
222 | XCTAssertIdentical(
223 | DataHandlerTestDouble.parameterResponse,
224 | response
225 | )
226 |
227 | XCTAssertEqual(
228 | JSONSerializationTestDouble.parameterData,
229 | DataHandlerTestDouble.returnData
230 | )
231 | XCTAssertEqual(
232 | JSONSerializationTestDouble.parameterOptions,
233 | []
234 | )
235 |
236 | XCTAssertIdentical(
237 | json,
238 | JSONSerializationTestDouble.returnJSON
239 | )
240 | }()
241 | )
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/Albums.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 55;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1D0AACD627386E3D0065C4B8 /* NetworkDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACD527386E3D0065C4B8 /* NetworkDataHandler.swift */; };
11 | 1D0AACD727386E3D0065C4B8 /* NetworkDataHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACD527386E3D0065C4B8 /* NetworkDataHandler.swift */; };
12 | 1D0AACDA27386E470065C4B8 /* NetworkDataHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACD827386E470065C4B8 /* NetworkDataHandlerTests.swift */; };
13 | 1D0AACDC27386EC90065C4B8 /* NetworkJSONHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACDB27386EC90065C4B8 /* NetworkJSONHandler.swift */; };
14 | 1D0AACDD27386EC90065C4B8 /* NetworkJSONHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACDB27386EC90065C4B8 /* NetworkJSONHandler.swift */; };
15 | 1D0AACDF27386ED70065C4B8 /* NetworkJSONHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACDE27386ED70065C4B8 /* NetworkJSONHandlerTests.swift */; };
16 | 1D0AACE427386F2B0065C4B8 /* NetworkImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACE327386F2B0065C4B8 /* NetworkImageSource.m */; };
17 | 1D0AACE527386F2B0065C4B8 /* NetworkImageSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACE327386F2B0065C4B8 /* NetworkImageSource.m */; };
18 | 1D0AACE727386F380065C4B8 /* NetworkImageSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACE627386F380065C4B8 /* NetworkImageSourceTests.m */; };
19 | 1D0AACE927386FCA0065C4B8 /* NetworkImageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACE827386FCA0065C4B8 /* NetworkImageSerialization.swift */; };
20 | 1D0AACEA27386FCA0065C4B8 /* NetworkImageSerialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACE827386FCA0065C4B8 /* NetworkImageSerialization.swift */; };
21 | 1D0AACEC27386FDA0065C4B8 /* NetworkImageSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACEB27386FDA0065C4B8 /* NetworkImageSerializationTests.swift */; };
22 | 1D0AACEE273870480065C4B8 /* NetworkImageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACED273870480065C4B8 /* NetworkImageHandler.swift */; };
23 | 1D0AACEF273870480065C4B8 /* NetworkImageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACED273870480065C4B8 /* NetworkImageHandler.swift */; };
24 | 1D0AACF1273870570065C4B8 /* NetworkImageHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACF0273870570065C4B8 /* NetworkImageHandlerTests.swift */; };
25 | 1D0AACF327387EA10065C4B8 /* NetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACF227387EA10065C4B8 /* NetworkSession.swift */; };
26 | 1D0AACF427387EA10065C4B8 /* NetworkSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACF227387EA10065C4B8 /* NetworkSession.swift */; };
27 | 1D0AACF627387EAD0065C4B8 /* NetworkSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACF527387EAD0065C4B8 /* NetworkSessionTests.swift */; };
28 | 1D0AACF827387EFA0065C4B8 /* NetworkJSONOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACF727387EFA0065C4B8 /* NetworkJSONOperation.swift */; };
29 | 1D0AACF927387EFA0065C4B8 /* NetworkJSONOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACF727387EFA0065C4B8 /* NetworkJSONOperation.swift */; };
30 | 1D0AACFB27387F060065C4B8 /* NetworkJSONOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACFA27387F060065C4B8 /* NetworkJSONOperationTests.swift */; };
31 | 1D0AACFD273880870065C4B8 /* NetworkImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACFC273880870065C4B8 /* NetworkImageOperation.swift */; };
32 | 1D0AACFE273880870065C4B8 /* NetworkImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACFC273880870065C4B8 /* NetworkImageOperation.swift */; };
33 | 1D0AAD00273880940065C4B8 /* NetworkImageOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AACFF273880940065C4B8 /* NetworkImageOperationTests.swift */; };
34 | 1D0AAD02273881620065C4B8 /* AlbumsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD01273881620065C4B8 /* AlbumsListModel.swift */; };
35 | 1D0AAD03273881620065C4B8 /* AlbumsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD01273881620065C4B8 /* AlbumsListModel.swift */; };
36 | 1D0AAD052738816F0065C4B8 /* AlbumsListModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD042738816E0065C4B8 /* AlbumsListModelTests.swift */; };
37 | 1D0AAD072738818E0065C4B8 /* Albums.json in Resources */ = {isa = PBXBuildFile; fileRef = 1D0AAD062738818D0065C4B8 /* Albums.json */; };
38 | 1D0AAD09273881D00065C4B8 /* AlbumsListRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD08273881D00065C4B8 /* AlbumsListRowModel.swift */; };
39 | 1D0AAD0A273881D00065C4B8 /* AlbumsListRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD08273881D00065C4B8 /* AlbumsListRowModel.swift */; };
40 | 1D0AAD0C273881DC0065C4B8 /* AlbumsListRowModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD0B273881DC0065C4B8 /* AlbumsListRowModelTests.swift */; };
41 | 1D0AAD0E2738823A0065C4B8 /* AlbumsListRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD0D2738823A0065C4B8 /* AlbumsListRowView.swift */; };
42 | 1D0AAD10273882750065C4B8 /* AlbumsListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0AAD0F273882750065C4B8 /* AlbumsListView.swift */; };
43 | AE657E3B27068BE40032C442 /* AlbumsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE657E3A27068BE40032C442 /* AlbumsApp.swift */; };
44 | AE657E4C27068BE70032C442 /* AlbumsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE657E4B27068BE70032C442 /* AlbumsTests.swift */; };
45 | /* End PBXBuildFile section */
46 |
47 | /* Begin PBXContainerItemProxy section */
48 | AE657E4827068BE70032C442 /* PBXContainerItemProxy */ = {
49 | isa = PBXContainerItemProxy;
50 | containerPortal = AE657E2F27068BE40032C442 /* Project object */;
51 | proxyType = 1;
52 | remoteGlobalIDString = AE657E3627068BE40032C442;
53 | remoteInfo = Albums;
54 | };
55 | /* End PBXContainerItemProxy section */
56 |
57 | /* Begin PBXFileReference section */
58 | 1D0AACD327386DA20065C4B8 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
59 | 1D0AACD427386DA20065C4B8 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; };
60 | 1D0AACD527386E3D0065C4B8 /* NetworkDataHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkDataHandler.swift; sourceTree = ""; };
61 | 1D0AACD827386E470065C4B8 /* NetworkDataHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkDataHandlerTests.swift; sourceTree = ""; };
62 | 1D0AACDB27386EC90065C4B8 /* NetworkJSONHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONHandler.swift; sourceTree = ""; };
63 | 1D0AACDE27386ED70065C4B8 /* NetworkJSONHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONHandlerTests.swift; sourceTree = ""; };
64 | 1D0AACE027386F2B0065C4B8 /* Albums-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Albums-Bridging-Header.h"; sourceTree = ""; };
65 | 1D0AACE227386F2B0065C4B8 /* NetworkImageSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NetworkImageSource.h; sourceTree = ""; };
66 | 1D0AACE327386F2B0065C4B8 /* NetworkImageSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkImageSource.m; sourceTree = ""; };
67 | 1D0AACE627386F380065C4B8 /* NetworkImageSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NetworkImageSourceTests.m; sourceTree = ""; };
68 | 1D0AACE827386FCA0065C4B8 /* NetworkImageSerialization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageSerialization.swift; sourceTree = ""; };
69 | 1D0AACEB27386FDA0065C4B8 /* NetworkImageSerializationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageSerializationTests.swift; sourceTree = ""; };
70 | 1D0AACED273870480065C4B8 /* NetworkImageHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageHandler.swift; sourceTree = ""; };
71 | 1D0AACF0273870570065C4B8 /* NetworkImageHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageHandlerTests.swift; sourceTree = ""; };
72 | 1D0AACF227387EA10065C4B8 /* NetworkSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkSession.swift; sourceTree = ""; };
73 | 1D0AACF527387EAD0065C4B8 /* NetworkSessionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkSessionTests.swift; sourceTree = ""; };
74 | 1D0AACF727387EFA0065C4B8 /* NetworkJSONOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONOperation.swift; sourceTree = ""; };
75 | 1D0AACFA27387F060065C4B8 /* NetworkJSONOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkJSONOperationTests.swift; sourceTree = ""; };
76 | 1D0AACFC273880870065C4B8 /* NetworkImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageOperation.swift; sourceTree = ""; };
77 | 1D0AACFF273880940065C4B8 /* NetworkImageOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkImageOperationTests.swift; sourceTree = ""; };
78 | 1D0AAD01273881620065C4B8 /* AlbumsListModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsListModel.swift; sourceTree = ""; };
79 | 1D0AAD042738816E0065C4B8 /* AlbumsListModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsListModelTests.swift; sourceTree = ""; };
80 | 1D0AAD062738818D0065C4B8 /* Albums.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Albums.json; sourceTree = ""; };
81 | 1D0AAD08273881D00065C4B8 /* AlbumsListRowModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsListRowModel.swift; sourceTree = ""; };
82 | 1D0AAD0B273881DC0065C4B8 /* AlbumsListRowModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsListRowModelTests.swift; sourceTree = ""; };
83 | 1D0AAD0D2738823A0065C4B8 /* AlbumsListRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsListRowView.swift; sourceTree = ""; };
84 | 1D0AAD0F273882750065C4B8 /* AlbumsListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumsListView.swift; sourceTree = ""; };
85 | AE657E3727068BE40032C442 /* Albums.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Albums.app; sourceTree = BUILT_PRODUCTS_DIR; };
86 | AE657E3A27068BE40032C442 /* AlbumsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumsApp.swift; sourceTree = ""; };
87 | AE657E4727068BE70032C442 /* AlbumsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AlbumsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
88 | AE657E4B27068BE70032C442 /* AlbumsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumsTests.swift; sourceTree = ""; };
89 | /* End PBXFileReference section */
90 |
91 | /* Begin PBXFrameworksBuildPhase section */
92 | AE657E3427068BE40032C442 /* Frameworks */ = {
93 | isa = PBXFrameworksBuildPhase;
94 | buildActionMask = 2147483647;
95 | files = (
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | AE657E4427068BE70032C442 /* Frameworks */ = {
100 | isa = PBXFrameworksBuildPhase;
101 | buildActionMask = 2147483647;
102 | files = (
103 | );
104 | runOnlyForDeploymentPostprocessing = 0;
105 | };
106 | /* End PBXFrameworksBuildPhase section */
107 |
108 | /* Begin PBXGroup section */
109 | AE657E2E27068BE40032C442 = {
110 | isa = PBXGroup;
111 | children = (
112 | AE657E3927068BE40032C442 /* Albums */,
113 | AE657E4A27068BE70032C442 /* AlbumsTests */,
114 | 1D0AACD427386DA20065C4B8 /* LICENSE.md */,
115 | AE657E3827068BE40032C442 /* Products */,
116 | 1D0AACD327386DA20065C4B8 /* README.md */,
117 | );
118 | sourceTree = "";
119 | };
120 | AE657E3827068BE40032C442 /* Products */ = {
121 | isa = PBXGroup;
122 | children = (
123 | AE657E3727068BE40032C442 /* Albums.app */,
124 | AE657E4727068BE70032C442 /* AlbumsTests.xctest */,
125 | );
126 | name = Products;
127 | sourceTree = "";
128 | };
129 | AE657E3927068BE40032C442 /* Albums */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 1D0AACE027386F2B0065C4B8 /* Albums-Bridging-Header.h */,
133 | AE657E3A27068BE40032C442 /* AlbumsApp.swift */,
134 | 1D0AAD01273881620065C4B8 /* AlbumsListModel.swift */,
135 | 1D0AAD08273881D00065C4B8 /* AlbumsListRowModel.swift */,
136 | 1D0AAD0D2738823A0065C4B8 /* AlbumsListRowView.swift */,
137 | 1D0AAD0F273882750065C4B8 /* AlbumsListView.swift */,
138 | 1D0AACD527386E3D0065C4B8 /* NetworkDataHandler.swift */,
139 | 1D0AACED273870480065C4B8 /* NetworkImageHandler.swift */,
140 | 1D0AACFC273880870065C4B8 /* NetworkImageOperation.swift */,
141 | 1D0AACE827386FCA0065C4B8 /* NetworkImageSerialization.swift */,
142 | 1D0AACE227386F2B0065C4B8 /* NetworkImageSource.h */,
143 | 1D0AACE327386F2B0065C4B8 /* NetworkImageSource.m */,
144 | 1D0AACDB27386EC90065C4B8 /* NetworkJSONHandler.swift */,
145 | 1D0AACF727387EFA0065C4B8 /* NetworkJSONOperation.swift */,
146 | 1D0AACF227387EA10065C4B8 /* NetworkSession.swift */,
147 | );
148 | path = Albums;
149 | sourceTree = "";
150 | };
151 | AE657E4A27068BE70032C442 /* AlbumsTests */ = {
152 | isa = PBXGroup;
153 | children = (
154 | 1D0AAD062738818D0065C4B8 /* Albums.json */,
155 | 1D0AAD042738816E0065C4B8 /* AlbumsListModelTests.swift */,
156 | 1D0AAD0B273881DC0065C4B8 /* AlbumsListRowModelTests.swift */,
157 | AE657E4B27068BE70032C442 /* AlbumsTests.swift */,
158 | 1D0AACD827386E470065C4B8 /* NetworkDataHandlerTests.swift */,
159 | 1D0AACF0273870570065C4B8 /* NetworkImageHandlerTests.swift */,
160 | 1D0AACFF273880940065C4B8 /* NetworkImageOperationTests.swift */,
161 | 1D0AACEB27386FDA0065C4B8 /* NetworkImageSerializationTests.swift */,
162 | 1D0AACE627386F380065C4B8 /* NetworkImageSourceTests.m */,
163 | 1D0AACDE27386ED70065C4B8 /* NetworkJSONHandlerTests.swift */,
164 | 1D0AACFA27387F060065C4B8 /* NetworkJSONOperationTests.swift */,
165 | 1D0AACF527387EAD0065C4B8 /* NetworkSessionTests.swift */,
166 | );
167 | path = AlbumsTests;
168 | sourceTree = "";
169 | };
170 | /* End PBXGroup section */
171 |
172 | /* Begin PBXNativeTarget section */
173 | AE657E3627068BE40032C442 /* Albums */ = {
174 | isa = PBXNativeTarget;
175 | buildConfigurationList = AE657E5B27068BE70032C442 /* Build configuration list for PBXNativeTarget "Albums" */;
176 | buildPhases = (
177 | AE657E3327068BE40032C442 /* Sources */,
178 | AE657E3427068BE40032C442 /* Frameworks */,
179 | AE657E3527068BE40032C442 /* Resources */,
180 | );
181 | buildRules = (
182 | );
183 | dependencies = (
184 | );
185 | name = Albums;
186 | productName = Albums;
187 | productReference = AE657E3727068BE40032C442 /* Albums.app */;
188 | productType = "com.apple.product-type.application";
189 | };
190 | AE657E4627068BE70032C442 /* AlbumsTests */ = {
191 | isa = PBXNativeTarget;
192 | buildConfigurationList = AE657E5E27068BE70032C442 /* Build configuration list for PBXNativeTarget "AlbumsTests" */;
193 | buildPhases = (
194 | AE657E4327068BE70032C442 /* Sources */,
195 | AE657E4427068BE70032C442 /* Frameworks */,
196 | AE657E4527068BE70032C442 /* Resources */,
197 | );
198 | buildRules = (
199 | );
200 | dependencies = (
201 | AE657E4927068BE70032C442 /* PBXTargetDependency */,
202 | );
203 | name = AlbumsTests;
204 | productName = AlbumsTests;
205 | productReference = AE657E4727068BE70032C442 /* AlbumsTests.xctest */;
206 | productType = "com.apple.product-type.bundle.unit-test";
207 | };
208 | /* End PBXNativeTarget section */
209 |
210 | /* Begin PBXProject section */
211 | AE657E2F27068BE40032C442 /* Project object */ = {
212 | isa = PBXProject;
213 | attributes = {
214 | BuildIndependentTargetsInParallel = 1;
215 | LastSwiftUpdateCheck = 1300;
216 | LastUpgradeCheck = 1300;
217 | TargetAttributes = {
218 | AE657E3627068BE40032C442 = {
219 | CreatedOnToolsVersion = 13.0;
220 | LastSwiftMigration = 1310;
221 | };
222 | AE657E4627068BE70032C442 = {
223 | CreatedOnToolsVersion = 13.0;
224 | LastSwiftMigration = 1310;
225 | TestTargetID = AE657E3627068BE40032C442;
226 | };
227 | };
228 | };
229 | buildConfigurationList = AE657E3227068BE40032C442 /* Build configuration list for PBXProject "Albums" */;
230 | compatibilityVersion = "Xcode 13.0";
231 | developmentRegion = en;
232 | hasScannedForEncodings = 0;
233 | knownRegions = (
234 | en,
235 | Base,
236 | );
237 | mainGroup = AE657E2E27068BE40032C442;
238 | productRefGroup = AE657E3827068BE40032C442 /* Products */;
239 | projectDirPath = "";
240 | projectRoot = "";
241 | targets = (
242 | AE657E3627068BE40032C442 /* Albums */,
243 | AE657E4627068BE70032C442 /* AlbumsTests */,
244 | );
245 | };
246 | /* End PBXProject section */
247 |
248 | /* Begin PBXResourcesBuildPhase section */
249 | AE657E3527068BE40032C442 /* Resources */ = {
250 | isa = PBXResourcesBuildPhase;
251 | buildActionMask = 2147483647;
252 | files = (
253 | );
254 | runOnlyForDeploymentPostprocessing = 0;
255 | };
256 | AE657E4527068BE70032C442 /* Resources */ = {
257 | isa = PBXResourcesBuildPhase;
258 | buildActionMask = 2147483647;
259 | files = (
260 | 1D0AAD072738818E0065C4B8 /* Albums.json in Resources */,
261 | );
262 | runOnlyForDeploymentPostprocessing = 0;
263 | };
264 | /* End PBXResourcesBuildPhase section */
265 |
266 | /* Begin PBXSourcesBuildPhase section */
267 | AE657E3327068BE40032C442 /* Sources */ = {
268 | isa = PBXSourcesBuildPhase;
269 | buildActionMask = 2147483647;
270 | files = (
271 | 1D0AAD09273881D00065C4B8 /* AlbumsListRowModel.swift in Sources */,
272 | 1D0AAD0E2738823A0065C4B8 /* AlbumsListRowView.swift in Sources */,
273 | 1D0AACF327387EA10065C4B8 /* NetworkSession.swift in Sources */,
274 | AE657E3B27068BE40032C442 /* AlbumsApp.swift in Sources */,
275 | 1D0AACFD273880870065C4B8 /* NetworkImageOperation.swift in Sources */,
276 | 1D0AAD02273881620065C4B8 /* AlbumsListModel.swift in Sources */,
277 | 1D0AACF827387EFA0065C4B8 /* NetworkJSONOperation.swift in Sources */,
278 | 1D0AACEE273870480065C4B8 /* NetworkImageHandler.swift in Sources */,
279 | 1D0AACE427386F2B0065C4B8 /* NetworkImageSource.m in Sources */,
280 | 1D0AACD627386E3D0065C4B8 /* NetworkDataHandler.swift in Sources */,
281 | 1D0AACE927386FCA0065C4B8 /* NetworkImageSerialization.swift in Sources */,
282 | 1D0AAD10273882750065C4B8 /* AlbumsListView.swift in Sources */,
283 | 1D0AACDC27386EC90065C4B8 /* NetworkJSONHandler.swift in Sources */,
284 | );
285 | runOnlyForDeploymentPostprocessing = 0;
286 | };
287 | AE657E4327068BE70032C442 /* Sources */ = {
288 | isa = PBXSourcesBuildPhase;
289 | buildActionMask = 2147483647;
290 | files = (
291 | 1D0AAD0A273881D00065C4B8 /* AlbumsListRowModel.swift in Sources */,
292 | 1D0AACDA27386E470065C4B8 /* NetworkDataHandlerTests.swift in Sources */,
293 | 1D0AACDD27386EC90065C4B8 /* NetworkJSONHandler.swift in Sources */,
294 | 1D0AACF1273870570065C4B8 /* NetworkImageHandlerTests.swift in Sources */,
295 | 1D0AAD03273881620065C4B8 /* AlbumsListModel.swift in Sources */,
296 | AE657E4C27068BE70032C442 /* AlbumsTests.swift in Sources */,
297 | 1D0AACFE273880870065C4B8 /* NetworkImageOperation.swift in Sources */,
298 | 1D0AAD052738816F0065C4B8 /* AlbumsListModelTests.swift in Sources */,
299 | 1D0AAD00273880940065C4B8 /* NetworkImageOperationTests.swift in Sources */,
300 | 1D0AACEF273870480065C4B8 /* NetworkImageHandler.swift in Sources */,
301 | 1D0AACE527386F2B0065C4B8 /* NetworkImageSource.m in Sources */,
302 | 1D0AACEC27386FDA0065C4B8 /* NetworkImageSerializationTests.swift in Sources */,
303 | 1D0AACDF27386ED70065C4B8 /* NetworkJSONHandlerTests.swift in Sources */,
304 | 1D0AACEA27386FCA0065C4B8 /* NetworkImageSerialization.swift in Sources */,
305 | 1D0AAD0C273881DC0065C4B8 /* AlbumsListRowModelTests.swift in Sources */,
306 | 1D0AACF627387EAD0065C4B8 /* NetworkSessionTests.swift in Sources */,
307 | 1D0AACF927387EFA0065C4B8 /* NetworkJSONOperation.swift in Sources */,
308 | 1D0AACF427387EA10065C4B8 /* NetworkSession.swift in Sources */,
309 | 1D0AACD727386E3D0065C4B8 /* NetworkDataHandler.swift in Sources */,
310 | 1D0AACE727386F380065C4B8 /* NetworkImageSourceTests.m in Sources */,
311 | 1D0AACFB27387F060065C4B8 /* NetworkJSONOperationTests.swift in Sources */,
312 | );
313 | runOnlyForDeploymentPostprocessing = 0;
314 | };
315 | /* End PBXSourcesBuildPhase section */
316 |
317 | /* Begin PBXTargetDependency section */
318 | AE657E4927068BE70032C442 /* PBXTargetDependency */ = {
319 | isa = PBXTargetDependency;
320 | target = AE657E3627068BE40032C442 /* Albums */;
321 | targetProxy = AE657E4827068BE70032C442 /* PBXContainerItemProxy */;
322 | };
323 | /* End PBXTargetDependency section */
324 |
325 | /* Begin XCBuildConfiguration section */
326 | AE657E5927068BE70032C442 /* Debug */ = {
327 | isa = XCBuildConfiguration;
328 | buildSettings = {
329 | ALWAYS_SEARCH_USER_PATHS = NO;
330 | CLANG_ANALYZER_NONNULL = YES;
331 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
333 | CLANG_CXX_LIBRARY = "libc++";
334 | CLANG_ENABLE_MODULES = YES;
335 | CLANG_ENABLE_OBJC_ARC = YES;
336 | CLANG_ENABLE_OBJC_WEAK = YES;
337 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
338 | CLANG_WARN_BOOL_CONVERSION = YES;
339 | CLANG_WARN_COMMA = YES;
340 | CLANG_WARN_CONSTANT_CONVERSION = YES;
341 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
342 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
343 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
344 | CLANG_WARN_EMPTY_BODY = YES;
345 | CLANG_WARN_ENUM_CONVERSION = YES;
346 | CLANG_WARN_INFINITE_RECURSION = YES;
347 | CLANG_WARN_INT_CONVERSION = YES;
348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
349 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
350 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
351 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
352 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
354 | CLANG_WARN_STRICT_PROTOTYPES = YES;
355 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
356 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
357 | CLANG_WARN_UNREACHABLE_CODE = YES;
358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
359 | COPY_PHASE_STRIP = NO;
360 | DEBUG_INFORMATION_FORMAT = dwarf;
361 | ENABLE_STRICT_OBJC_MSGSEND = YES;
362 | ENABLE_TESTABILITY = YES;
363 | GCC_C_LANGUAGE_STANDARD = gnu11;
364 | GCC_DYNAMIC_NO_PIC = NO;
365 | GCC_NO_COMMON_BLOCKS = YES;
366 | GCC_OPTIMIZATION_LEVEL = 0;
367 | GCC_PREPROCESSOR_DEFINITIONS = (
368 | "DEBUG=1",
369 | "$(inherited)",
370 | );
371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
373 | GCC_WARN_UNDECLARED_SELECTOR = YES;
374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
375 | GCC_WARN_UNUSED_FUNCTION = YES;
376 | GCC_WARN_UNUSED_VARIABLE = YES;
377 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
378 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
379 | MTL_FAST_MATH = YES;
380 | ONLY_ACTIVE_ARCH = YES;
381 | SDKROOT = iphoneos;
382 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
383 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
384 | };
385 | name = Debug;
386 | };
387 | AE657E5A27068BE70032C442 /* Release */ = {
388 | isa = XCBuildConfiguration;
389 | buildSettings = {
390 | ALWAYS_SEARCH_USER_PATHS = NO;
391 | CLANG_ANALYZER_NONNULL = YES;
392 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
394 | CLANG_CXX_LIBRARY = "libc++";
395 | CLANG_ENABLE_MODULES = YES;
396 | CLANG_ENABLE_OBJC_ARC = YES;
397 | CLANG_ENABLE_OBJC_WEAK = YES;
398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
399 | CLANG_WARN_BOOL_CONVERSION = YES;
400 | CLANG_WARN_COMMA = YES;
401 | CLANG_WARN_CONSTANT_CONVERSION = YES;
402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
404 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
405 | CLANG_WARN_EMPTY_BODY = YES;
406 | CLANG_WARN_ENUM_CONVERSION = YES;
407 | CLANG_WARN_INFINITE_RECURSION = YES;
408 | CLANG_WARN_INT_CONVERSION = YES;
409 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
410 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
411 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
413 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
415 | CLANG_WARN_STRICT_PROTOTYPES = YES;
416 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
417 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
418 | CLANG_WARN_UNREACHABLE_CODE = YES;
419 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
420 | COPY_PHASE_STRIP = NO;
421 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
422 | ENABLE_NS_ASSERTIONS = NO;
423 | ENABLE_STRICT_OBJC_MSGSEND = YES;
424 | GCC_C_LANGUAGE_STANDARD = gnu11;
425 | GCC_NO_COMMON_BLOCKS = YES;
426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
428 | GCC_WARN_UNDECLARED_SELECTOR = YES;
429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
430 | GCC_WARN_UNUSED_FUNCTION = YES;
431 | GCC_WARN_UNUSED_VARIABLE = YES;
432 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
433 | MTL_ENABLE_DEBUG_INFO = NO;
434 | MTL_FAST_MATH = YES;
435 | SDKROOT = iphoneos;
436 | SWIFT_COMPILATION_MODE = wholemodule;
437 | SWIFT_OPTIMIZATION_LEVEL = "-O";
438 | VALIDATE_PRODUCT = YES;
439 | };
440 | name = Release;
441 | };
442 | AE657E5C27068BE70032C442 /* Debug */ = {
443 | isa = XCBuildConfiguration;
444 | buildSettings = {
445 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
446 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
447 | CLANG_ENABLE_MODULES = YES;
448 | CODE_SIGN_STYLE = Automatic;
449 | CURRENT_PROJECT_VERSION = 1;
450 | DEVELOPMENT_ASSET_PATHS = "";
451 | ENABLE_PREVIEWS = YES;
452 | GENERATE_INFOPLIST_FILE = YES;
453 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
454 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
455 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
456 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
457 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
458 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
459 | LD_RUNPATH_SEARCH_PATHS = (
460 | "$(inherited)",
461 | "@executable_path/Frameworks",
462 | );
463 | MARKETING_VERSION = 1.0;
464 | PRODUCT_BUNDLE_IDENTIFIER = com.northbronson.Albums;
465 | PRODUCT_NAME = "$(TARGET_NAME)";
466 | SWIFT_EMIT_LOC_STRINGS = YES;
467 | SWIFT_OBJC_BRIDGING_HEADER = "Albums/Albums-Bridging-Header.h";
468 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
469 | SWIFT_VERSION = 5.0;
470 | TARGETED_DEVICE_FAMILY = "1,2";
471 | };
472 | name = Debug;
473 | };
474 | AE657E5D27068BE70032C442 /* Release */ = {
475 | isa = XCBuildConfiguration;
476 | buildSettings = {
477 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
478 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
479 | CLANG_ENABLE_MODULES = YES;
480 | CODE_SIGN_STYLE = Automatic;
481 | CURRENT_PROJECT_VERSION = 1;
482 | DEVELOPMENT_ASSET_PATHS = "";
483 | ENABLE_PREVIEWS = YES;
484 | GENERATE_INFOPLIST_FILE = YES;
485 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
486 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
487 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
488 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
489 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
490 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
491 | LD_RUNPATH_SEARCH_PATHS = (
492 | "$(inherited)",
493 | "@executable_path/Frameworks",
494 | );
495 | MARKETING_VERSION = 1.0;
496 | PRODUCT_BUNDLE_IDENTIFIER = com.northbronson.Albums;
497 | PRODUCT_NAME = "$(TARGET_NAME)";
498 | SWIFT_EMIT_LOC_STRINGS = YES;
499 | SWIFT_OBJC_BRIDGING_HEADER = "Albums/Albums-Bridging-Header.h";
500 | SWIFT_VERSION = 5.0;
501 | TARGETED_DEVICE_FAMILY = "1,2";
502 | };
503 | name = Release;
504 | };
505 | AE657E5F27068BE70032C442 /* Debug */ = {
506 | isa = XCBuildConfiguration;
507 | buildSettings = {
508 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
509 | BUNDLE_LOADER = "$(TEST_HOST)";
510 | CLANG_ENABLE_MODULES = YES;
511 | CODE_SIGN_STYLE = Automatic;
512 | CURRENT_PROJECT_VERSION = 1;
513 | GENERATE_INFOPLIST_FILE = YES;
514 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
515 | LD_RUNPATH_SEARCH_PATHS = (
516 | "$(inherited)",
517 | "@executable_path/Frameworks",
518 | "@loader_path/Frameworks",
519 | );
520 | MARKETING_VERSION = 1.0;
521 | PRODUCT_BUNDLE_IDENTIFIER = com.northbronson.AlbumsTests;
522 | PRODUCT_NAME = "$(TARGET_NAME)";
523 | SWIFT_EMIT_LOC_STRINGS = NO;
524 | SWIFT_OBJC_BRIDGING_HEADER = "Albums/Albums-Bridging-Header.h";
525 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
526 | SWIFT_VERSION = 5.0;
527 | TARGETED_DEVICE_FAMILY = "1,2";
528 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Albums.app/Albums";
529 | };
530 | name = Debug;
531 | };
532 | AE657E6027068BE70032C442 /* Release */ = {
533 | isa = XCBuildConfiguration;
534 | buildSettings = {
535 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
536 | BUNDLE_LOADER = "$(TEST_HOST)";
537 | CLANG_ENABLE_MODULES = YES;
538 | CODE_SIGN_STYLE = Automatic;
539 | CURRENT_PROJECT_VERSION = 1;
540 | GENERATE_INFOPLIST_FILE = YES;
541 | IPHONEOS_DEPLOYMENT_TARGET = 15.0;
542 | LD_RUNPATH_SEARCH_PATHS = (
543 | "$(inherited)",
544 | "@executable_path/Frameworks",
545 | "@loader_path/Frameworks",
546 | );
547 | MARKETING_VERSION = 1.0;
548 | PRODUCT_BUNDLE_IDENTIFIER = com.northbronson.AlbumsTests;
549 | PRODUCT_NAME = "$(TARGET_NAME)";
550 | SWIFT_EMIT_LOC_STRINGS = NO;
551 | SWIFT_OBJC_BRIDGING_HEADER = "Albums/Albums-Bridging-Header.h";
552 | SWIFT_VERSION = 5.0;
553 | TARGETED_DEVICE_FAMILY = "1,2";
554 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Albums.app/Albums";
555 | };
556 | name = Release;
557 | };
558 | /* End XCBuildConfiguration section */
559 |
560 | /* Begin XCConfigurationList section */
561 | AE657E3227068BE40032C442 /* Build configuration list for PBXProject "Albums" */ = {
562 | isa = XCConfigurationList;
563 | buildConfigurations = (
564 | AE657E5927068BE70032C442 /* Debug */,
565 | AE657E5A27068BE70032C442 /* Release */,
566 | );
567 | defaultConfigurationIsVisible = 0;
568 | defaultConfigurationName = Release;
569 | };
570 | AE657E5B27068BE70032C442 /* Build configuration list for PBXNativeTarget "Albums" */ = {
571 | isa = XCConfigurationList;
572 | buildConfigurations = (
573 | AE657E5C27068BE70032C442 /* Debug */,
574 | AE657E5D27068BE70032C442 /* Release */,
575 | );
576 | defaultConfigurationIsVisible = 0;
577 | defaultConfigurationName = Release;
578 | };
579 | AE657E5E27068BE70032C442 /* Build configuration list for PBXNativeTarget "AlbumsTests" */ = {
580 | isa = XCConfigurationList;
581 | buildConfigurations = (
582 | AE657E5F27068BE70032C442 /* Debug */,
583 | AE657E6027068BE70032C442 /* Release */,
584 | );
585 | defaultConfigurationIsVisible = 0;
586 | defaultConfigurationName = Release;
587 | };
588 | /* End XCConfigurationList section */
589 | };
590 | rootObject = AE657E2F27068BE40032C442 /* Project object */;
591 | }
592 |
--------------------------------------------------------------------------------