From d0fb45d8ac54852992b7281446affdc4b89a2c7f Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Tue, 9 Apr 2024 09:26:35 +1000 Subject: [PATCH] Fix bookmarks bar visibility (#2554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1177771139624306/1207002191511983/f **Description**: The current bookmarks bar has some bugs related to its visibility when the appearance preference is “Only shows on New Tab”. This PR extracts and fix the logic to determine whether the bookmarks bar should be visible or not. --- DuckDuckGo.xcodeproj/project.pbxproj | 14 + .../BookmarksBarVisibilityManager.swift | 96 ++++++ .../MainWindow/MainViewController.swift | 28 +- .../Model/AppearancePreferences.swift | 2 + .../BookmarksBarVisibilityManagerTests.swift | 296 ++++++++++++++++++ 5 files changed, 418 insertions(+), 18 deletions(-) create mode 100644 DuckDuckGo/BookmarksBar/BookmarksBarVisibilityManager.swift create mode 100644 UnitTests/BookmarksBar/BookmarksBarVisibilityManagerTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f32dde62b9..2bbc99a10b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2479,6 +2479,11 @@ 9F26060C2B85C20B00819292 /* AddEditBookmarkDialogViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F2606092B85C20400819292 /* AddEditBookmarkDialogViewModelTests.swift */; }; 9F26060E2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */; }; 9F26060F2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */; }; + 9F33445E2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F33445D2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift */; }; + 9F33445F2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F33445D2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift */; }; + 9F3344602BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F33445D2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift */; }; + 9F3344622BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3344612BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift */; }; + 9F3344632BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3344612BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift */; }; 9F3910622B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910612B68C35600CB5112 /* DownloadsTabExtensionTests.swift */; }; 9F3910632B68C35600CB5112 /* DownloadsTabExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910612B68C35600CB5112 /* DownloadsTabExtensionTests.swift */; }; 9F3910692B68D87B00CB5112 /* ProgressExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F3910682B68D87B00CB5112 /* ProgressExtensionTests.swift */; }; @@ -4227,6 +4232,8 @@ 9F180D112B69C665000D695F /* DownloadsTabExtensionMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsTabExtensionMock.swift; sourceTree = ""; }; 9F2606092B85C20400819292 /* AddEditBookmarkDialogViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogViewModelTests.swift; sourceTree = ""; }; 9F26060D2B85E17D00819292 /* AddEditBookmarkDialogCoordinatorViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkDialogCoordinatorViewModelTests.swift; sourceTree = ""; }; + 9F33445D2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarVisibilityManager.swift; sourceTree = ""; }; + 9F3344612BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksBarVisibilityManagerTests.swift; sourceTree = ""; }; 9F3910612B68C35600CB5112 /* DownloadsTabExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsTabExtensionTests.swift; sourceTree = ""; }; 9F3910682B68D87B00CB5112 /* ProgressExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressExtensionTests.swift; sourceTree = ""; }; 9F514F902B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddEditBookmarkFolderDialogView.swift; sourceTree = ""; }; @@ -5761,6 +5768,7 @@ isa = PBXGroup; children = ( 4B43468E285ED6CB00177407 /* ViewModel */, + 9F3344612BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift */, ); path = BookmarksBar; sourceTree = ""; @@ -6481,6 +6489,7 @@ children = ( 4BD18F02283F0F1000058124 /* View */, 850E8DFA2A6FEC5E00691187 /* BookmarksBarAppearance.swift */, + 9F33445D2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift */, ); path = BookmarksBar; sourceTree = ""; @@ -10528,6 +10537,7 @@ 4B9DB0422A983B24000927DB /* WaitlistDialogView.swift in Sources */, 3706FB57293F65D500E42796 /* AppPrivacyConfigurationDataProvider.swift in Sources */, 857E5AF62A790B7000FC0FB4 /* PixelExperiment.swift in Sources */, + 9F33445F2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift in Sources */, 3706FB58293F65D500E42796 /* LinkButton.swift in Sources */, 4B0EF7272B578096009D6481 /* AppVersionExtension.swift in Sources */, 3706FB59293F65D500E42796 /* TemporaryFileHandler.swift in Sources */, @@ -11231,6 +11241,7 @@ 028904212A7B25770028369C /* AppConfigurationURLProviderTests.swift in Sources */, 3706FE6F293F661700E42796 /* LocalStatisticsStoreTests.swift in Sources */, 3706FE70293F661700E42796 /* HistoryCoordinatorTests.swift in Sources */, + 9F3344632BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift in Sources */, 3706FE71293F661700E42796 /* SavedStateMock.swift in Sources */, 3706FE72293F661700E42796 /* ClickToLoadTDSTests.swift in Sources */, 3706FE73293F661700E42796 /* PermissionManagerMock.swift in Sources */, @@ -11879,6 +11890,7 @@ 4B957A932AC7AE700062CA31 /* PermissionModel.swift in Sources */, 4B957A942AC7AE700062CA31 /* PasteboardFolder.swift in Sources */, 4B957A952AC7AE700062CA31 /* CookieManagedNotificationView.swift in Sources */, + 9F3344602BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift in Sources */, 4B957A962AC7AE700062CA31 /* PermissionType.swift in Sources */, 4B957A982AC7AE700062CA31 /* RecentlyClosedWindow.swift in Sources */, 4B957A992AC7AE700062CA31 /* ActionSpeech.swift in Sources */, @@ -12958,6 +12970,7 @@ AA7EB6DF27E7C57D00036718 /* MouseOverAnimationButton.swift in Sources */, AA7412B724D1687000D22FE0 /* TabBarScrollView.swift in Sources */, 1E559BB12BBCA9F1002B4AF6 /* RedirectNavigationResponder.swift in Sources */, + 9F33445E2BBFA77F0040CBEB /* BookmarksBarVisibilityManager.swift in Sources */, 4B9292D92667124B00AD2C21 /* BookmarkListTreeControllerDataSource.swift in Sources */, 14D9B8FB24F7E089000D4D13 /* AddressBarViewController.swift in Sources */, B65536A62685B82B00085A79 /* Permissions.swift in Sources */, @@ -13136,6 +13149,7 @@ AAC9C01C24CB594C00AD1325 /* TabViewModelTests.swift in Sources */, 567DA93F29E8045D008AC5EE /* MockEmailStorage.swift in Sources */, 37CD54B727F1B28A00F1F7B9 /* DefaultBrowserPreferencesTests.swift in Sources */, + 9F3344622BBFBDA40040CBEB /* BookmarksBarVisibilityManagerTests.swift in Sources */, 028904202A7B25380028369C /* AppConfigurationURLProviderTests.swift in Sources */, B65349AA265CF45000DCC645 /* DispatchQueueExtensionsTests.swift in Sources */, 858A798A26A9B35E00A75A42 /* PasswordManagementItemModelTests.swift in Sources */, diff --git a/DuckDuckGo/BookmarksBar/BookmarksBarVisibilityManager.swift b/DuckDuckGo/BookmarksBar/BookmarksBarVisibilityManager.swift new file mode 100644 index 0000000000..9e837b1a47 --- /dev/null +++ b/DuckDuckGo/BookmarksBar/BookmarksBarVisibilityManager.swift @@ -0,0 +1,96 @@ +// +// BookmarksBarVisibilityManager.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Combine + +/// Decides if the BookmarksBar should be visible based on the Tab.Content and Appearance preferences. +final class BookmarksBarVisibilityManager { + private var bookmarkBarVisibilityCancellable: AnyCancellable? + private var bookmarkContentCancellable: AnyCancellable? + + /// A published value indicating the visibility of the bookmarks bar. + /// Returns`true` if the bookmarks bar is visible; otherwise, `false`. + @Published var isBookmarksBarVisible: Bool = false + + private let selectedTabPublisher: AnyPublisher + private let preferences: AppearancePreferences + + /// Create an instance given the specified `TabViewModel` publisher and `AppearancePreferences`. + /// - Parameters: + /// - selectedTabPublisher: A publisher that returns the selected Tab view model. + /// - preferences: The `AppearancePreferences` to read the bookmarks appearance preferences from. + init(selectedTabPublisher: AnyPublisher, preferences: AppearancePreferences = .shared) { + self.selectedTabPublisher = selectedTabPublisher + self.preferences = preferences + bind() + } + +} + +// MARK: - Private + +private extension BookmarksBarVisibilityManager { + + func bind() { + let bookmarksBarVisibilityPublisher = NotificationCenter.default + .publisher(for: AppearancePreferences.Notifications.showBookmarksBarSettingChanged) + + let bookmarksBarAppearancePublisher = NotificationCenter.default + .publisher(for: AppearancePreferences.Notifications.bookmarksBarSettingAppearanceChanged) + + let bookmarksBarNotificationsPublisher = Publishers.Merge(bookmarksBarVisibilityPublisher, bookmarksBarAppearancePublisher) + .map { _ in () } // Map To Void, we're not interested in the notification itself + .prepend(()) // Start with a value so combineLatest can fire + + // Every time the user select a tab or the Appeareance preference changes check if bookmarks bar should be visible or not. + // For the selected Tab we should also check if the Tab content changes as it can switch from empty to url if the user loads a web page. + bookmarkBarVisibilityCancellable = bookmarksBarNotificationsPublisher + .combineLatest(selectedTabPublisher) + .compactMap { _, selectedTab -> TabViewModel? in + guard let selectedTab else { return nil } + return selectedTab + } + .flatMap { tabViewModel in + // Subscribe to the selected tab content. + // When a tab is empty and the bookmarksBar should show only on empty Tabs it should disappear when we load a website. + tabViewModel.tab.$content.eraseToAnyPublisher() + } + .sink(receiveValue: { [weak self] tabContent in + guard let self = self else { return } + self.updateBookmarksBar(content: tabContent, preferences: self.preferences) + }) + } + + func updateBookmarksBar(content: Tab.TabContent, preferences: AppearancePreferences) { + // If visibility should be off, set visibility off and exit + guard preferences.showBookmarksBar else { + isBookmarksBarVisible = false + return + } + + // If visibility is on check Appearance + switch preferences.bookmarksBarAppearance { + case .newTabOnly: + isBookmarksBarVisible = content.isEmpty + case .alwaysOn: + isBookmarksBarVisible = true + } + } + +} diff --git a/DuckDuckGo/MainWindow/MainViewController.swift b/DuckDuckGo/MainWindow/MainViewController.swift index 5e0840ebc5..04b5810087 100644 --- a/DuckDuckGo/MainWindow/MainViewController.swift +++ b/DuckDuckGo/MainWindow/MainViewController.swift @@ -32,6 +32,7 @@ final class MainViewController: NSViewController { let findInPageViewController: FindInPageViewController let fireViewController: FireViewController let bookmarksBarViewController: BookmarksBarViewController + private let bookmarksBarVisibilityManager: BookmarksBarVisibilityManager let tabCollectionViewModel: TabCollectionViewModel let isBurner: Bool @@ -60,6 +61,7 @@ final class MainViewController: NSViewController { self.isBurner = tabCollectionViewModel.isBurner tabBarViewController = TabBarViewController.create(tabCollectionViewModel: tabCollectionViewModel) + bookmarksBarVisibilityManager = BookmarksBarVisibilityManager(selectedTabPublisher: tabCollectionViewModel.$selectedTabViewModel.eraseToAnyPublisher()) let networkProtectionPopoverManager: NetPPopoverManager = { #if DEBUG @@ -124,7 +126,7 @@ final class MainViewController: NSViewController { listenToKeyDownEvents() subscribeToMouseTrackingArea() subscribeToSelectedTabViewModel() - subscribeToAppSettingsNotifications() + subscribeToBookmarkBarVisibility() subscribeToFirstResponder() mainView.findInPageContainerView.applyDropShadow() @@ -171,9 +173,6 @@ final class MainViewController: NSViewController { resizeNavigationBar(isHomePage: tabCollectionViewModel.selectedTabViewModel?.tab.content == .newtab, animated: false) - - let bookmarksBarVisible = AppearancePreferences.shared.showBookmarksBar - updateBookmarksBarViewVisibility(visible: bookmarksBarVisible) } updateDividerColor(isShowingHomePage: tabCollectionViewModel.selectedTabViewModel?.tab.content == .newtab) @@ -314,11 +313,13 @@ final class MainViewController: NSViewController { .store(in: &tabViewModelCancellables) } - private func subscribeToAppSettingsNotifications() { - bookmarksBarVisibilityChangedCancellable = NotificationCenter.default - .publisher(for: AppearancePreferences.Notifications.showBookmarksBarSettingChanged) - .sink { [weak self] _ in - self?.updateBookmarksBarViewVisibility(visible: AppearancePreferences.shared.showBookmarksBar) + private func subscribeToBookmarkBarVisibility() { + bookmarksBarVisibilityChangedCancellable = bookmarksBarVisibilityManager + .$isBookmarksBarVisible + .removeDuplicates() + .receive(on: DispatchQueue.main) + .sink { [weak self] isBookmarksBarVisible in + self?.updateBookmarksBarViewVisibility(visible: isBookmarksBarVisible) } } @@ -335,7 +336,6 @@ final class MainViewController: NSViewController { defer { lastTabContent = content } resizeNavigationBar(isHomePage: content == .newtab, animated: content == .newtab && lastTabContent != .newtab) - updateBookmarksBar(content) adjustFirstResponder(selectedTabViewModel: selectedTabViewModel, tabContent: content) } .store(in: &self.tabViewModelCancellables) @@ -355,14 +355,6 @@ final class MainViewController: NSViewController { } } - private func updateBookmarksBar(_ content: Tab.TabContent, _ prefs: AppearancePreferences = AppearancePreferences.shared) { - if content.isUrl && prefs.bookmarksBarAppearance == .newTabOnly { - updateBookmarksBarViewVisibility(visible: false) - } else if prefs.showBookmarksBar { - updateBookmarksBarViewVisibility(visible: true) - } - } - private func subscribeToFindInPage(of selectedTabViewModel: TabViewModel?) { selectedTabViewModel?.findInPage? .$isVisible diff --git a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift index 833bab73f0..c5558b2637 100644 --- a/DuckDuckGo/Preferences/Model/AppearancePreferences.swift +++ b/DuckDuckGo/Preferences/Model/AppearancePreferences.swift @@ -135,6 +135,7 @@ final class AppearancePreferences: ObservableObject { struct Notifications { static let showBookmarksBarSettingChanged = NSNotification.Name("ShowBookmarksBarSettingChanged") + static let bookmarksBarSettingAppearanceChanged = NSNotification.Name("BookmarksBarSettingAppearanceChanged") } static let shared = AppearancePreferences() @@ -197,6 +198,7 @@ final class AppearancePreferences: ObservableObject { @Published var bookmarksBarAppearance: BookmarksBarAppearance { didSet { persistor.bookmarksBarAppearance = bookmarksBarAppearance + NotificationCenter.default.post(name: Notifications.bookmarksBarSettingAppearanceChanged, object: nil) } } diff --git a/UnitTests/BookmarksBar/BookmarksBarVisibilityManagerTests.swift b/UnitTests/BookmarksBar/BookmarksBarVisibilityManagerTests.swift new file mode 100644 index 0000000000..802a33323c --- /dev/null +++ b/UnitTests/BookmarksBar/BookmarksBarVisibilityManagerTests.swift @@ -0,0 +1,296 @@ +// +// BookmarksBarVisibilityManagerTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest +import Combine +@testable import DuckDuckGo_Privacy_Browser + +@MainActor +final class BookmarksBarVisibilityManagerTests: XCTestCase { + private let selectedTabSubject = PassthroughSubject() + private var appearance: AppearancePreferences! + private var cancellables: Set! + let tabContents: [Tab.TabContent] = [ + .none, + .newtab, + .url(URL.duckDuckGo, credential: nil, source: .link), + .settings(pane: nil), + .bookmarks, + .onboarding, + .dataBrokerProtection, + .subscription(URL.duckDuckGo), + .identityTheftRestoration(URL.duckDuckGo) + ] + + override func setUpWithError() throws { + try super.setUpWithError() + + cancellables = [] + appearance = AppearancePreferences(persistor: AppearancePreferencesPersistorMock()) + } + + override func tearDownWithError() throws { + cancellables = nil + appearance = nil + try super.tearDownWithError() + } + + func makeSUT() -> BookmarksBarVisibilityManager { + return BookmarksBarVisibilityManager( + selectedTabPublisher: selectedTabSubject.eraseToAnyPublisher(), + preferences: appearance + ) + } + + func testWhenSubscribeThenIsBookmarksBarVisibleIsFalse() { + // GIVEN + let sut = makeSUT() + + // WHEN + let result = sut.isBookmarksBarVisible + + // THEN + XCTAssertFalse(result) + } + + // MARK: - Selecting a Tab + + // Appearance `showBookmarksBars` false + func testWhenSelectedTaContentAndShowBookmarksBarIsFalseThenIsBookmarksBarVisibleIsFalse() throws { + // GIVEN + appearance.showBookmarksBar = false + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + + // WHEN + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // THEN + try assertFalse(capturedValue) + } + } + + // Appearance `showBookmarksBars` true and .newTabOnly + + func testWhenShowBookmarksBarIsTrueAndBookmarkBarAppearanceIsTabOnlyThenIsBookmarksBarVisibleIsTrueForNoneAndNewTab() throws { + // GIVEN + appearance.showBookmarksBar = true + appearance.bookmarksBarAppearance = .newTabOnly + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + + // WHEN + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // THEN + switch content { + case .none, .newtab: + try assertTrue(capturedValue) + default: + try assertFalse(capturedValue) + } + } + } + + // Appearance `showBookmarksBars` true and .alwaysOn + + func testWhenShowBookmarksBarIsTrueAndBookmarkBarAppearanceIsAlwaysOnThenIsBookmarksBarVisibleIsTrue() throws { + // GIVEN + appearance.showBookmarksBar = true + appearance.bookmarksBarAppearance = .alwaysOn + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + + // WHEN + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // THEN + try assertTrue(capturedValue) + } + } + + // MARK: - Settings Change + + func testWhenChangingShowBookmarksBarToTrueThenIsBookmarksBarVisibleIsTrue() throws { + // GIVEN + appearance.showBookmarksBar = false + appearance.bookmarksBarAppearance = .alwaysOn + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + XCTAssertFalse(sut.isBookmarksBarVisible) + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // WHEN + appearance.showBookmarksBar = true + + // THEN + try assertTrue(capturedValue) + } + } + + func testWhenChangingShowBookmarksBarToFalseThenIsBookmarksBarVisibleIsFalse() throws { + // GIVEN + appearance.showBookmarksBar = true + appearance.bookmarksBarAppearance = .alwaysOn + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // WHEN + appearance.showBookmarksBar = false + + // THEN + try assertFalse(capturedValue) + } + } + + func testWhenBookmarksBarAppearanceChangesToAlwaysVisibleThenIsBookmarkBarVisibleIsTrue() throws { + // GIVEN + appearance.showBookmarksBar = true + appearance.bookmarksBarAppearance = .newTabOnly + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + XCTAssertFalse(sut.isBookmarksBarVisible) + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // WHEN + appearance.bookmarksBarAppearance = .alwaysOn + + // THEN + try assertTrue(capturedValue) + } + } + + func testWhenBookmarksBarAppearanceChangesToOnlyOnNewTabThenIsBookmarkBarVisibleIsTrueForNoneAndNewTab() throws { + // GIVEN + appearance.showBookmarksBar = true + appearance.bookmarksBarAppearance = .alwaysOn + + for content in tabContents { + var capturedValue: Bool? + let sut = makeSUT() + XCTAssertFalse(sut.isBookmarksBarVisible) + sut.$isBookmarksBarVisible + .dropFirst() // Not interested in the value when subscribing + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + selectedTabSubject.send(TabViewModel(tab: Tab(content: content))) + + // WHEN + appearance.bookmarksBarAppearance = .newTabOnly + + // THEN + switch content { + case .none, .newtab: + try assertTrue(capturedValue) + default: + try assertFalse(capturedValue) + } + } + } + + // MARK: - New Tab becoming URL + + func testWhenBookmarksBarAppeareanceIsNewTabOnlyAndTabContentBecomesURLThenIsBookmarkBarVisibleIsFalse() throws { + // GIVEN + appearance.showBookmarksBar = true + appearance.bookmarksBarAppearance = .newTabOnly + let sut = makeSUT() + let tab = Tab(content: .newtab) + selectedTabSubject.send(TabViewModel(tab: tab)) + + var capturedValue: Bool? + sut.$isBookmarksBarVisible + .sink { value in + capturedValue = value + } + .store(in: &cancellables) + try assertTrue(capturedValue) + + // WHEN + tab.setContent(.url(URL.duckDuckGo, credential: nil, source: .link)) + + // THEN + try assertFalse(capturedValue) + } + +} + +private extension BookmarksBarVisibilityManagerTests { + + func assertFalse(_ value: Bool?) throws { + let value = try XCTUnwrap(value) + XCTAssertFalse(value) + } + + func assertTrue(_ value: Bool?) throws { + let value = try XCTUnwrap(value) + XCTAssertTrue(value) + } + +}