diff --git a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj index 345f5e7..cdcd5ab 100644 --- a/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj +++ b/EssentialFeed/EssentialFeed.xcodeproj/project.pbxproj @@ -50,7 +50,7 @@ EDF8FE4E2AE2389D00605C5C /* FeedCacheTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF8FE4D2AE2389D00605C5C /* FeedCacheTestHelpers.swift */; }; EDF8FE502AE2390B00605C5C /* SharedTestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF8FE4F2AE2390B00605C5C /* SharedTestHelpers.swift */; }; EDF8FE522AE2470300605C5C /* FeedCachePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF8FE512AE2470300605C5C /* FeedCachePolicy.swift */; }; - EDFBE2402B10409C00EFB793 /* FeedViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE23F2B10409C00EFB793 /* FeedViewControllerTests.swift */; }; + EDFBE2402B10409C00EFB793 /* FeedUIIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE23F2B10409C00EFB793 /* FeedUIIntegrationTests.swift */; }; EDFBE2422B1046C600EFB793 /* EssentialFeed.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1438189629A135C0005B41C9 /* EssentialFeed.framework */; platformFilter = ios; }; EDFBE2432B1046C600EFB793 /* EssentialFeed.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1438189629A135C0005B41C9 /* EssentialFeed.framework */; platformFilter = ios; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; EDFBE2472B1048CF00EFB793 /* XCTTestCase + MemoryLeakTracking.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED3FD1222A267833006299CD /* XCTTestCase + MemoryLeakTracking.swift */; }; @@ -77,6 +77,9 @@ EDFBE2802B16B44E00EFB793 /* Feed.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EDFBE27F2B16B44E00EFB793 /* Feed.xcassets */; }; EDFBE2822B16C2EB00EFB793 /* UITableView+Dequeueing.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE2812B16C2EB00EFB793 /* UITableView+Dequeueing.swift */; }; EDFBE2842B16C40800EFB793 /* UIImageView+Animations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE2832B16C40800EFB793 /* UIImageView+Animations.swift */; }; + EDFBE2882B17B52700EFB793 /* FeedViewControllerTests+Localization.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE2872B17B52700EFB793 /* FeedViewControllerTests+Localization.swift */; }; + EDFBE2892B17BA2100EFB793 /* Feed.strings in Resources */ = {isa = PBXBuildFile; fileRef = EDFBE28B2B17BA2100EFB793 /* Feed.strings */; }; + EDFBE2902B17BD1C00EFB793 /* FeedLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDFBE28F2B17BD1C00EFB793 /* FeedLocalizationTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -179,7 +182,7 @@ EDF8FE4D2AE2389D00605C5C /* FeedCacheTestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCacheTestHelpers.swift; sourceTree = ""; }; EDF8FE4F2AE2390B00605C5C /* SharedTestHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedTestHelpers.swift; sourceTree = ""; }; EDF8FE512AE2470300605C5C /* FeedCachePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCachePolicy.swift; sourceTree = ""; }; - EDFBE23F2B10409C00EFB793 /* FeedViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewControllerTests.swift; sourceTree = ""; }; + EDFBE23F2B10409C00EFB793 /* FeedUIIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedUIIntegrationTests.swift; sourceTree = ""; }; EDFBE2482B10534300EFB793 /* FeedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewController.swift; sourceTree = ""; }; EDFBE24A2B107C8900EFB793 /* FeedImageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedImageCell.swift; sourceTree = ""; }; EDFBE24C2B13186200EFB793 /* UIView+Shimmering.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Shimmering.swift"; sourceTree = ""; }; @@ -203,6 +206,11 @@ EDFBE27F2B16B44E00EFB793 /* Feed.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Feed.xcassets; sourceTree = ""; }; EDFBE2812B16C2EB00EFB793 /* UITableView+Dequeueing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITableView+Dequeueing.swift"; sourceTree = ""; }; EDFBE2832B16C40800EFB793 /* UIImageView+Animations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Animations.swift"; sourceTree = ""; }; + EDFBE2872B17B52700EFB793 /* FeedViewControllerTests+Localization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeedViewControllerTests+Localization.swift"; sourceTree = ""; }; + EDFBE28A2B17BA2100EFB793 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Feed.strings; sourceTree = ""; }; + EDFBE28C2B17BAEF00EFB793 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Feed.strings"; sourceTree = ""; }; + EDFBE28D2B17BBDF00EFB793 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Feed.strings; sourceTree = ""; }; + EDFBE28F2B17BD1C00EFB793 /* FeedLocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedLocalizationTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -348,7 +356,7 @@ ED0EDEC62B0FB681004857B7 /* EssentialFeediOSTests */ = { isa = PBXGroup; children = ( - EDFBE2522B14FE9500EFB793 /* Helpers */, + EDFBE28E2B17BC8500EFB793 /* Feed Presentation */, EDFBE2652B1572A800EFB793 /* Feed UI */, ); path = EssentialFeediOSTests; @@ -506,33 +514,18 @@ path = Views; sourceTree = ""; }; - EDFBE2522B14FE9500EFB793 /* Helpers */ = { + EDFBE25E2B150A5000EFB793 /* Helpers */ = { isa = PBXGroup; children = ( EDFBE2532B14FEE100EFB793 /* UIImage+TestHelpers.swift */, EDFBE2552B14FFF500EFB793 /* UIRefreshControl+TestHelpers.swift */, EDFBE2572B15005500EFB793 /* UIButton+TestHelpers.swift */, EDFBE2592B1500F900EFB793 /* UIControl+TestHelpers.swift */, - ); - path = Helpers; - sourceTree = ""; - }; - EDFBE25B2B15036400EFB793 /* Controllers */ = { - isa = PBXGroup; - children = ( - EDFBE25E2B150A5000EFB793 /* Helpers */, - EDFBE23F2B10409C00EFB793 /* FeedViewControllerTests.swift */, - ); - path = Controllers; - sourceTree = ""; - }; - EDFBE25E2B150A5000EFB793 /* Helpers */ = { - isa = PBXGroup; - children = ( EDFBE25C2B15051100EFB793 /* FeedViewController+TestHelpers.swift */, EDFBE25F2B150A7200EFB793 /* FeedImageCell+TestHelpers.swift */, EDFBE2612B150DC600EFB793 /* FeedViewControllerTests+LoaderSpy.swift */, EDFBE2632B15713C00EFB793 /* FeedViewControllerTests+Assertions.swift */, + EDFBE2872B17B52700EFB793 /* FeedViewControllerTests+Localization.swift */, ); path = Helpers; sourceTree = ""; @@ -540,7 +533,8 @@ EDFBE2652B1572A800EFB793 /* Feed UI */ = { isa = PBXGroup; children = ( - EDFBE25B2B15036400EFB793 /* Controllers */, + EDFBE25E2B150A5000EFB793 /* Helpers */, + EDFBE23F2B10409C00EFB793 /* FeedUIIntegrationTests.swift */, ); path = "Feed UI"; sourceTree = ""; @@ -561,6 +555,15 @@ EDFBE2772B15C8EB00EFB793 /* FeedImagePresenter.swift */, EDFBE2792B15CC5700EFB793 /* FeedViewModel.swift */, EDFBE27B2B15CDC600EFB793 /* FeedLoadingViewModel.swift */, + EDFBE28B2B17BA2100EFB793 /* Feed.strings */, + ); + path = "Feed Presentation"; + sourceTree = ""; + }; + EDFBE28E2B17BC8500EFB793 /* Feed Presentation */ = { + isa = PBXGroup; + children = ( + EDFBE28F2B17BD1C00EFB793 /* FeedLocalizationTests.swift */, ); path = "Feed Presentation"; sourceTree = ""; @@ -735,6 +738,8 @@ knownRegions = ( en, Base, + "pt-BR", + el, ); mainGroup = 1438188C29A135C0005B41C9; productRefGroup = 1438189729A135C0005B41C9 /* Products */; @@ -779,6 +784,7 @@ files = ( EDFBE27E2B16B37E00EFB793 /* Feed.storyboard in Resources */, EDFBE2802B16B44E00EFB793 /* Feed.xcassets in Resources */, + EDFBE2892B17BA2100EFB793 /* Feed.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -882,14 +888,16 @@ files = ( EDFBE2472B1048CF00EFB793 /* XCTTestCase + MemoryLeakTracking.swift in Sources */, EDFBE2642B15713C00EFB793 /* FeedViewControllerTests+Assertions.swift in Sources */, + EDFBE2902B17BD1C00EFB793 /* FeedLocalizationTests.swift in Sources */, EDFBE2582B15005500EFB793 /* UIButton+TestHelpers.swift in Sources */, EDFBE2562B14FFF500EFB793 /* UIRefreshControl+TestHelpers.swift in Sources */, EDFBE25A2B1500F900EFB793 /* UIControl+TestHelpers.swift in Sources */, EDFBE2622B150DC600EFB793 /* FeedViewControllerTests+LoaderSpy.swift in Sources */, EDFBE25D2B15051100EFB793 /* FeedViewController+TestHelpers.swift in Sources */, - EDFBE2402B10409C00EFB793 /* FeedViewControllerTests.swift in Sources */, + EDFBE2402B10409C00EFB793 /* FeedUIIntegrationTests.swift in Sources */, EDFBE2542B14FEE100EFB793 /* UIImage+TestHelpers.swift in Sources */, EDFBE2602B150A7200EFB793 /* FeedImageCell+TestHelpers.swift in Sources */, + EDFBE2882B17B52700EFB793 /* FeedViewControllerTests+Localization.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -933,11 +941,25 @@ }; /* End PBXTargetDependency section */ +/* Begin PBXVariantGroup section */ + EDFBE28B2B17BA2100EFB793 /* Feed.strings */ = { + isa = PBXVariantGroup; + children = ( + EDFBE28A2B17BA2100EFB793 /* en */, + EDFBE28C2B17BAEF00EFB793 /* pt-BR */, + EDFBE28D2B17BBDF00EFB793 /* el */, + ); + name = Feed.strings; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + /* Begin XCBuildConfiguration section */ 143818A829A135C0005B41C9 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -1004,6 +1026,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; diff --git a/EssentialFeed/EssentialFeediOS/Feed Presentation/FeedPresenter.swift b/EssentialFeed/EssentialFeediOS/Feed Presentation/FeedPresenter.swift index eb6b62b..e4f65c9 100644 --- a/EssentialFeed/EssentialFeediOS/Feed Presentation/FeedPresenter.swift +++ b/EssentialFeed/EssentialFeediOS/Feed Presentation/FeedPresenter.swift @@ -5,6 +5,7 @@ // Created by Oluwaseun Adebanwo on 28/11/2023. // +import Foundation import EssentialFeed protocol FeedLoadingView { @@ -24,6 +25,13 @@ final class FeedPresenter { self.loadingView = loadingView } + static var title: String { + return NSLocalizedString("FEED_VIEW_TITLE", + tableName: "Feed", + bundle: Bundle(for: FeedPresenter.self), + comment: "Title for the feed view") + } + func didStartLoadingFeed() { loadingView.display(FeedLoadingViewModel(isLoading: true)) } diff --git a/EssentialFeed/EssentialFeediOS/Feed Presentation/el.lproj/Feed.strings b/EssentialFeed/EssentialFeediOS/Feed Presentation/el.lproj/Feed.strings new file mode 100644 index 0000000..7f8910b --- /dev/null +++ b/EssentialFeed/EssentialFeediOS/Feed Presentation/el.lproj/Feed.strings @@ -0,0 +1,9 @@ +/* + Feed.strings + EssentialFeed + + Created by Oluwaseun Adebanwo on 29/11/2023. + +*/ + +"FEED_VIEW_TITLE" = "Το Feed μου"; diff --git a/EssentialFeed/EssentialFeediOS/Feed Presentation/en.lproj/Feed.strings b/EssentialFeed/EssentialFeediOS/Feed Presentation/en.lproj/Feed.strings new file mode 100644 index 0000000..da2404c --- /dev/null +++ b/EssentialFeed/EssentialFeediOS/Feed Presentation/en.lproj/Feed.strings @@ -0,0 +1,9 @@ +/* + Feed.strings + EssentialFeed + + Created by Oluwaseun Adebanwo on 29/11/2023. + +*/ + +"FEED_VIEW_TITLE" = "My Feed"; diff --git a/EssentialFeed/EssentialFeediOS/Feed Presentation/pt-BR.lproj/Feed.strings b/EssentialFeed/EssentialFeediOS/Feed Presentation/pt-BR.lproj/Feed.strings new file mode 100644 index 0000000..675181c --- /dev/null +++ b/EssentialFeed/EssentialFeediOS/Feed Presentation/pt-BR.lproj/Feed.strings @@ -0,0 +1,9 @@ +/* + Feed.strings + EssentialFeed + + Created by Oluwaseun Adebanwo on 29/11/2023. + +*/ + +"FEED_VIEW_TITLE" = "Meu Feed"; diff --git a/EssentialFeed/EssentialFeediOS/Feed UI/Composers/FeedUIComposer.swift b/EssentialFeed/EssentialFeediOS/Feed UI/Composers/FeedUIComposer.swift index f224090..5ef08c6 100644 --- a/EssentialFeed/EssentialFeediOS/Feed UI/Composers/FeedUIComposer.swift +++ b/EssentialFeed/EssentialFeediOS/Feed UI/Composers/FeedUIComposer.swift @@ -14,10 +14,9 @@ public final class FeedUIComposer { public static func feedComposedWith(feedLoader: FeedLoader, imageLoader: FeedImageDataLoader) -> FeedViewController { let presentationAdapter = FeedLoaderPresentationAdapter(feedLoader: feedLoader) - let bundle = Bundle(for: FeedViewController.self) - let storyboard = UIStoryboard(name: "Feed", bundle: bundle) - let feedController = storyboard.instantiateInitialViewController() as! FeedViewController - feedController.delegate = presentationAdapter + let feedController = FeedViewController.makeWith( + delegate: presentationAdapter, + title: FeedPresenter.title) presentationAdapter.presenter = FeedPresenter( feedView: FeedViewAdapter(controller: feedController, imageLoader: imageLoader), @@ -28,6 +27,17 @@ public final class FeedUIComposer { } } +private extension FeedViewController { + static func makeWith(delegate: FeedViewControllerDelegate, title: String) -> FeedViewController { + let bundle = Bundle(for: FeedViewController.self) + let storyboard = UIStoryboard(name: "Feed", bundle: bundle) + let feedController = storyboard.instantiateInitialViewController() as! FeedViewController + feedController.delegate = delegate + feedController.title = title + return feedController + } +} + private final class WeakRefVirtualProxy { private weak var object: T? diff --git a/EssentialFeed/EssentialFeediOSTests/Feed Presentation/FeedLocalizationTests.swift b/EssentialFeed/EssentialFeediOSTests/Feed Presentation/FeedLocalizationTests.swift new file mode 100644 index 0000000..f91fd01 --- /dev/null +++ b/EssentialFeed/EssentialFeediOSTests/Feed Presentation/FeedLocalizationTests.swift @@ -0,0 +1,64 @@ +// +// FeedLocalizationTests.swift +// EssentialFeediOSTests +// +// Created by Oluwaseun Adebanwo on 29/11/2023. +// + +import XCTest +@testable import EssentialFeediOS + +final class FeedLocalizationTests: XCTestCase { + + func test_localizedStrings_haveKeysAndValuesForAllSupportedLocalizations() { + let table = "Feed" + let presentationBundle = Bundle(for: FeedPresenter.self) + let localizationBundles = allLocalizationBundles(in: presentationBundle) + let localizedStringKeys = allLocalizedStringKeys(in: localizationBundles, table: table) + + localizationBundles.forEach { (bundle, localization) in + localizedStringKeys.forEach { key in + let localizedString = bundle.localizedString(forKey: key, value: nil, table: table) + + if localizedString == key { + let language = Locale.current.localizedString(forLanguageCode: localization) ?? "" + + XCTFail("Missing \(language) (\(localization)) localized string for key: '\(key)' in table: '\(table)'") + } + } + } + } + + // MARK: - Helpers + + private typealias LocalizedBundle = (bundle: Bundle, localization: String) + + private func allLocalizationBundles(in bundle: Bundle, file: StaticString = #file, line: UInt = #line) -> [LocalizedBundle] { + return bundle.localizations.compactMap { localization in + guard + let path = bundle.path(forResource: localization, ofType: "lproj"), + let localizedBundle = Bundle(path: path) + else { + XCTFail("Couldn't find bundle for localization: \(localization)", file: file, line: line) + return nil + } + + return (localizedBundle, localization) + } + } + + private func allLocalizedStringKeys(in bundles: [LocalizedBundle], table: String, file: StaticString = #file, line: UInt = #line) -> Set { + return bundles.reduce([]) { (acc, current) in + guard + let path = current.bundle.path(forResource: table, ofType: "strings"), + let strings = NSDictionary(contentsOfFile: path), + let keys = strings.allKeys as? [String] + else { + XCTFail("Couldn't load localized strings for localization: \(current.localization)", file: file, line: line) + return acc + } + + return acc.union(Set(keys)) + } + } +} diff --git a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/FeedViewControllerTests.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/FeedUIIntegrationTests.swift similarity index 98% rename from EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/FeedViewControllerTests.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/FeedUIIntegrationTests.swift index e164d86..507471d 100644 --- a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/FeedViewControllerTests.swift +++ b/EssentialFeed/EssentialFeediOSTests/Feed UI/FeedUIIntegrationTests.swift @@ -10,7 +10,15 @@ import UIKit import EssentialFeed import EssentialFeediOS -final class FeedViewControllerTests: XCTestCase { +final class FeedUIIntegrationTests: XCTestCase { + + func test_feedView_hasTitle() { + let (sut, _) = makeSUT() + + sut.loadViewIfNeeded() + + XCTAssertEqual(sut.title, localized("FEED_VIEW_TITLE")) + } func test_loadFeedActions_requestFeedFromLoader() { let (sut, loader) = makeSUT() diff --git a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedImageCell+TestHelpers.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedImageCell+TestHelpers.swift similarity index 100% rename from EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedImageCell+TestHelpers.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedImageCell+TestHelpers.swift diff --git a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewController+TestHelpers.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewController+TestHelpers.swift similarity index 100% rename from EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewController+TestHelpers.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewController+TestHelpers.swift diff --git a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewControllerTests+Assertions.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+Assertions.swift similarity index 97% rename from EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewControllerTests+Assertions.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+Assertions.swift index 0c53613..4195728 100644 --- a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewControllerTests+Assertions.swift +++ b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+Assertions.swift @@ -9,7 +9,7 @@ import XCTest import EssentialFeed import EssentialFeediOS -extension FeedViewControllerTests { +extension FeedUIIntegrationTests { func assertThat(_ sut: FeedViewController, isRendering feed: [FeedImage], file: StaticString = #file, line: UInt = #line) { guard sut.numberOfRenderedFeedImageViews() == feed.count else { return XCTFail("Expected \(feed.count) images, got \(sut.numberOfRenderedFeedImageViews()) instead.", file: file, line: line) diff --git a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewControllerTests+LoaderSpy.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+LoaderSpy.swift similarity index 98% rename from EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewControllerTests+LoaderSpy.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+LoaderSpy.swift index f9562d0..5f87c7b 100644 --- a/EssentialFeed/EssentialFeediOSTests/Feed UI/Controllers/Helpers/FeedViewControllerTests+LoaderSpy.swift +++ b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+LoaderSpy.swift @@ -9,7 +9,7 @@ import Foundation import EssentialFeed import EssentialFeediOS -extension FeedViewControllerTests { +extension FeedUIIntegrationTests { class LoaderSpy: FeedLoader, FeedImageDataLoader { diff --git a/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+Localization.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+Localization.swift new file mode 100644 index 0000000..ec299d6 --- /dev/null +++ b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/FeedViewControllerTests+Localization.swift @@ -0,0 +1,22 @@ +// +// FeedViewControllerTests+Localization.swift +// EssentialFeediOSTests +// +// Created by Oluwaseun Adebanwo on 29/11/2023. +// + +import Foundation +import XCTest +import EssentialFeediOS + +extension FeedUIIntegrationTests { + func localized(_ key: String, file: StaticString = #file, line: UInt = #line) -> String { + let table = "Feed" + let bundle = Bundle(for: FeedViewController.self) + let value = bundle.localizedString(forKey: key, value: nil, table: table) + if value == key { + XCTFail("Missing localized string for key: \(key) in table: \(table)", file: file, line: line) + } + return value + } +} diff --git a/EssentialFeed/EssentialFeediOSTests/Helpers/UIButton+TestHelpers.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIButton+TestHelpers.swift similarity index 100% rename from EssentialFeed/EssentialFeediOSTests/Helpers/UIButton+TestHelpers.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIButton+TestHelpers.swift diff --git a/EssentialFeed/EssentialFeediOSTests/Helpers/UIControl+TestHelpers.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIControl+TestHelpers.swift similarity index 100% rename from EssentialFeed/EssentialFeediOSTests/Helpers/UIControl+TestHelpers.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIControl+TestHelpers.swift diff --git a/EssentialFeed/EssentialFeediOSTests/Helpers/UIImage+TestHelpers.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIImage+TestHelpers.swift similarity index 100% rename from EssentialFeed/EssentialFeediOSTests/Helpers/UIImage+TestHelpers.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIImage+TestHelpers.swift diff --git a/EssentialFeed/EssentialFeediOSTests/Helpers/UIRefreshControl+TestHelpers.swift b/EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIRefreshControl+TestHelpers.swift similarity index 100% rename from EssentialFeed/EssentialFeediOSTests/Helpers/UIRefreshControl+TestHelpers.swift rename to EssentialFeed/EssentialFeediOSTests/Feed UI/Helpers/UIRefreshControl+TestHelpers.swift