From 2ec10a505a530bd61bfa606f1e0807d33903af5c Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Mon, 17 Mar 2025 04:15:31 +0300 Subject: [PATCH 1/9] Add end-to-end API tests in a separate test target --- .../EssentialFeed.xcodeproj/project.pbxproj | 121 +++++++++++++++++- .../EssentialFeed/Feed Feature/FeedItem.swift | 12 ++ .../EssentialFeedAPIEndToEndTests.swift | 99 ++++++++++++++ 3 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift diff --git a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj index 200b79a..1eef998 100644 --- a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj +++ b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 55; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -15,6 +15,7 @@ AF9074A52D1D7AA70083835F /* RemoteFeedLoaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9074A42D1D7AA70083835F /* RemoteFeedLoaderTests.swift */; }; AFAE6E0E2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFAE6E0D2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift */; }; AFCCD9602D87A3EC003221BE /* URLSessionHTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCCD95F2D87A3EC003221BE /* URLSessionHTTPClient.swift */; }; + AFCCD9692D87A9BC003221BE /* EssentialFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 080EDEF121B6DA7E00813479 /* EssentialFeed.framework */; }; AFFE11122D2358ED00B44DEB /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFFE11112D2358ED00B44DEB /* HTTPClient.swift */; }; AFFE11142D2359EE00B44DEB /* FeedItemsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFFE11132D2359EE00B44DEB /* FeedItemsMapper.swift */; }; /* End PBXBuildFile section */ @@ -27,6 +28,13 @@ remoteGlobalIDString = 080EDEF021B6DA7E00813479; remoteInfo = EssentialFeed; }; + AFCCD96A2D87A9BC003221BE /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 080EDEE821B6DA7E00813479 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 080EDEF021B6DA7E00813479; + remoteInfo = EssentialFeed; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -42,10 +50,15 @@ AF9074A42D1D7AA70083835F /* RemoteFeedLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteFeedLoaderTests.swift; sourceTree = ""; }; AFAE6E0D2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+MemoryLeakTracking.swift"; sourceTree = ""; }; AFCCD95F2D87A3EC003221BE /* URLSessionHTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionHTTPClient.swift; sourceTree = ""; }; + AFCCD9652D87A9BC003221BE /* EssentialFeedAPIEndToEndTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EssentialFeedAPIEndToEndTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AFFE11112D2358ED00B44DEB /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; AFFE11132D2359EE00B44DEB /* FeedItemsMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedItemsMapper.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + AFCCD9662D87A9BC003221BE /* EssentialFeedAPIEndToEndTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = EssentialFeedAPIEndToEndTests; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 080EDEEE21B6DA7E00813479 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -62,6 +75,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AFCCD9622D87A9BC003221BE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AFCCD9692D87A9BC003221BE /* EssentialFeed.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -70,6 +91,7 @@ children = ( 080EDEF321B6DA7E00813479 /* EssentialFeed */, 080EDEFE21B6DA7E00813479 /* EssentialFeedTests */, + AFCCD9662D87A9BC003221BE /* EssentialFeedAPIEndToEndTests */, 080EDEF221B6DA7E00813479 /* Products */, ); sourceTree = ""; @@ -79,6 +101,7 @@ children = ( 080EDEF121B6DA7E00813479 /* EssentialFeed.framework */, 080EDEFA21B6DA7E00813479 /* EssentialFeedTests.xctest */, + AFCCD9652D87A9BC003221BE /* EssentialFeedAPIEndToEndTests.xctest */, ); name = Products; sourceTree = ""; @@ -190,6 +213,29 @@ productReference = 080EDEFA21B6DA7E00813479 /* EssentialFeedTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + AFCCD9642D87A9BC003221BE /* EssentialFeedAPIEndToEndTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = AFCCD96E2D87A9BC003221BE /* Build configuration list for PBXNativeTarget "EssentialFeedAPIEndToEndTests" */; + buildPhases = ( + AFCCD9612D87A9BC003221BE /* Sources */, + AFCCD9622D87A9BC003221BE /* Frameworks */, + AFCCD9632D87A9BC003221BE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + AFCCD96B2D87A9BC003221BE /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + AFCCD9662D87A9BC003221BE /* EssentialFeedAPIEndToEndTests */, + ); + name = EssentialFeedAPIEndToEndTests; + packageProductDependencies = ( + ); + productName = EssentialFeedAPIEndToEndTests; + productReference = AFCCD9652D87A9BC003221BE /* EssentialFeedAPIEndToEndTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -197,7 +243,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1010; + LastSwiftUpdateCheck = 1620; LastUpgradeCheck = 1600; ORGANIZATIONNAME = ""; TargetAttributes = { @@ -209,6 +255,9 @@ CreatedOnToolsVersion = 10.1; LastSwiftMigration = 1620; }; + AFCCD9642D87A9BC003221BE = { + CreatedOnToolsVersion = 16.2; + }; }; }; buildConfigurationList = 080EDEEB21B6DA7E00813479 /* Build configuration list for PBXProject "EssentialFeed" */; @@ -226,6 +275,7 @@ targets = ( 080EDEF021B6DA7E00813479 /* EssentialFeed */, 080EDEF921B6DA7E00813479 /* EssentialFeedTests */, + AFCCD9642D87A9BC003221BE /* EssentialFeedAPIEndToEndTests */, ); }; /* End PBXProject section */ @@ -245,6 +295,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AFCCD9632D87A9BC003221BE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -271,6 +328,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + AFCCD9612D87A9BC003221BE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -279,6 +343,11 @@ target = 080EDEF021B6DA7E00813479 /* EssentialFeed */; targetProxy = 080EDEFC21B6DA7E00813479 /* PBXContainerItemProxy */; }; + AFCCD96B2D87A9BC003221BE /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 080EDEF021B6DA7E00813479 /* EssentialFeed */; + targetProxy = AFCCD96A2D87A9BC003221BE /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -519,6 +588,45 @@ }; name = Release; }; + AFCCD96C2D87A9BC003221BE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KM569NM77P; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.Obada.EssentialFeedAPIEndToEndTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + AFCCD96D2D87A9BC003221BE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = KM569NM77P; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 15.2; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.Obada.EssentialFeedAPIEndToEndTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -549,6 +657,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + AFCCD96E2D87A9BC003221BE /* Build configuration list for PBXNativeTarget "EssentialFeedAPIEndToEndTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + AFCCD96C2D87A9BC003221BE /* Debug */, + AFCCD96D2D87A9BC003221BE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ }; rootObject = 080EDEE821B6DA7E00813479 /* Project object */; diff --git a/EssentialFeed/EssentialFeed/Feed Feature/FeedItem.swift b/EssentialFeed/EssentialFeed/Feed Feature/FeedItem.swift index daa0b5e..c7ba368 100644 --- a/EssentialFeed/EssentialFeed/Feed Feature/FeedItem.swift +++ b/EssentialFeed/EssentialFeed/Feed Feature/FeedItem.swift @@ -9,4 +9,16 @@ public struct FeedItem: Equatable { let description: String? let location: String? let imageURL: URL + + public init( + id: UUID, + description: String?, + location: String?, + imageURL: URL + ) { + self.id = id + self.description = description + self.location = location + self.imageURL = imageURL + } } diff --git a/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift b/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift new file mode 100644 index 0000000..77050d9 --- /dev/null +++ b/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift @@ -0,0 +1,99 @@ +// +// EssentialFeedAPIEndToEndTests.swift +// EssentialFeedAPIEndToEndTests +// +// Created by Abdelrahman Mohamed on 17.03.2025. +// + +import XCTest +import EssentialFeed + +class EssentialFeedAPIEndToEndTests: XCTestCase { + + func test_endToEndTestServerGETFeedResult_matchesFixedTestAccountData() { + let testServerURL = URL(string: "https://essentialdeveloper.com/feed-case-study/test-api/feed")! + let client = URLSessionHTTPClient() + let loader = RemoteFeedLoader(url: testServerURL, client: client) + + let exp = expectation(description: "Waiting for load completion") + + var receivedResult: LoadFeedResult? + loader.load { result in + receivedResult = result + exp.fulfill() + } + + wait(for: [exp], timeout: 5.0) + + switch receivedResult { + case .success(let items): + XCTAssertEqual(items.count, 8, "Expected 8 items in the test account feed") + XCTAssertEqual(items[0], expectedItem(at: 0)) + XCTAssertEqual(items[1], expectedItem(at: 1)) + XCTAssertEqual(items[2], expectedItem(at: 2)) + XCTAssertEqual(items[3], expectedItem(at: 3)) + XCTAssertEqual(items[4], expectedItem(at: 4)) + XCTAssertEqual(items[5], expectedItem(at: 5)) + XCTAssertEqual(items[6], expectedItem(at: 6)) + XCTAssertEqual(items[7], expectedItem(at: 7)) + case .failure(let error): + XCTFail("Expected successful feed result, got \(error) instead") + case nil: + XCTFail("Expected successful feed result, got no result instead") + } + } + + // MARK: - Helpers + + private func expectedItem(at index: Int) -> FeedItem { + FeedItem( + id: id(at: index), + description: description(at: index), + location: location(at: index), + imageURL: imageURL(at: index) + ) + } + + private func id(at index: Int) -> UUID { + return UUID(uuidString: [ + "73A7F70C-75DA-4C2E-B5A3-EED40DC53AA6", + "BA298A85-6275-48D3-8315-9C8F7C1CD109", + "5A0D45B3-8E26-4385-8C5D-213E160A5E3C", + "FF0ECFE2-2879-403F-8DBE-A83B4010B340", + "DC97EF5E-2CC9-4905-A8AD-3C351C311001", + "557D87F1-25D3-4D77-82E9-364B2ED9CB30", + "A83284EF-C2DF-415D-AB73-2A9B8B04950B", + "F79BD7F8-063F-46E2-8147-A67635C3BB01" + ][index])! + } + + private func description(at index: Int) -> String? { + return [ + "Description 1", + nil, + "Description 3", + nil, + "Description 5", + "Description 6", + "Description 7", + "Description 8" + ][index] + } + + private func location(at index: Int) -> String? { + return [ + "Location 1", + "Location 2", + nil, + nil, + "Location 5", + "Location 6", + "Location 7", + "Location 8" + ][index] + } + + private func imageURL(at index: Int) -> URL { + return URL(string: "https://url-\(index+1).com")! + } +} From fa166ed2f26d995e9880f1f28242804682eb6334 Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Mon, 17 Mar 2025 04:18:09 +0300 Subject: [PATCH 2/9] Extract test setup to a helper method --- .../EssentialFeedAPIEndToEndTests.swift | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift b/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift index 77050d9..1d4b5ba 100644 --- a/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift +++ b/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift @@ -11,21 +11,7 @@ import EssentialFeed class EssentialFeedAPIEndToEndTests: XCTestCase { func test_endToEndTestServerGETFeedResult_matchesFixedTestAccountData() { - let testServerURL = URL(string: "https://essentialdeveloper.com/feed-case-study/test-api/feed")! - let client = URLSessionHTTPClient() - let loader = RemoteFeedLoader(url: testServerURL, client: client) - - let exp = expectation(description: "Waiting for load completion") - - var receivedResult: LoadFeedResult? - loader.load { result in - receivedResult = result - exp.fulfill() - } - - wait(for: [exp], timeout: 5.0) - - switch receivedResult { + switch getFeedResult() { case .success(let items): XCTAssertEqual(items.count, 8, "Expected 8 items in the test account feed") XCTAssertEqual(items[0], expectedItem(at: 0)) @@ -45,6 +31,24 @@ class EssentialFeedAPIEndToEndTests: XCTestCase { // MARK: - Helpers + private func getFeedResult() -> LoadFeedResult? { + let testServerURL = URL(string: "https://essentialdeveloper.com/feed-case-study/test-api/feed")! + let client = URLSessionHTTPClient() + let loader = RemoteFeedLoader(url: testServerURL, client: client) + + let exp = expectation(description: "Waiting for load completion") + + var receivedResult: LoadFeedResult? + loader.load { result in + receivedResult = result + exp.fulfill() + } + + wait(for: [exp], timeout: 5.0) + + return receivedResult + } + private func expectedItem(at index: Int) -> FeedItem { FeedItem( id: id(at: index), From 781634792ff429635fd58cf5709019f908a362b8 Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Mon, 17 Mar 2025 04:21:17 +0300 Subject: [PATCH 3/9] Add memory leak tracking for components in integration --- EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj | 2 ++ .../EssentialFeedAPIEndToEndTests.swift | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj index 1eef998..d9100b3 100644 --- a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj +++ b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ AFAE6E0E2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFAE6E0D2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift */; }; AFCCD9602D87A3EC003221BE /* URLSessionHTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFCCD95F2D87A3EC003221BE /* URLSessionHTTPClient.swift */; }; AFCCD9692D87A9BC003221BE /* EssentialFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 080EDEF121B6DA7E00813479 /* EssentialFeed.framework */; }; + AFCCD96F2D87B019003221BE /* XCTestCase+MemoryLeakTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFAE6E0D2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift */; }; AFFE11122D2358ED00B44DEB /* HTTPClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFFE11112D2358ED00B44DEB /* HTTPClient.swift */; }; AFFE11142D2359EE00B44DEB /* FeedItemsMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFFE11132D2359EE00B44DEB /* FeedItemsMapper.swift */; }; /* End PBXBuildFile section */ @@ -332,6 +333,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + AFCCD96F2D87B019003221BE /* XCTestCase+MemoryLeakTracking.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift b/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift index 1d4b5ba..a50b5fa 100644 --- a/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift +++ b/EssentialFeed/EssentialFeedAPIEndToEndTests/EssentialFeedAPIEndToEndTests.swift @@ -31,10 +31,15 @@ class EssentialFeedAPIEndToEndTests: XCTestCase { // MARK: - Helpers - private func getFeedResult() -> LoadFeedResult? { + private func getFeedResult( + file: StaticString = #filePath, + line: UInt = #line + ) -> LoadFeedResult? { let testServerURL = URL(string: "https://essentialdeveloper.com/feed-case-study/test-api/feed")! let client = URLSessionHTTPClient() let loader = RemoteFeedLoader(url: testServerURL, client: client) + trackForMemoryLeaks(client, file: file, line: line) + trackForMemoryLeaks(loader, file: file, line: line) let exp = expectation(description: "Waiting for load completion") From 6029e6b5c5b91da7100cf6480bce695623e6899a Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Fri, 30 May 2025 22:40:48 +0300 Subject: [PATCH 4/9] Add CI scheme --- .DS_Store | Bin 6148 -> 6148 bytes EssentialFeed/.DS_Store | Bin 12292 -> 14340 bytes EssentialFeed/CI.xctestplan | 36 ++++++++++++ .../EssentialFeedAPIEndToEndTests.xcscheme | 55 ++++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 EssentialFeed/CI.xctestplan create mode 100644 EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/EssentialFeedAPIEndToEndTests.xcscheme diff --git a/.DS_Store b/.DS_Store index 64fd04bd7dcf38daab5d9b290da2c64d6d9ed246..b6735d0197f700e0ad29f73ed947611ac6e3245a 100644 GIT binary patch delta 360 zcmZoMXfc=|#>B)qu~2NHo+2a5!~pA!7aACWj2<}|&z$_^q@4UD1_lNJAa(>|z5igq zzyOr5Pbx1ifXeSqD#*z!E-^5;#>m9X!pg?Z!Op?W5gVM5UmjeNSW;T-lvorE;)Uer z=On?{iAiCZspatkBF_1FC5f4NsYPH7nJKA2B{AWdc`5njPWh#IDaByD!4L@!P7cm^ z0qJUwYF$GkGfN!>LnFgl9ffK`BU1w%1q)My+FDKyQDuGWp!n>Z+`Rl=pu2&9kr6^O z@Iq-A)i<$6SC^9^h`|-;MHdENAg#xc%aDTR{q;cgQ15R%*u=7#or9kP7;+mAerKM{ ZFQO{~G7e-#1B3?KwmC#(1M|cN767;`Vcq}$ delta 76 zcmZoMXfc=|#>CJzu~2NHo+2aT!~knX#>qTPa+5uo`8Q8tE@s)hfcZAlW_AvK4xqBl f6Pdp=Pv#fV7S6$B@Dh!jKPVr7{#VlrR)~=Hw?Q<>V)U3m51Kp;_P{EK4bPL2q z1q?Y1i41vY?&_XwrJ%K0OJOoQGs}!;lMl$-aX8oAJ|_S5nB&9=$ZVs@b^^T2tPBj3 z7m23=ZMwxK3FLr)0XL9v1%(}uZf9Un;Mpw5_?~$(zl<*r6C`LYK2~lb-~VEn^O?Ki7B6 zaqB3X|E9)xaGFcGPACMh->*qUkZn diff --git a/EssentialFeed/CI.xctestplan b/EssentialFeed/CI.xctestplan new file mode 100644 index 0000000..90b5b3d --- /dev/null +++ b/EssentialFeed/CI.xctestplan @@ -0,0 +1,36 @@ +{ + "configurations" : [ + { + "id" : "EE53C984-12B0-4CA1-863A-8694A6FF63F8", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:EssentialFeed.xcodeproj", + "identifier" : "080EDEF921B6DA7E00813479", + "name" : "EssentialFeedTests" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:EssentialFeed.xcodeproj", + "identifier" : "080EDEF921B6DA7E00813479", + "name" : "EssentialFeedTests" + } + }, + { + "target" : { + "containerPath" : "container:EssentialFeed.xcodeproj", + "identifier" : "AFCCD9642D87A9BC003221BE", + "name" : "EssentialFeedAPIEndToEndTests" + } + } + ], + "version" : 1 +} diff --git a/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/EssentialFeedAPIEndToEndTests.xcscheme b/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/EssentialFeedAPIEndToEndTests.xcscheme new file mode 100644 index 0000000..a6f4ed0 --- /dev/null +++ b/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/EssentialFeedAPIEndToEndTests.xcscheme @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + From 14e1763d664e913b98aa240030206caf621383d5 Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Sat, 31 May 2025 23:29:11 +0300 Subject: [PATCH 5/9] Create swift.yml --- .github/workflows/swift.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/swift.yml diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml new file mode 100644 index 0000000..4637494 --- /dev/null +++ b/.github/workflows/swift.yml @@ -0,0 +1,32 @@ +name: CI + +# Controls when the action will run. +# Triggers the workflow on pull request events but only for the master branch. +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build-and-test" + build-and-test: + # The type of runner that the job will run on + runs-on: macos-15-xlarge + + timeout-minutes: 8 + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v4 + + - name: Select Xcode + run: sudo xcode-select -switch /Applications/Xcode_16.2.app + + - name: Xcode version + run: /usr/bin/xcodebuild -version + + - name: Build and Test + run: xcodebuild clean build test -workspace EssentialApp/EssentialApp.xcworkspace -scheme "CI" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" ONLY_ACTIVE_ARCH=YES From 90ec75de6f3dd9eab4342a13525e8be99afa2fe4 Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Sat, 31 May 2025 23:31:44 +0300 Subject: [PATCH 6/9] chore: add `.DS_Store` --- .DS_Store | Bin 6148 -> 6148 bytes EssentialFeed/.DS_Store | Bin 14340 -> 14340 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/.DS_Store b/.DS_Store index b6735d0197f700e0ad29f73ed947611ac6e3245a..d1d182ef448c548d1b96a38aa662e686eabdd64f 100644 GIT binary patch delta 175 zcmZoMXfc@JFUroqz`)4BAi&_6lb@WFlb;0S3v8aqyqvKfB*ns@$B@pD$xwnU4b&kE zl+*hU20#{)y0YM+yqx^Jbf6f}SOzYbexMoY40#Os3`Gp7Xl8W-)ghb3%izjT%uqZz RfJtPt7E>ncW_FIh`~VZMD;NL( delta 48 zcmZoMXfc@JFUrEez`)4BAi%(o$56tM$&kp9!{D}=k!3j}Bg^Inrgqke4JDh|IsWnk E0PCF#NdN!< diff --git a/EssentialFeed/.DS_Store b/EssentialFeed/.DS_Store index 7ebae55b4b0c292ea110671f397b35d23ca434e9..56fb3113935470f77387dcd419bf9c457a15e3e0 100644 GIT binary patch delta 30 mcmZoEXerq6Q=ReUWCsnC&3YQA`MF;*FffQSxH1$o6axUea0%-G delta 30 mcmZoEXerq6Q=ReIWCsnC&3YQA`MIAlFffQSxH1$o6axUe4GHA{ From f1b781ccbb6d7d5949ad59944d4220deb185f1e7 Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Fri, 27 Jun 2025 06:40:57 +0300 Subject: [PATCH 7/9] chore: update `.DS_Store` --- .DS_Store | Bin 6148 -> 6148 bytes EssentialFeed/.DS_Store | Bin 14340 -> 14340 bytes EssentialFeed/CI.xctestplan | 36 ------------------ .../EssentialFeed.xcodeproj/project.pbxproj | 2 +- 4 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 EssentialFeed/CI.xctestplan diff --git a/.DS_Store b/.DS_Store index d1d182ef448c548d1b96a38aa662e686eabdd64f..05e7a6e50ab86086bc88a932239bf5ddf0fbcdab 100644 GIT binary patch delta 131 zcmZoMXfc=|#>B!ku~2NHo+2a1#(>?7iv?Ji7&#{MFqurg!E|b}Inx@(9h09h6{<^A zSDP4^=qQ+27}x43R9hMu0NKW7wY8iaqRRT#LGjr+xq10rlk1t~8M`MpFv)G+%Us2{ hnVo~518Bu&Mwai)lles)IT#rjm>7UybA-qmW&nQ$BAWmJ delta 75 zcmZoMXfc=|#>B)qu~2NHo+2ar#(>?7jO>$nSWG4hvo4z>%o|EnoFtgeb)eQj)lYk%|vt%G-2$O#mB(qi+RR^;NF!BVmGbIZHsWlnQ delta 124 zcmZoEXepTRWU`im;$|VnuWXYQ7$*BBQ~=ed}7(WUxk|)00;0OTmS$7 diff --git a/EssentialFeed/CI.xctestplan b/EssentialFeed/CI.xctestplan deleted file mode 100644 index 90b5b3d..0000000 --- a/EssentialFeed/CI.xctestplan +++ /dev/null @@ -1,36 +0,0 @@ -{ - "configurations" : [ - { - "id" : "EE53C984-12B0-4CA1-863A-8694A6FF63F8", - "name" : "Test Scheme Action", - "options" : { - - } - } - ], - "defaultOptions" : { - "codeCoverage" : false, - "targetForVariableExpansion" : { - "containerPath" : "container:EssentialFeed.xcodeproj", - "identifier" : "080EDEF921B6DA7E00813479", - "name" : "EssentialFeedTests" - } - }, - "testTargets" : [ - { - "target" : { - "containerPath" : "container:EssentialFeed.xcodeproj", - "identifier" : "080EDEF921B6DA7E00813479", - "name" : "EssentialFeedTests" - } - }, - { - "target" : { - "containerPath" : "container:EssentialFeed.xcodeproj", - "identifier" : "AFCCD9642D87A9BC003221BE", - "name" : "EssentialFeedAPIEndToEndTests" - } - } - ], - "version" : 1 -} diff --git a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj index d9100b3..87b527d 100644 --- a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj +++ b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj @@ -244,7 +244,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1620; + LastSwiftUpdateCheck = 1640; LastUpgradeCheck = 1600; ORGANIZATIONNAME = ""; TargetAttributes = { From 07a9d36eceaf3b2bcf4202ac5813e63a7ade2ddf Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Tue, 29 Jul 2025 22:56:46 +0300 Subject: [PATCH 8/9] Add CI scheme --- EssentialFeed/CI.xctestplan | 39 ++++++++++ .../EssentialFeed.xcodeproj/project.pbxproj | 2 + .../xcshareddata/xcschemes/CI.xcscheme | 72 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 EssentialFeed/CI.xctestplan create mode 100644 EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI.xcscheme diff --git a/EssentialFeed/CI.xctestplan b/EssentialFeed/CI.xctestplan new file mode 100644 index 0000000..5e6be2c --- /dev/null +++ b/EssentialFeed/CI.xctestplan @@ -0,0 +1,39 @@ +{ + "configurations" : [ + { + "id" : "AC0E7322-42B7-4363-8F3B-6F336365CFB3", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : { + "targets" : [ + { + "containerPath" : "container:EssentialFeed.xcodeproj", + "identifier" : "080EDEF021B6DA7E00813479", + "name" : "EssentialFeed" + } + ] + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:EssentialFeed.xcodeproj", + "identifier" : "AFCCD9642D87A9BC003221BE", + "name" : "EssentialFeedAPIEndToEndTests" + } + }, + { + "target" : { + "containerPath" : "container:EssentialFeed.xcodeproj", + "identifier" : "080EDEF921B6DA7E00813479", + "name" : "EssentialFeedTests" + } + } + ], + "version" : 1 +} diff --git a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj index 87b527d..715fc07 100644 --- a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj +++ b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ AF9074A32D1D7AA70083835F /* EssentialFeedTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "EssentialFeedTests-Bridging-Header.h"; sourceTree = ""; }; AF9074A42D1D7AA70083835F /* RemoteFeedLoaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteFeedLoaderTests.swift; sourceTree = ""; }; AFAE6E0D2D862C2100AE2724 /* XCTestCase+MemoryLeakTracking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+MemoryLeakTracking.swift"; sourceTree = ""; }; + AFB964AD2E395E750098D10F /* CI.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = CI.xctestplan; sourceTree = ""; }; AFCCD95F2D87A3EC003221BE /* URLSessionHTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionHTTPClient.swift; sourceTree = ""; }; AFCCD9652D87A9BC003221BE /* EssentialFeedAPIEndToEndTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EssentialFeedAPIEndToEndTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; AFFE11112D2358ED00B44DEB /* HTTPClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPClient.swift; sourceTree = ""; }; @@ -90,6 +91,7 @@ 080EDEE721B6DA7E00813479 = { isa = PBXGroup; children = ( + AFB964AD2E395E750098D10F /* CI.xctestplan */, 080EDEF321B6DA7E00813479 /* EssentialFeed */, 080EDEFE21B6DA7E00813479 /* EssentialFeedTests */, AFCCD9662D87A9BC003221BE /* EssentialFeedAPIEndToEndTests */, diff --git a/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI.xcscheme b/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI.xcscheme new file mode 100644 index 0000000..3498fab --- /dev/null +++ b/EssentialFeed/EssentialFeed.xcodeproj/xcshareddata/xcschemes/CI.xcscheme @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 0e33bfe4c3661c3fc84bc7d2f70c7c259d804eb9 Mon Sep 17 00:00:00 2001 From: Abdelrahman Mohamed Date: Wed, 30 Jul 2025 00:01:21 +0300 Subject: [PATCH 9/9] Migrate CI to GitHub actions --- .DS_Store | Bin 6148 -> 6148 bytes .github/.DS_Store | Bin 0 -> 6148 bytes .github/workflows/.DS_Store | Bin 0 -> 6148 bytes .../{swift.yml => .github/workflows/CI.yml} | 10 +++++----- 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 .github/.DS_Store create mode 100644 .github/workflows/.DS_Store rename .github/workflows/{swift.yml => .github/workflows/CI.yml} (65%) diff --git a/.DS_Store b/.DS_Store index 05e7a6e50ab86086bc88a932239bf5ddf0fbcdab..aaf8bb2a19e5e0155c59edf049a9957caa1a2e4b 100644 GIT binary patch delta 184 zcmZoMXfc@J&&a(oU^g=(_hudzF-A{z20ey!hD?SMh75*Mh9u9N{N$vZ{3Hej1_1^J z#-Bi1?>`s-Squzls*}o#3!tiZP4;0aXjL_K=MYYK)uNIsWnk0JPg1Q~&?~ diff --git a/.github/.DS_Store b/.github/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..35d3be871e61c0873837abaab311558b0e02d260 GIT binary patch literal 6148 zcmeHK%}T>S5T32oCKRCu1@W}twQ5`N4==Im!K)EHsMN+*4UO5-wB}F>c>sMOAH>(u zncb}@tw%*=24=s>?tIDS+pwDf08t;bssJSbaL@^JE;dt)#>povXM1K4g}%oPM4%vr zN<#2fTQ$j?4hr{YQtzMBS@33WjqJnC`|g5%8SU& z+5nHhU literal 0 HcmV?d00001 diff --git a/.github/workflows/.DS_Store b/.github/workflows/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0