From 17047bd67e16cd81a8373f0a68c664488f6aef43 Mon Sep 17 00:00:00 2001 From: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Date: Wed, 8 May 2024 10:26:37 +0200 Subject: [PATCH] "Clear This History.." for today burns windows (#2745) Task/Issue URL: https://app.asana.com/0/1177771139624306/1207195823454499/f **Description**: When user selects "Clear This History" from the history menu to clear history for today, we have to burn windows too. --- DuckDuckGo/Fire/Model/Fire.swift | 12 ++- .../Menus/CleanThisHistoryMenuItem.swift | 41 ++++++++++- DuckDuckGo/Menus/HistoryMenu.swift | 3 +- DuckDuckGo/Menus/MainMenuActions.swift | 5 +- UnitTests/Fire/Model/FireTests.swift | 73 +++++++++++++++++++ 5 files changed, 128 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Fire/Model/Fire.swift b/DuckDuckGo/Fire/Model/Fire.swift index 4766d8b6e6..29d355c9cd 100644 --- a/DuckDuckGo/Fire/Model/Fire.swift +++ b/DuckDuckGo/Fire/Model/Fire.swift @@ -235,6 +235,7 @@ final class Fire { @MainActor func burnVisits(of visits: [Visit], except fireproofDomains: FireproofDomains, + isToday: Bool, completion: (() -> Void)? = nil) { // Get domains to burn @@ -254,7 +255,16 @@ final class Fire { domains = domains.convertedToETLDPlus1(tld: tld) historyCoordinating.burnVisits(visits) { - self.burnEntity(entity: .none(selectedDomains: domains), + let entity: BurningEntity + + // Burn all windows in case we are burning visits for today + if isToday { + entity = .allWindows(mainWindowControllers: self.windowControllerManager.mainWindowControllers, selectedDomains: domains) + } else { + entity = .none(selectedDomains: domains) + } + + self.burnEntity(entity: entity, includingHistory: false, completion: completion) } diff --git a/DuckDuckGo/Menus/CleanThisHistoryMenuItem.swift b/DuckDuckGo/Menus/CleanThisHistoryMenuItem.swift index 202bee5105..a2c274cf69 100644 --- a/DuckDuckGo/Menus/CleanThisHistoryMenuItem.swift +++ b/DuckDuckGo/Menus/CleanThisHistoryMenuItem.swift @@ -22,13 +22,48 @@ import History final class ClearThisHistoryMenuItem: NSMenuItem { + enum HistoryTimeWindow { + case today + case other(dateString: String) + + var isToday: Bool { + switch self { + case .today: + return true + case .other: + return false + } + } + + var dateString: String? { + switch self { + case .today: + return nil + case .other(let dateString): + return dateString + } + } + + init(dateString: String?) { + if let dateString { + self = .other(dateString: dateString) + } else { + self = .today + } + } + } + // Keep the dateString for alerts so we don't need to use the formatter again - func setDateString(_ dateString: String?) { - representedObject = dateString + func setRepresentingObject(historyTimeWindow: HistoryTimeWindow) { + representedObject = historyTimeWindow } var dateString: String? { - representedObject as? String + (representedObject as? HistoryTimeWindow)?.dateString + } + + var isToday: Bool { + (representedObject as? HistoryTimeWindow)?.isToday ?? false } // Getting visits for the whole menu section in order to perform burning diff --git a/DuckDuckGo/Menus/HistoryMenu.swift b/DuckDuckGo/Menus/HistoryMenu.swift index 42fd97d990..54b9c3633a 100644 --- a/DuckDuckGo/Menus/HistoryMenu.swift +++ b/DuckDuckGo/Menus/HistoryMenu.swift @@ -248,7 +248,8 @@ final class HistoryMenu: NSMenu { let headerItem = ClearThisHistoryMenuItem(title: UserText.clearThisHistoryMenuItem, action: #selector(AppDelegate.clearThisHistory(_:)), keyEquivalent: "") - headerItem.setDateString(dateString) + let historyTimeWindow = ClearThisHistoryMenuItem.HistoryTimeWindow(dateString: dateString) + headerItem.setRepresentingObject(historyTimeWindow: historyTimeWindow) return [ headerItem, .separator() diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index eca7f8004d..884a6695d5 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -482,13 +482,16 @@ extension MainViewController { } let dateString = sender.dateString + let isToday = sender.isToday let visits = sender.getVisits() let alert = NSAlert.clearHistoryAndDataAlert(dateString: dateString) alert.beginSheetModal(for: window, completionHandler: { response in guard case .alertFirstButtonReturn = response else { return } - FireCoordinator.fireViewModel.fire.burnVisits(of: visits, except: FireproofDomains.shared) + FireCoordinator.fireViewModel.fire.burnVisits(of: visits, + except: FireproofDomains.shared, + isToday: isToday) }) } diff --git a/UnitTests/Fire/Model/FireTests.swift b/UnitTests/Fire/Model/FireTests.swift index 2e8e3e778e..2d9340bb09 100644 --- a/UnitTests/Fire/Model/FireTests.swift +++ b/UnitTests/Fire/Model/FireTests.swift @@ -192,6 +192,79 @@ final class FireTests: XCTestCase { XCTAssertFalse(appStateRestorationManager.canRestoreLastSessionState) } + func testWhenBurnVisitIsCalledForTodayThenAllExistingTabsAreCleared() { + let manager = WebCacheManagerMock() + let historyCoordinator = HistoryCoordinatingMock() + let permissionManager = PermissionManagerMock() + let faviconManager = FaviconManagerMock() + let recentlyClosedCoordinator = RecentlyClosedCoordinatorMock() + + let fire = Fire(cacheManager: manager, + historyCoordinating: historyCoordinator, + permissionManager: permissionManager, + windowControllerManager: WindowControllersManager.shared, + faviconManagement: faviconManager, + recentlyClosedCoordinator: recentlyClosedCoordinator, + tld: ContentBlocking.shared.tld) + let tabCollectionViewModel = TabCollectionViewModel.makeTabCollectionViewModel() + _ = WindowsManager.openNewWindow(with: tabCollectionViewModel, lazyLoadTabs: true) + XCTAssertNotEqual(tabCollectionViewModel.allTabsCount, 0) + + let finishedBurningExpectation = expectation(description: "Finished burning") + fire.burnVisits(of: [], + except: FireproofDomains.shared, + isToday: true, + completion: { + finishedBurningExpectation.fulfill() + }) + + waitForExpectations(timeout: 5) + XCTAssertEqual(tabCollectionViewModel.allTabsCount, 0) + XCTAssert(manager.clearCalled) + XCTAssert(historyCoordinator.burnVisitsCalled) + XCTAssertFalse(historyCoordinator.burnAllCalled) + XCTAssert(permissionManager.burnPermissionsOfDomainsCalled) + XCTAssertFalse(permissionManager.burnPermissionsCalled) + XCTAssert(recentlyClosedCoordinator.burnCacheCalled) + } + + func testWhenBurnVisitIsCalledForOtherDayThenExistingTabsRemainOpen() { + let manager = WebCacheManagerMock() + let historyCoordinator = HistoryCoordinatingMock() + let permissionManager = PermissionManagerMock() + let faviconManager = FaviconManagerMock() + let recentlyClosedCoordinator = RecentlyClosedCoordinatorMock() + + let fire = Fire(cacheManager: manager, + historyCoordinating: historyCoordinator, + permissionManager: permissionManager, + windowControllerManager: WindowControllersManager.shared, + faviconManagement: faviconManager, + recentlyClosedCoordinator: recentlyClosedCoordinator, + tld: ContentBlocking.shared.tld) + let tabCollectionViewModel = TabCollectionViewModel.makeTabCollectionViewModel() + _ = WindowsManager.openNewWindow(with: tabCollectionViewModel, lazyLoadTabs: true) + XCTAssertNotEqual(tabCollectionViewModel.allTabsCount, 0) + let numberOfTabs = tabCollectionViewModel.allTabsCount + + let finishedBurningExpectation = expectation(description: "Finished burning") + fire.burnVisits(of: [], + except: FireproofDomains.shared, + isToday: false, + completion: { + finishedBurningExpectation.fulfill() + }) + + waitForExpectations(timeout: 5) + XCTAssertEqual(tabCollectionViewModel.allTabsCount, numberOfTabs) + XCTAssert(manager.clearCalled) + XCTAssert(historyCoordinator.burnVisitsCalled) + XCTAssertFalse(historyCoordinator.burnAllCalled) + XCTAssert(permissionManager.burnPermissionsOfDomainsCalled) + XCTAssertFalse(permissionManager.burnPermissionsCalled) + XCTAssert(recentlyClosedCoordinator.burnCacheCalled) + } + func preparePersistedState(withFileName fileName: String) -> FileStore { let fileStore = FileStoreMock() let state = SavedStateMock()