diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 06a5ccb48f..ea6a8a607a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -15385,8 +15385,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 225.0.0; + branch = dominik/customizer; + kind = branch; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fb5f5537b3..e46153407f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "20e6eaf0b1e423d9a270e2d460cae284c08f73d8", - "version" : "225.0.0" + "branch" : "dominik/customizer", + "revision" : "b951930902a499af162f985fbe408949bd0cb599" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "7958ddab724c26326333cae13fe81478290607fa", - "version" : "7.6.0" + "revision" : "0ac30560ec969a321caea321f95537120416c323", + "version" : "7.7.0" } }, { diff --git a/DuckDuckGo/NewTabPage/DefaultsFavoritesActionHandler.swift b/DuckDuckGo/NewTabPage/DefaultsFavoritesActionHandler.swift index 207904a3eb..0e0e52c746 100644 --- a/DuckDuckGo/NewTabPage/DefaultsFavoritesActionHandler.swift +++ b/DuckDuckGo/NewTabPage/DefaultsFavoritesActionHandler.swift @@ -72,11 +72,6 @@ final class DefaultFavoritesActionsHandler: FavoritesActionsHandling { bookmarkManager.moveFavorites(with: [bookmarkID], toIndex: index) { _ in } } - @MainActor - func onFaviconMissing() { - faviconsFetcherOnboarding?.presentOnboardingIfNeeded() - } - @MainActor private var window: NSWindow? { WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.view.window @@ -86,14 +81,17 @@ final class DefaultFavoritesActionsHandler: FavoritesActionsHandling { private var tabCollectionViewModel: TabCollectionViewModel? { WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController.tabCollectionViewModel } +} + +extension Bookmark: NewTabPageFavorite { + private enum Const { + static let wwwPrefix = "www." + } - private lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding? = { - guard let syncService = NSApp.delegateTyped.syncService, let syncBookmarksAdapter = NSApp.delegateTyped.syncDataProviders?.bookmarksAdapter else { - assertionFailure("SyncService and/or SyncBookmarksAdapter is nil") + var etldPlusOne: String? { + guard let domain = urlObject?.host else { return nil } - return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter) - }() + return ContentBlocking.shared.tld.eTLDplus1(domain)?.dropping(prefix: Const.wwwPrefix) + } } - -extension Bookmark: NewTabPageFavorite {} diff --git a/DuckDuckGo/NewTabPage/NewTabPageCustomizationProvider.swift b/DuckDuckGo/NewTabPage/NewTabPageCustomizationProvider.swift index 336d51c8ab..66002d3036 100644 --- a/DuckDuckGo/NewTabPage/NewTabPageCustomizationProvider.swift +++ b/DuckDuckGo/NewTabPage/NewTabPageCustomizationProvider.swift @@ -89,6 +89,25 @@ final class NewTabPageCustomizationProvider: NewTabPageCustomBackgroundProviding } homePageSettingsModel.customImagesManager?.deleteImage(image) } + + @MainActor + func showContextMenu(for imageID: String, using presenter: any NewTabPageContextMenuPresenting) async { + let menu = NSMenu() + + menu.buildItems { + NSMenuItem(title: UserText.deleteBackground, action: #selector(deleteBackground(_:)), target: self, representedObject: imageID) + .withAccessibilityIdentifier("HomePage.Views.deleteBackground") + } + + presenter.showContextMenu(menu) + } + + @objc public func deleteBackground(_ sender: NSMenuItem) { + Task { + guard let imageID = sender.representedObject as? String else { return } + await deleteImage(with: imageID) + } + } } extension NewTabPageDataModel.Background { diff --git a/DuckDuckGo/Tab/Navigation/DuckURLSchemeHandler.swift b/DuckDuckGo/Tab/Navigation/DuckURLSchemeHandler.swift index 3ddbb252a4..1b4ca5c37d 100644 --- a/DuckDuckGo/Tab/Navigation/DuckURLSchemeHandler.swift +++ b/DuckDuckGo/Tab/Navigation/DuckURLSchemeHandler.swift @@ -73,6 +73,14 @@ final class DuckURLSchemeHandler: NSObject, WKURLSchemeHandler { } func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {} + + private lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding? = { + guard let syncService = NSApp.delegateTyped.syncService, let syncBookmarksAdapter = NSApp.delegateTyped.syncDataProviders?.bookmarksAdapter else { + assertionFailure("SyncService and/or SyncBookmarksAdapter is nil") + return nil + } + return .init(syncService: syncService, syncBookmarksAdapter: syncBookmarksAdapter) + }() } // MARK: - Native UI Paged @@ -177,11 +185,16 @@ private extension DuckURLSchemeHandler { guard let response = HTTPURLResponse(url: requestURL, statusCode: 404, httpVersion: "HTTP/1.1", headerFields: nil) else { return nil } + onFaviconMissing() return (response, Data()) } let response = URLResponse(url: requestURL, mimeType: "image/png", expectedContentLength: imagePNGData.count, textEncodingName: nil) return (response, imagePNGData) } + + private func onFaviconMissing() { + faviconsFetcherOnboarding?.presentOnboardingIfNeeded() + } } // MARK: - Custom Background Images diff --git a/LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift b/LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift index 3627ee5af6..acbec1557e 100644 --- a/LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift +++ b/LocalPackages/FeatureFlags/Sources/FeatureFlags/FeatureFlag.swift @@ -105,7 +105,7 @@ extension FeatureFlag: FeatureFlagDescribing { case .networkProtectionEnforceRoutes: return .remoteDevelopment(.subfeature(NetworkProtectionSubfeature.enforceRoutes)) case .htmlNewTabPage: - return .disabled + return .remoteReleasable(.subfeature(HTMLNewTabPageSubfeature.isLaunched)) case .autofillPartialFormSaves: return .remoteReleasable(.subfeature(AutofillSubfeature.partialFormSaves)) case .autcompleteTabs: diff --git a/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageCustomBackgroundClient.swift b/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageCustomBackgroundClient.swift index 9f86ec64e9..50b7c53dbf 100644 --- a/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageCustomBackgroundClient.swift +++ b/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageCustomBackgroundClient.swift @@ -35,17 +35,24 @@ public protocol NewTabPageCustomBackgroundProviding: AnyObject { @MainActor func presentUploadDialog() async func deleteImage(with imageID: String) async + + @MainActor func showContextMenu(for imageID: String, using presenter: NewTabPageContextMenuPresenting) async } public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient { let model: NewTabPageCustomBackgroundProviding + let contextMenuPresenter: NewTabPageContextMenuPresenting public weak var userScriptsSource: NewTabPageUserScriptsSource? private var cancellables: Set = [] - public init(model: NewTabPageCustomBackgroundProviding) { + public init( + model: NewTabPageCustomBackgroundProviding, + contextMenuPresenter: NewTabPageContextMenuPresenting = DefaultNewTabPageContextMenuPresenter() + ) { self.model = model + self.contextMenuPresenter = contextMenuPresenter model.backgroundPublisher .sink { [weak self] background in @@ -82,6 +89,7 @@ public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient { enum MessageName: String, CaseIterable { case autoOpen = "customizer_autoOpen" + case contextMenu = "customizer_contextMenu" case deleteImage = "customizer_deleteImage" case onBackgroundUpdate = "customizer_onBackgroundUpdate" case onImagesUpdate = "customizer_onImagesUpdate" @@ -93,6 +101,7 @@ public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient { public func registerMessageHandlers(for userScript: any SubfeatureWithExternalMessageHandling) { userScript.registerMessageHandlers([ + MessageName.contextMenu.rawValue: { [weak self] in try await self?.showContextMenu(params: $0, original: $1) }, MessageName.deleteImage.rawValue: { [weak self] in try await self?.deleteImage(params: $0, original: $1) }, MessageName.setBackground.rawValue: { [weak self] in try await self?.setBackground(params: $0, original: $1) }, MessageName.setTheme.rawValue: { [weak self] in try await self?.setTheme(params: $0, original: $1) }, @@ -100,6 +109,15 @@ public final class NewTabPageCustomBackgroundClient: NewTabPageScriptClient { ]) } + @MainActor + func showContextMenu(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let contextMenu: NewTabPageDataModel.UserImageContextMenu = DecodableHelper.decode(from: params) else { + return nil + } + await model.showContextMenu(for: contextMenu.id, using: contextMenuPresenter) + return nil + } + @MainActor func deleteImage(params: Any, original: WKScriptMessage) async throws -> Encodable? { guard let data: NewTabPageDataModel.DeleteImageData = DecodableHelper.decode(from: params) else { diff --git a/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageDataModel+CustomBackground.swift b/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageDataModel+CustomBackground.swift index 69ee33149e..3177e12c75 100644 --- a/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageDataModel+CustomBackground.swift +++ b/LocalPackages/NewTabPage/Sources/NewTabPage/CustomBackground/NewTabPageDataModel+CustomBackground.swift @@ -192,4 +192,13 @@ extension NewTabPageDataModel { struct DeleteImageData: Codable, Equatable { let id: String } + + struct UserImageContextMenu: Codable, Equatable { + let target: Target + let id: String + + enum Target: String, Codable, Equatable { + case userImage + } + } } diff --git a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageDataModel+Favorites.swift b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageDataModel+Favorites.swift index d57d112b31..e87a538cb2 100644 --- a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageDataModel+Favorites.swift +++ b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageDataModel+Favorites.swift @@ -48,28 +48,30 @@ extension NewTabPageDataModel { } struct Favorite: Encodable, Equatable { + let etldPlusOne: String? let favicon: FavoriteFavicon? let id: String let title: String let url: String - init(id: String, title: String, url: String, favicon: NewTabPageDataModel.FavoriteFavicon? = nil) { + init(id: String, title: String, url: String, etldPlusOne: String?, favicon: NewTabPageDataModel.FavoriteFavicon? = nil) { self.id = id self.title = title self.url = url self.favicon = favicon + self.etldPlusOne = etldPlusOne } @MainActor - init(_ bookmark: NewTabPageFavorite, preferredFaviconSize: Int, onFaviconMissing: () -> Void) { + init(_ bookmark: NewTabPageFavorite, preferredFaviconSize: Int) { id = bookmark.id title = bookmark.title url = bookmark.url + etldPlusOne = bookmark.etldPlusOne if let url = bookmark.urlObject, let duckFaviconURL = URL.duckFavicon(for: url) { favicon = FavoriteFavicon(maxAvailableSize: preferredFaviconSize, src: duckFaviconURL.absoluteString) } else { - onFaviconMissing() favicon = nil } } diff --git a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesActionsHandler.swift b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesActionsHandler.swift index 4a2faf672d..426be4ae3d 100644 --- a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesActionsHandler.swift +++ b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesActionsHandler.swift @@ -23,6 +23,7 @@ public protocol NewTabPageFavorite { var title: String { get } var url: String { get } var urlObject: URL? { get } + var etldPlusOne: String? { get } } public protocol FavoritesActionsHandling { @@ -31,7 +32,6 @@ public protocol FavoritesActionsHandling { @MainActor func open(_ url: URL, target: FavoriteOpenTarget) @MainActor func addNewFavorite() @MainActor func edit(_ favorite: FavoriteType) - @MainActor func onFaviconMissing() func removeFavorite(_ favorite: FavoriteType) func deleteBookmark(for favorite: FavoriteType) diff --git a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesClient.swift b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesClient.swift index 628d32a2e5..c211ef65c0 100644 --- a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesClient.swift +++ b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesClient.swift @@ -99,7 +99,7 @@ public final class NewTabPageFavoritesClient: NewTa @MainActor private func getData(params: Any, original: WKScriptMessage) async throws -> Encodable? { let favorites = favoritesModel.favorites.map { - NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize, onFaviconMissing: favoritesModel.onFaviconMissing) + NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize) } return NewTabPageDataModel.FavoritesData(favorites: favorites) } @@ -107,7 +107,7 @@ public final class NewTabPageFavoritesClient: NewTa @MainActor private func notifyDataUpdated(_ favorites: [NewTabPageFavorite]) { let favorites = favoritesModel.favorites.map { - NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize, onFaviconMissing: favoritesModel.onFaviconMissing) + NewTabPageDataModel.Favorite($0, preferredFaviconSize: preferredFaviconSize) } pushMessage(named: MessageName.onDataUpdate.rawValue, params: NewTabPageDataModel.FavoritesData(favorites: favorites)) } diff --git a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesModel.swift b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesModel.swift index 2502d3dab2..a268d07c23 100644 --- a/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesModel.swift +++ b/LocalPackages/NewTabPage/Sources/NewTabPage/Favorites/NewTabPageFavoritesModel.swift @@ -127,11 +127,6 @@ public final class NewTabPageFavoritesModel: NSObje actionsHandler.addNewFavorite() } - @MainActor - func onFaviconMissing() { - actionsHandler.onFaviconMissing() - } - // MARK: Context Menu @MainActor diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageCustomBackgroundProvider.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageCustomBackgroundProvider.swift index 072fa781d1..05e3696058 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageCustomBackgroundProvider.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageCustomBackgroundProvider.swift @@ -53,6 +53,11 @@ final class CapturingNewTabPageCustomBackgroundProvider: NewTabPageCustomBackgro deleteImageCalls.append(imageID) } + func showContextMenu(for imageID: String, using presenter: any NewTabPage.NewTabPageContextMenuPresenting) async { + showContextMenuCalls.append(imageID) + } + var presentUploadDialogCallsCount: Int = 0 var deleteImageCalls: [String] = [] + var showContextMenuCalls: [String] = [] } diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageFavoritesActionsHandler.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageFavoritesActionsHandler.swift index f0fde984b4..e7690f49cf 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageFavoritesActionsHandler.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/CapturingNewTabPageFavoritesActionsHandler.swift @@ -62,10 +62,6 @@ final class CapturingNewTabPageFavoritesActionsHandler: FavoritesActionsHandling editCalls.append(favorite) } - func onFaviconMissing() { - onFaviconMissingCallCount += 1 - } - func removeFavorite(_ favorite: MockNewTabPageFavorite) { removeFavoriteCalls.append(favorite) } diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/MockNewTabPageFavorite.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/MockNewTabPageFavorite.swift index d9cca07405..d948d54406 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/MockNewTabPageFavorite.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/Mocks/MockNewTabPageFavorite.swift @@ -26,4 +26,6 @@ struct MockNewTabPageFavorite: NewTabPageFavorite, Equatable { var urlObject: URL? { URL(string: url) } + + var etldPlusOne: String? } diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageCustomBackgroundClientTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageCustomBackgroundClientTests.swift index 960a2c156a..f2cc3f4c18 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageCustomBackgroundClientTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageCustomBackgroundClientTests.swift @@ -24,19 +24,29 @@ import XCTest final class NewTabPageCustomBackgroundClientTests: XCTestCase { private var client: NewTabPageCustomBackgroundClient! private var model: CapturingNewTabPageCustomBackgroundProvider! + private var contextMenuPresenter: CapturingNewTabPageContextMenuPresenter! private var userScript: NewTabPageUserScript! private var messageHelper: MessageHelper! override func setUpWithError() throws { try super.setUpWithError() model = CapturingNewTabPageCustomBackgroundProvider() - client = NewTabPageCustomBackgroundClient(model: model) + contextMenuPresenter = CapturingNewTabPageContextMenuPresenter() + client = NewTabPageCustomBackgroundClient(model: model, contextMenuPresenter: contextMenuPresenter) userScript = NewTabPageUserScript() messageHelper = .init(userScript: userScript) client.registerMessageHandlers(for: userScript) } + // MARK: - contextMenu + + func testThatContextMenuActionIsForwardedToTheModel() async throws { + let action = NewTabPageDataModel.UserImageContextMenu(target: .userImage, id: "abcd.jpg") + try await messageHelper.handleMessageExpectingNilResponse(named: .contextMenu, parameters: action) + XCTAssertEqual(model.showContextMenuCalls, ["abcd.jpg"]) + } + // MARK: - deleteImage func testThatDeleteImageCallsModel() async throws { diff --git a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift index b638bc2ba8..7714f8064d 100644 --- a/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift +++ b/LocalPackages/NewTabPage/Tests/NewTabPageTests/NewTabPageFavoritesClientTests.swift @@ -102,11 +102,11 @@ final class NewTabPageFavoritesClientTests: XCTestCase { ] let data: NewTabPageDataModel.FavoritesData = try await messageHelper.handleMessage(named: .getData) XCTAssertEqual(data.favorites, [ - .init(id: "1", title: "A", url: "https://a.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//a.com")), - .init(id: "10", title: "B", url: "https://b.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//b.com")), - .init(id: "5", title: "C", url: "https://c.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//c.com")), - .init(id: "2", title: "D", url: "https://d.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//d.com")), - .init(id: "3", title: "E", url: "https://e.com", favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//e.com")) + .init(id: "1", title: "A", url: "https://a.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//a.com")), + .init(id: "10", title: "B", url: "https://b.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//b.com")), + .init(id: "5", title: "C", url: "https://c.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//c.com")), + .init(id: "2", title: "D", url: "https://d.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//d.com")), + .init(id: "3", title: "E", url: "https://e.com", etldPlusOne: nil, favicon: .init(maxAvailableSize: 100, src: "duck://favicon/https%3A//e.com")) ]) } diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/LetterIconView.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/LetterIconView.swift index f0e70db952..f8ff72c110 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/LetterIconView.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/LetterIconView.swift @@ -26,14 +26,14 @@ public struct LetterIconView: View { public var characterCount: Int private var paddingModifier: CGFloat private var font: Font - private static let wwwPreffix = "www." + private static let wwwPrefix = "www." private var characters: String { if let prefferedFirstCharacters = prefferedFirstCharacters, prefferedFirstCharacters != "" { return String(prefferedFirstCharacters.prefix(characterCount)) } - return String(title.replacingOccurrences(of: Self.wwwPreffix, with: "").prefix(characterCount)) + return String(title.replacingOccurrences(of: Self.wwwPrefix, with: "").prefix(characterCount)) } /// Initializes a `LetterIconView` diff --git a/UnitTests/NewTabPage/NewTabPageCustomizationProviderTests.swift b/UnitTests/NewTabPage/NewTabPageCustomizationProviderTests.swift index 2fa49beae9..1379bfa843 100644 --- a/UnitTests/NewTabPage/NewTabPageCustomizationProviderTests.swift +++ b/UnitTests/NewTabPage/NewTabPageCustomizationProviderTests.swift @@ -249,6 +249,26 @@ final class NewTabPageCustomizationProviderTests: XCTestCase { XCTAssertEqual(userBackgroundImagesManager.deleteImageCallCount, 0) } + @MainActor + func testThatShowContextMenuPresentsTheMenuForTheSpecifiedImageID() async throws { + + final class CapturingNewTabPageContextMenuPresenter: NewTabPageContextMenuPresenting { + func showContextMenu(_ menu: NSMenu) { + showContextMenuCalls.append(menu) + } + var showContextMenuCalls: [NSMenu] = [] + } + + let contextMenuPresenter = CapturingNewTabPageContextMenuPresenter() + await provider.showContextMenu(for: "abcd.jpg", using: contextMenuPresenter) + + let menu = try XCTUnwrap(contextMenuPresenter.showContextMenuCalls.first) + + let deleteBackgroundItem = try XCTUnwrap(menu.item(at: 0)) + let imageID = try XCTUnwrap(deleteBackgroundItem.representedObject as? String) + XCTAssertEqual(imageID, "abcd.jpg") + } + // MARK: - Helpers /**