diff --git a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift index cd39ddb5b9638..a4e9553707466 100644 --- a/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift +++ b/firefox-ios/Client/Frontend/Browser/SearchEngines/SearchEnginesManager.swift @@ -8,9 +8,13 @@ import Shared import Storage protocol SearchEnginesManagerProvider: AnyObject { + @MainActor var defaultEngine: OpenSearchEngine? { get } + @MainActor var orderedEngines: [OpenSearchEngine] { get } + @MainActor var delegate: SearchEngineDelegate? { get set } + @MainActor func getOrderedEngines(completion: @escaping SearchEngineCompletion) } @@ -46,6 +50,7 @@ struct SearchEngineProviderFactory { /// enabled quick search engines, and it is possible to disable every non-default quick search engine). /// /// This class is not thread-safe -- you should only access it on a single thread (usually, the main thread)! +@MainActor class SearchEnginesManager: SearchEnginesManagerProvider { private let prefs: Prefs private let fileAccessor: FileAccessor @@ -291,7 +296,7 @@ class SearchEnginesManager: SearchEnginesManagerProvider { } } - func deleteCustomEngine(_ engine: OpenSearchEngine, completion: @escaping @Sendable () -> Void) { + func deleteCustomEngine(_ engine: OpenSearchEngine, completion: @MainActor @escaping @Sendable () -> Void) { // We can't delete a preinstalled engine or an engine that is currently the default. guard engine.isCustomEngine && !isEngineDefault(engine) else { return } diff --git a/firefox-ios/Client/Frontend/Browser/Toolbars/Models/AddressToolbarContainerModel.swift b/firefox-ios/Client/Frontend/Browser/Toolbars/Models/AddressToolbarContainerModel.swift index 027af648dfb2d..936e39c6196c0 100644 --- a/firefox-ios/Client/Frontend/Browser/Toolbars/Models/AddressToolbarContainerModel.swift +++ b/firefox-ios/Client/Frontend/Browser/Toolbars/Models/AddressToolbarContainerModel.swift @@ -190,6 +190,7 @@ final class AddressToolbarContainerModel: Equatable { ) } + @MainActor init( state: ToolbarState, profile: Profile, @@ -242,6 +243,7 @@ final class AddressToolbarContainerModel: Equatable { self.toolbarHelper = toolbarHelper } + @MainActor func searchTermFromURL(_ url: URL?) -> String? { var searchURL: URL? = url diff --git a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/TopSites/TopSitesManager.swift b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/TopSites/TopSitesManager.swift index 55409c5951192..f518fe1569145 100644 --- a/firefox-ios/Client/Frontend/Home/Homepage Rebuild/TopSites/TopSitesManager.swift +++ b/firefox-ios/Client/Frontend/Home/Homepage Rebuild/TopSites/TopSitesManager.swift @@ -28,6 +28,7 @@ protocol TopSitesManagerInterface { /// - Parameters: /// - otherSites: Contains the user's pinned sites, history, and default suggested sites. /// - sponsoredSites: Contains the sponsored sites. + @MainActor func recalculateTopSites(otherSites: [TopSiteConfiguration], sponsoredSites: [Site]) -> [TopSiteConfiguration] /// Removes the site out of the top sites. @@ -78,6 +79,7 @@ final class TopSitesManager: TopSitesManagerInterface, FeatureFlaggable { self.maxTopSites = maxTopSites } + @MainActor func recalculateTopSites(otherSites: [TopSiteConfiguration], sponsoredSites: [Site]) -> [TopSiteConfiguration] { let availableSpaceCount = getAvailableSpaceCount(with: otherSites) let googleTopSite = addGoogleTopSite(with: availableSpaceCount) @@ -142,6 +144,7 @@ final class TopSitesManager: TopSitesManagerInterface, FeatureFlaggable { return featureFlags.isFeatureEnabled(.hntSponsoredShortcuts, checking: .userOnly) } + @MainActor private func filterSponsoredSites( contiles: [Site], with availableSpaceCount: Int, @@ -161,6 +164,7 @@ final class TopSitesManager: TopSitesManagerInterface, FeatureFlaggable { /// Show the sponsored site only if site is not already present in the pinned sites /// and it's not the default search engine + @MainActor private func shouldShowSponsoredSite(with sponsoredSite: Site, and otherSites: [TopSiteConfiguration]) -> Bool { let siteDomain = sponsoredSite.url.asURL?.shortDomain let sponsoredSiteIsAlreadyPresent = otherSites.contains { (topSite: TopSiteConfiguration) in diff --git a/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSiteHistoryManager.swift b/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSiteHistoryManager.swift index b031f8e45e18f..769b405920bcd 100644 --- a/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSiteHistoryManager.swift +++ b/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSiteHistoryManager.swift @@ -19,6 +19,7 @@ class TopSiteHistoryManager: TopSiteHistoryManagerProvider { private let topSiteCacheSize: Int32 = 32 private let topSitesProvider: TopSitesProvider + @MainActor init(profile: Profile) { self.profile = profile self.topSitesProvider = TopSitesProviderImplementation( diff --git a/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSitesDataAdaptor.swift b/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSitesDataAdaptor.swift index dc5e1299076af..bba995890aee1 100644 --- a/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSitesDataAdaptor.swift +++ b/firefox-ios/Client/Frontend/Home/TopSites/DataManagement/TopSitesDataAdaptor.swift @@ -20,6 +20,7 @@ protocol TopSitesDataAdaptor { var numberOfRows: Int { get } /// Get top sites data + @MainActor func getTopSitesData() -> [TopSite] } @@ -78,6 +79,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable { loadTopSitesData() } + @MainActor func getTopSitesData() -> [TopSite] { recalculateTopSiteData() return topSites @@ -88,6 +90,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable { /// Top sites are composed of pinned sites, history, Contiles and Google top site. /// Google top site is always first, then comes the contiles, pinned sites and history top sites. /// We only add Google top site or Contiles if number of pins doesn't exceeds the available number shown of tiles. + @MainActor private func recalculateTopSiteData() { var sites = historySites let availableSpaceCount = getAvailableSpaceCount(maxTopSites: maxTopSites) @@ -120,9 +123,11 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable { // FXIOS-13954 - This code will disappear soon since it's part of the legacy homepage only. If it hasn't // disappeared then we should is to be revisited once we are on Swift 6.2 with default main actor isolation. // Note: `didLoadNewData` calls `ensureMainThread` - dispatchGroup.notify(queue: dataQueue) { [weak self] in - self?.recalculateTopSiteData() - self?.delegate?.didLoadNewData() + dispatchGroup.notify(queue: .main) { [weak self] in + MainActor.assumeIsolated { + self?.recalculateTopSiteData() + self?.delegate?.didLoadNewData() + } } } @@ -188,6 +193,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable { return Int(preferredNumberOfRows ?? defaultNumberOfRows) } + @MainActor func addSponsoredTiles(sites: inout [Site], shouldAddGoogle: Bool, availableSpaceCount: Int) { diff --git a/firefox-ios/Client/Frontend/Home/TopSites/TopSitesViewModel.swift b/firefox-ios/Client/Frontend/Home/TopSites/TopSitesViewModel.swift index 09e1f4d3505fd..0adfa38a695f1 100644 --- a/firefox-ios/Client/Frontend/Home/TopSites/TopSitesViewModel.swift +++ b/firefox-ios/Client/Frontend/Home/TopSites/TopSitesViewModel.swift @@ -35,6 +35,7 @@ class TopSitesViewModel { private let unifiedAdsTelemetry: UnifiedAdsCallbackTelemetry private let sponsoredTileTelemetry: SponsoredTileTelemetry + @MainActor init(profile: Profile, isZeroSearch: Bool = false, theme: Theme, diff --git a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift index f45955fa18710..215457c344cb8 100644 --- a/firefox-ios/Client/Telemetry/TelemetryWrapper.swift +++ b/firefox-ios/Client/Telemetry/TelemetryWrapper.swift @@ -39,7 +39,10 @@ enum SearchLocation: String { } // FIXME: FXIOS-13987 Make truly thread safe -class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked Sendable { +class TelemetryWrapper: TelemetryWrapperProtocol, + FeatureFlaggable, + Notifiable, + @unchecked Sendable { typealias ExtraKey = TelemetryWrapper.EventExtraKey static let shared = TelemetryWrapper() @@ -83,6 +86,7 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S } catch {} } + @MainActor func setup(profile: Profile) { migratePathComponentInDocumentsDirectory("MozTelemetry-Default-core", to: .cachesDirectory) migratePathComponentInDocumentsDirectory("MozTelemetry-Default-mobile-event", to: .cachesDirectory) @@ -94,6 +98,7 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S initGlean(profile, sendUsageData: sendUsageData) } + @MainActor func initGlean( _ profile: Profile, searchEnginesManager: SearchEnginesManager = AppContainer.shared.resolve(), @@ -176,17 +181,13 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S // Register an observer to record settings and other metrics that are more appropriate to // record on going to background rather than during initialization. - NotificationCenter.default.addObserver( - self, - selector: #selector(recordEnteredBackgroundPreferenceMetrics(notification:)), - name: UIApplication.didEnterBackgroundNotification, - object: nil - ) - NotificationCenter.default.addObserver( - self, - selector: #selector(recordFinishedLaunchingPreferenceMetrics(notification:)), - name: UIApplication.didFinishLaunchingNotification, - object: nil + startObservingNotifications( + withNotificationCenter: NotificationCenter.default, + forObserver: self, + observing: [ + UIApplication.didEnterBackgroundNotification, + UIApplication.didFinishLaunchingNotification + ] ) } @@ -206,9 +207,28 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S } } - @objc - func recordFinishedLaunchingPreferenceMetrics(notification: NSNotification) { + // MARK: Notifiable + func handleNotifications(_ notification: Notification) { + switch notification.name { + case UIApplication.didEnterBackgroundNotification: + // For recording metrics that are better recorded when going to background due + // to the particular measurement, or availability of the information. + ensureMainThread { + self.recordEnteredBackgroundPreferenceMetrics() + } + case UIApplication.didFinishLaunchingNotification: + ensureMainThread { + self.recordFinishedLaunchingPreferenceMetrics() + } + default: + break + } + } + + @MainActor + func recordFinishedLaunchingPreferenceMetrics() { guard let profile = self.profile else { return } + // Pocket stories visible if let pocketStoriesVisible = profile.prefs.boolForKey(PrefsKeys.UserFeatureFlagPrefs.ASPocketStories) { GleanMetrics.FirefoxHomePage.pocketStoriesVisible.set(pocketStoriesVisible) @@ -217,10 +237,8 @@ class TelemetryWrapper: TelemetryWrapperProtocol, FeatureFlaggable, @unchecked S } } - // Function for recording metrics that are better recorded when going to background due - // to the particular measurement, or availability of the information. - @objc - func recordEnteredBackgroundPreferenceMetrics(notification: NSNotification) { + @MainActor + func recordEnteredBackgroundPreferenceMetrics() { guard let profile = self.profile else { assertionFailure("Error unwrapping profile") return diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DependencyInjection/DependencyHelperMock.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DependencyInjection/DependencyHelperMock.swift index 9149f9a7f267e..0ec595dbd008e 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/DependencyInjection/DependencyHelperMock.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/DependencyInjection/DependencyHelperMock.swift @@ -20,13 +20,6 @@ class DependencyHelperMock { ) AppContainer.shared.register(service: profile as Profile) - let searchEnginesManager = SearchEnginesManager( - prefs: profile.prefs, - files: profile.files, - engineProvider: MockSearchEngineProvider() - ) - AppContainer.shared.register(service: searchEnginesManager) - let diskImageStore: DiskImageStore = DefaultDiskImageStore( files: profile.files, namespace: TabManagerConstants.tabScreenshotNamespace, @@ -52,6 +45,13 @@ class DependencyHelperMock { windowManager: windowManager ) AppContainer.shared.register(service: MockThemeManager() as ThemeManager) + + let searchEnginesManager = SearchEnginesManager( + prefs: profile.prefs, + files: profile.files, + engineProvider: MockSearchEngineProvider() + ) + AppContainer.shared.register(service: searchEnginesManager) } } else { DispatchQueue.main.sync { @@ -61,6 +61,13 @@ class DependencyHelperMock { windowManager: windowManager ) AppContainer.shared.register(service: MockThemeManager() as ThemeManager) + + let searchEnginesManager = SearchEnginesManager( + prefs: profile.prefs, + files: profile.files, + engineProvider: MockSearchEngineProvider() + ) + AppContainer.shared.register(service: searchEnginesManager) } } diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/SearchEngines/SearchEnginesManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/SearchEngines/SearchEnginesManagerTests.swift index 9b04311cb2be5..f3b9b1919a455 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/SearchEngines/SearchEnginesManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Browser/SearchEngines/SearchEnginesManagerTests.swift @@ -5,8 +5,10 @@ @testable import Client import Foundation import XCTest +import Common import Shared +@MainActor class SearchEnginesManagerTests: XCTestCase { private let defaultSearchEngineName = "ATester" private let expectedEngineNames = ["ATester", "BTester", "CTester", "DTester", "ETester", "FTester"] @@ -73,9 +75,11 @@ class SearchEnginesManagerTests: XCTestCase { let exp = expectation(description: "Engine was deleted") searchEnginesManager.deleteCustomEngine(testEngine) { [self] in - XCTAssertFalse(searchEnginesManager.orderedEngines.contains(where: { $0 == testEngine })) + ensureMainThread { + XCTAssertFalse(self.searchEnginesManager.orderedEngines.contains(where: { $0 == testEngine })) - exp.fulfill() + exp.fulfill() + } } waitForExpectations(timeout: 2) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesDataAdaptorTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesDataAdaptorTests.swift index 0d1d822004a36..3f0159cc6d96e 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesDataAdaptorTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesDataAdaptorTests.swift @@ -9,6 +9,7 @@ import Shared import Storage import XCTest +@MainActor class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable { private var profile: MockProfile! private var searchEnginesManager: SearchEnginesManager! diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesViewModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesViewModelTests.swift index e26fee413ad4e..c8339cc34611d 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesViewModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Home/TopSites/TopSitesViewModelTests.swift @@ -28,6 +28,7 @@ class TopSitesViewModelTests: XCTestCase { super.tearDown() } + @MainActor func testDeletionOfSingleSuggestedSite() { let viewModel = TopSitesViewModel(profile: profile, isZeroSearch: false, @@ -47,6 +48,7 @@ class TopSitesViewModelTests: XCTestCase { })) } + @MainActor func testDeletionOfAllDefaultSites() { let viewModel = TopSitesViewModel(profile: self.profile, isZeroSearch: false, @@ -68,6 +70,7 @@ class TopSitesViewModelTests: XCTestCase { // MARK: Helper methods extension TopSitesViewModelTests { + @MainActor func createViewModel(overridenSiteCount: Int = 40, overridenNumberOfRows: Int = 2) -> TopSitesViewModel { let viewModel = TopSitesViewModel(profile: self.profile, isZeroSearch: false, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/TopSitesManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/TopSitesManagerTests.swift index 4428e601d0fea..a70555ce5bdc7 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/TopSitesManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Frontend/Homepage Rebuild/TopSitesManagerTests.swift @@ -26,6 +26,7 @@ final class TopSitesManagerTests: XCTestCase { } // MARK: Google Top Site + @MainActor func test_recalculateTopSites_shouldShowGoogle_returnGoogleTopSite() throws { let subject = try createSubject(googleTopSiteManager: MockGoogleTopSiteManager()) let topSites = subject.recalculateTopSites(otherSites: [], sponsoredSites: []) @@ -36,6 +37,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.first?.title, "Google Test") } + @MainActor func test_recalculateTopSites_noGoogleSiteData_returnNoGoogleTopSite() throws { let subject = try createSubject() let topSites = subject.recalculateTopSites(otherSites: [], sponsoredSites: []) @@ -44,6 +46,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertNil(topSites.first) } + @MainActor func test_recalculateTopSites_withOtherSitesAndShouldShowGoogle_returnGoogleTopSite() throws { let subject = try createSubject(googleTopSiteManager: MockGoogleTopSiteManager()) let topSites = subject.recalculateTopSites( @@ -57,6 +60,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.first?.title, "Google Test") } + @MainActor func test_recalculateTopSites_withOtherSitesAndNoGoogleSite_returnNoGoogleTopSite() throws { let subject = try createSubject() let topSites = subject.recalculateTopSites( @@ -148,6 +152,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.count, 0) } + @MainActor func test_recalculateTopSites_shouldShowSponsoredSites_returnOnlyMaxSponsoredSites() throws { // Max contiles is currently at 2, so it should add 2 contiles only. let subject = try createSubject() @@ -174,6 +179,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.compactMap { $0.title }, expectedTitles) } + @MainActor func test_recalculateTopSites_shouldNotShowSponsoredSites_returnNoSponsoredSites() throws { profile?.prefs.setBool(false, forKey: PrefsKeys.FeatureFlags.SponsoredShortcuts) @@ -217,6 +223,7 @@ final class TopSitesManagerTests: XCTestCase { } // MARK: Tiles space calculation + @MainActor func test_recalculateTopSites_maxCountZero_returnNoSites() throws { let subject = try createSubject( googleTopSiteManager: MockGoogleTopSiteManager(), @@ -230,6 +237,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.count, 0) } + @MainActor func test_recalculateTopSites_noAvailableSpace_returnOnlyPinnedSites() throws { let subject = try createSubject(googleTopSiteManager: MockGoogleTopSiteManager(), maxCount: 2) @@ -244,6 +252,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.compactMap { $0.site.url }, ["www.mozilla.com", "www.firefox.com"]) } + @MainActor func test_recalculateTopSites_duplicatePinnedTile_doesNotShowDuplicateSponsoredSite() throws { let subject = try createSubject() @@ -259,6 +268,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.compactMap { $0.site.url }, ["https://mozilla.com", "https://firefox.com"]) } + @MainActor func test_recalculateTopSites_andNoPinnedSites_returnGoogleAndSponsoredSites() throws { let subject = try createSubject(googleTopSiteManager: MockGoogleTopSiteManager(), maxCount: 2) let topSites = subject.recalculateTopSites( @@ -273,6 +283,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.compactMap { $0.site.url }, ["https://www.google.com/webhp?client=firefox-b-1-m&channel=ts", "https://firefox.com"]) } + @MainActor func test_recalculateTopSites_availableSpace_returnSitesInOrder() throws { let subject = try createSubject(googleTopSiteManager: MockGoogleTopSiteManager()) @@ -301,6 +312,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.compactMap { $0.site.url }, expectedURLs) } + @MainActor func test_recalculateTopSites_matchingSponsoredAndHistoryBasedTiles_removeDuplicates() throws { let subject = try createSubject() @@ -324,6 +336,7 @@ final class TopSitesManagerTests: XCTestCase { } // MARK: - Search engine + @MainActor func test_searchEngine_sponsoredSite_getsRemoved() throws { let searchEngine = OpenSearchEngine(engineID: "Firefox", shortName: "Firefox", @@ -415,7 +428,8 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(mockPinnedSites.addPinnedTopSiteCalledCount, 1) } - func test_sponsoredShorcutsFlagEnabled_withoutUserPref_returnsSponsoredSites() throws { + @MainActor + func test_sponsoredShortcutsFlagEnabled_withoutUserPref_returnsSponsoredSites() throws { setupNimbusHNTSponsoredShortcutsTesting(isEnabled: true) let subject = try createSubject() @@ -428,6 +442,7 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.count, 2) } + @MainActor func test_sponsoredShorcutsFlagDisabled_withoutUserPref_returnsNoSponsoredSites() throws { setupNimbusHNTSponsoredShortcutsTesting(isEnabled: false) @@ -441,7 +456,8 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.count, 0) } - func test_sponsoredShorcutsFlagEnabled_withUserPref_returnsNoSponsoredSites() throws { + @MainActor + func test_sponsoredShortcutsFlagEnabled_withUserPref_returnsNoSponsoredSites() throws { profile?.prefs.setBool(false, forKey: PrefsKeys.FeatureFlags.SponsoredShortcuts) setupNimbusHNTSponsoredShortcutsTesting(isEnabled: true) @@ -455,7 +471,8 @@ final class TopSitesManagerTests: XCTestCase { XCTAssertEqual(topSites.count, 0) } - func test_sponsoredShorcutsFlagDisabled_withUserPref_returnsSponsoredSites() throws { + @MainActor + func test_sponsoredShortcutsFlagDisabled_withUserPref_returnsSponsoredSites() throws { profile?.prefs.setBool(true, forKey: PrefsKeys.FeatureFlags.SponsoredShortcuts) setupNimbusHNTSponsoredShortcutsTesting(isEnabled: false) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Messaging/GleanPlumbMessageManagerTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Messaging/GleanPlumbMessageManagerTests.swift index 3f5ccb8020bcc..f7dc10e5918c2 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Messaging/GleanPlumbMessageManagerTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Messaging/GleanPlumbMessageManagerTests.swift @@ -34,6 +34,7 @@ class GleanPlumbMessageManagerTests: XCTestCase { var applicationHelper: MockApplicationHelper! let messageId = "testId" + @MainActor override func setUp() { super.setUp() setupTelemetry(with: MockProfile()) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift index b04d360521615..7bd6506d2ff2c 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/OnboardingTests/OnboardingTelemetryUtilityTests.swift @@ -11,6 +11,7 @@ import Glean class OnboardingTelemetryUtilityTests: XCTestCase { typealias CardNames = NimbusOnboardingTestingConfigUtility.CardOrder + @MainActor override func setUp() { super.setUp() setupTelemetry(with: MockProfile()) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/PasswordManagerViewModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/PasswordManagerViewModelTests.swift index 1798e082015ea..d48d5f64d44b5 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/PasswordManagerViewModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/PasswordManagerViewModelTests.swift @@ -41,7 +41,8 @@ class PasswordManagerViewModelTests: XCTestCase { super.tearDown() } - func testaddLoginWithEmptyString() { + @MainActor + func testAddLoginWithEmptyString() { setupTelemetry(with: MockProfile()) let login = LoginEntry(fromJSONDict: [ "hostname": "https://example.com", @@ -58,6 +59,7 @@ class PasswordManagerViewModelTests: XCTestCase { testCounterMetricRecordingSuccess(metric: GleanMetrics.Logins.saved) } + @MainActor func testaddLoginWithString() { setupTelemetry(with: MockProfile()) let login = LoginEntry(fromJSONDict: [ diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryTests.swift index 5e2be1a09cc20..d667f23e3095d 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Sharing/ShareTelemetryTests.swift @@ -28,6 +28,7 @@ final class ShareTelemetryTests: XCTestCase { super.tearDown() } + @MainActor func testSharedTo_withNoActivityType() throws { setupTelemetry(with: MockProfile()) let subject = createSubject() @@ -52,6 +53,7 @@ final class ShareTelemetryTests: XCTestCase { XCTAssert(savedMetric === event, "Received \(savedMetric) instead of \(event)") } + @MainActor func testSharedTo_withActivityType() throws { setupTelemetry(with: MockProfile()) let subject = createSubject() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/FxSuggestTelemetryTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/FxSuggestTelemetryTests.swift index 84c392e4bb6b1..7c768d740225a 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/FxSuggestTelemetryTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Telemetry/FxSuggestTelemetryTests.swift @@ -11,6 +11,7 @@ import XCTest final class FxSuggestTelemetryTests: XCTestCase { private var gleanWrapper: MockGleanWrapper! + @MainActor override func setUp() { super.setUp() setupTelemetry(with: MockProfile()) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryContextualIdentifierTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryContextualIdentifierTests.swift index 31d920c999493..70d2472b789e1 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryContextualIdentifierTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryContextualIdentifierTests.swift @@ -42,6 +42,7 @@ class TelemetryContextualIdentifierTests: XCTestCase { XCTAssertNotNil(TelemetryContextualIdentifier.contextId) } + @MainActor func testTelemetryWrapper_setsContextId() { TelemetryWrapper.shared.setup(profile: MockProfile()) XCTAssertNotNil(TelemetryContextualIdentifier.contextId) diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift index 1c0f31c41c6f2..2cecb2f7c40fb 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/TelemetryWrapperTests.swift @@ -14,6 +14,7 @@ class TelemetryWrapperTests: XCTestCase { var profile: Profile! + @MainActor override func setUp() { super.setUp() profile = MockProfile() @@ -238,6 +239,7 @@ class TelemetryWrapperTests: XCTestCase { // MARK: Wallpapers + @MainActor func test_backgroundWallpaperMetric_defaultBackgroundIsNotSent() { TelemetryWrapper.shared.setup(profile: profile) LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) @@ -250,14 +252,14 @@ class TelemetryWrapperTests: XCTestCase { WallpaperManager().setCurrentWallpaper(to: defaultWallpaper) { _ in } XCTAssertEqual(WallpaperManager().currentWallpaper.type, .none) - let fakeNotif = NSNotification(name: UIApplication.didEnterBackgroundNotification, object: nil) - TelemetryWrapper.shared.recordEnteredBackgroundPreferenceMetrics(notification: fakeNotif) + TelemetryWrapper.shared.recordEnteredBackgroundPreferenceMetrics() testLabeledMetricSuccess(metric: GleanMetrics.WallpaperAnalytics.themedWallpaper) let wallpaperName = WallpaperManager().currentWallpaper.id.lowercased() XCTAssertNil(GleanMetrics.WallpaperAnalytics.themedWallpaper[wallpaperName].testGetValue()) } + @MainActor func test_backgroundWallpaperMetric_themedWallpaperIsSent() { LegacyFeatureFlagsManager.shared.initializeDeveloperFeatures(with: profile) TelemetryWrapper.shared.setup(profile: profile) @@ -270,8 +272,7 @@ class TelemetryWrapperTests: XCTestCase { WallpaperManager().setCurrentWallpaper(to: themedWallpaper) { _ in } XCTAssertEqual(WallpaperManager().currentWallpaper.type, .other) - let fakeNotif = NSNotification(name: UIApplication.didEnterBackgroundNotification, object: nil) - TelemetryWrapper.shared.recordEnteredBackgroundPreferenceMetrics(notification: fakeNotif) + TelemetryWrapper.shared.recordEnteredBackgroundPreferenceMetrics() testLabeledMetricSuccess(metric: GleanMetrics.WallpaperAnalytics.themedWallpaper) let wallpaperName = WallpaperManager().currentWallpaper.id.lowercased() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift index 63956d3a0332b..4d93c4acf0bb3 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Toolbar/AddressToolbarContainerModelTests.swift @@ -12,6 +12,7 @@ final class AddressToolbarContainerModelTests: XCTestCase { private var searchEnginesManager: SearchEnginesManagerProvider! private let windowUUID: WindowUUID = .XCTestDefaultUUID + @MainActor override func setUp() { super.setUp() DependencyHelperMock().bootstrapDependencies() @@ -31,11 +32,13 @@ final class AddressToolbarContainerModelTests: XCTestCase { super.tearDown() } + @MainActor func testSearchWordFromURLWhenUrlIsNilThenSearchWordIsNil() { let viewModel = createSubject(withState: createToolbarState()) XCTAssertNil(viewModel.searchTermFromURL(nil)) } + @MainActor func testSearchWordFromURLWhenUsingGoogleSearchThenSearchWordIsCorrect() { let viewModel = createSubject(withState: createToolbarState()) let searchTerm = "test" @@ -44,6 +47,7 @@ final class AddressToolbarContainerModelTests: XCTestCase { XCTAssertEqual(searchTerm, result) } + @MainActor func testSearchWordFromURLWhenUsingInternalUrlThenSearchWordIsNil() { let viewModel = createSubject(withState: createToolbarState()) let searchTerm = "test" @@ -51,6 +55,7 @@ final class AddressToolbarContainerModelTests: XCTestCase { XCTAssertNil(viewModel.searchTermFromURL(url)) } + @MainActor func testUsesDefaultSearchEngine_WhenNoSearchEngineSelected() { let viewModel = createSubject(withState: createToolbarState()) @@ -63,6 +68,7 @@ final class AddressToolbarContainerModelTests: XCTestCase { XCTAssertEqual(viewModel.searchEngineImage, defaultEngine.image) } + @MainActor func testUsesAlternativeSearchEngine_WhenSearchEngineSelected() { let searchEngineImage = UIImage() let selectedSearchEngine = OpenSearchEngineTests.generateOpenSearchEngine( @@ -119,38 +125,45 @@ final class AddressToolbarContainerModelTests: XCTestCase { XCTAssertEqual(config.locationViewConfiguration.url, testURL) } + @MainActor func testToolbarColor_withTopToolbar_andNavigationToolbar_andNoTopTabs_hasAlternativeColor() { let viewModel = createSubject(withState: createToolbarState(isShowingTopTabs: false)) XCTAssertTrue(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withTopToolbar_andNavigationToolbar_andTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState()) XCTAssertFalse(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withTopToolbar_andNoNavigationToolbar_andTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState(isShowingNavigationToolbar: false)) XCTAssertFalse(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withTopToolbar_andNoNavigationToolbar_andNoTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState(isShowingNavigationToolbar: false, isShowingTopTabs: false)) XCTAssertFalse(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withBottomToolbar_andNavigationToolbar_andTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState(toolbarPosition: .bottom)) XCTAssertFalse(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withBottomToolbar_andNoNavigationToolbar_andTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState(toolbarPosition: .bottom, isShowingNavigationToolbar: false)) XCTAssertFalse(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withBottomToolbar_andNoNavigationToolbar_andNoTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState(toolbarPosition: .bottom, isShowingNavigationToolbar: false, @@ -158,6 +171,7 @@ final class AddressToolbarContainerModelTests: XCTestCase { XCTAssertFalse(viewModel.hasAlternativeLocationColor) } + @MainActor func testToolbarColor_withBottomToolbar_andNavigationToolbar_andNoTopTabs_hasNormalColor() { let viewModel = createSubject(withState: createToolbarState(toolbarPosition: .bottom, isShowingTopTabs: false)) @@ -166,6 +180,7 @@ final class AddressToolbarContainerModelTests: XCTestCase { // MARK: - Private helpers + @MainActor private func createSubject(withState state: ToolbarState) -> AddressToolbarContainerModel { return AddressToolbarContainerModel(state: state, profile: mockProfile, diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Wallpaper/WallpaperSelectorViewModelTests.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Wallpaper/WallpaperSelectorViewModelTests.swift index efedacd08667a..b6ac2a442571a 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/Wallpaper/WallpaperSelectorViewModelTests.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/Wallpaper/WallpaperSelectorViewModelTests.swift @@ -54,6 +54,7 @@ class WallpaperSelectorViewModelTests: XCTestCase { } // TODO: FXIOS-13652 - Migrate FixWallpaperSelectorViewModelTests to use mock telemetry or GleanWrapper + @MainActor func testRecordsWallpaperSelectorView() throws { setupTelemetry(with: MockProfile()) wallpaperManager = WallpaperManager() @@ -64,6 +65,7 @@ class WallpaperSelectorViewModelTests: XCTestCase { } // TODO: FXIOS-13652 - Migrate FixWallpaperSelectorViewModelTests to use mock telemetry or GleanWrapper + @MainActor func testRecordsWallpaperSelectorClose() throws { setupTelemetry(with: MockProfile()) wallpaperManager = WallpaperManager() diff --git a/firefox-ios/firefox-ios-tests/Tests/ClientTests/XCTestCaseExtensions.swift b/firefox-ios/firefox-ios-tests/Tests/ClientTests/XCTestCaseExtensions.swift index f4dd3e5751da8..0c1e1c24d8228 100644 --- a/firefox-ios/firefox-ios-tests/Tests/ClientTests/XCTestCaseExtensions.swift +++ b/firefox-ios/firefox-ios-tests/Tests/ClientTests/XCTestCaseExtensions.swift @@ -45,6 +45,7 @@ extension XCTestCase { /// Helper function to ensure Glean telemetry is setup for unit tests /// This should not be called in new code: /// - We should us GleanWrapper or mock objects instead of concrete type testing for Glean + @MainActor func setupTelemetry(with profile: Profile) { TelemetryWrapper.hasTelemetryOverride = true