| /* |
| * Copyright (C) 2025 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "CocoaHelpers.h" |
| #import "config.h" |
| #import "HTTPServer.h" |
| #import "WebExtensionUtilities.h" |
| #import <WebKit/WKWebExtensionPermissionPrivate.h> |
| |
| #if ENABLE(WK_WEB_EXTENSIONS_BOOKMARKS) |
| |
| #import <WebKit/WKPreferencesPrivate.h> |
| #import <WebKit/WKWebExtensionControllerDelegatePrivate.h> |
| #import <WebKit/_WKFeature.h> |
| #import <WebKit/_WKWebExtensionBookmarks.h> |
| |
| |
| @interface _MockBookmarkNode : NSObject <_WKWebExtensionBookmark> |
| - (instancetype)initWithDictionary:(NSDictionary *)dictionary; |
| @property (nonatomic, strong) NSMutableDictionary *dictionary; |
| @end |
| |
| @implementation _MockBookmarkNode |
| - (instancetype)initWithDictionary:(NSDictionary *)dictionary |
| { |
| self = [super init]; |
| if (self) |
| _dictionary = [dictionary mutableCopy]; |
| return self; |
| } |
| |
| - (NSString *)identifierForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| return _dictionary[@"id"]; |
| } |
| |
| - (NSString *)parentIdentifierForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| return _dictionary[@"parentId"]; |
| } |
| |
| - (NSString *)titleForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| return _dictionary[@"title"]; |
| } |
| |
| - (NSString *)urlStringForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| return _dictionary[@"url"]; |
| } |
| |
| - (NSInteger)indexForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| return [_dictionary[@"index"] integerValue]; |
| } |
| |
| - (_WKWebExtensionBookmarkType)bookmarkTypeForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| NSString *typeString = self.dictionary[@"type"]; |
| if ([typeString isEqualToString:@"folder"]) |
| return _WKWebExtensionBookmarkTypeFolder; |
| |
| NSString *url = self.dictionary[@"url"]; |
| if (url && url.length > 0) |
| return _WKWebExtensionBookmarkTypeBookmark; |
| return _WKWebExtensionBookmarkTypeFolder; |
| } |
| |
| - (NSArray<id<_WKWebExtensionBookmark>> *)childrenForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| NSArray *childDictionaries = _dictionary[@"children"]; |
| if (!childDictionaries) |
| return nil; |
| NSMutableArray<id<_WKWebExtensionBookmark>> *children = [NSMutableArray array]; |
| for (NSDictionary *childDict in childDictionaries) |
| [children addObject:[[_MockBookmarkNode alloc] initWithDictionary:childDict]]; |
| return children; |
| } |
| |
| - (NSDate *)dateAddedForWebExtensionContext:(WKWebExtensionContext *)context |
| { |
| NSNumber *dateValue = WebKit::objectForKey<NSNumber>(_dictionary, @"dateAdded"); |
| if (!dateValue) |
| return nil; |
| |
| double millisecondsSinceEpoch = dateValue.doubleValue; |
| NSTimeInterval secondsSinceEpoch = millisecondsSinceEpoch / 1000.0; |
| |
| return [NSDate dateWithTimeIntervalSince1970:secondsSinceEpoch]; |
| } |
| |
| @end |
| |
| |
| @interface TestBookmarksDelegate : NSObject <WKWebExtensionControllerDelegatePrivate> |
| @property (nonatomic, strong) NSMutableArray<NSMutableDictionary *> *mockBookmarks; |
| @property (nonatomic) NSInteger nextMockBookmarkId; |
| @end |
| |
| static NSMutableDictionary *findParentInMockTree(NSMutableArray<NSMutableDictionary *> *tree, NSString *parentId) |
| { |
| for (NSMutableDictionary *node in tree) { |
| if ([node[@"id"] isEqualToString:parentId]) |
| return node; |
| id children = node[@"children"]; |
| if (children && [children isKindOfClass:[NSMutableArray class]]) { |
| NSMutableDictionary *found = findParentInMockTree(children, parentId); |
| if (found) |
| return found; |
| } |
| } |
| return nil; |
| } |
| |
| static NSMutableDictionary *findBookmarkAndParentArrayInMockTree(NSMutableArray *children, NSString *bookmarkId) |
| { |
| if (!children.count) |
| return nil; |
| |
| for (NSMutableDictionary *childDict in children) { |
| if ([childDict[@"id"] isEqualToString:bookmarkId]) |
| return [@{ @"bookmark": childDict, @"parentChildren": children } mutableCopy]; |
| if ([childDict[@"type"] isEqualToString:@"folder"] && [childDict[@"children"] isKindOfClass:[NSMutableArray class]]) { |
| NSMutableDictionary *found = findBookmarkAndParentArrayInMockTree(childDict[@"children"], bookmarkId); |
| if (found) |
| return found; |
| } |
| } |
| return nil; |
| } |
| |
| NSMutableDictionary* findBookmarkInMockTree(NSMutableArray *tree, NSString *bookmarkId) |
| { |
| if (!tree || !bookmarkId) |
| return nil; |
| for (NSMutableDictionary *item in tree) { |
| if ([item[@"id"] isEqualToString:bookmarkId]) |
| return item; |
| if ([item[@"type"] isEqualToString:@"folder"] && item[@"children"]) { |
| NSMutableDictionary *foundInChild = findBookmarkInMockTree(item[@"children"], bookmarkId); |
| if (foundInChild) |
| return foundInChild; |
| } |
| } |
| return nil; |
| } |
| |
| |
| @implementation TestBookmarksDelegate |
| - (instancetype)init |
| { |
| self = [super init]; |
| if (self) { |
| _mockBookmarks = [NSMutableArray array]; |
| _nextMockBookmarkId = 100; |
| } |
| return self; |
| } |
| |
| - (void)_webExtensionController:(WKWebExtensionController *)controller bookmarksForExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSArray<NSObject<_WKWebExtensionBookmark> *> *, NSError *))completionHandler |
| { |
| NSMutableArray<NSObject<_WKWebExtensionBookmark> *> *results = [NSMutableArray array]; |
| for (NSDictionary *bookmarkDict in self.mockBookmarks) |
| [results addObject:[[_MockBookmarkNode alloc] initWithDictionary:bookmarkDict]]; |
| completionHandler(results, nil); |
| } |
| |
| - (void)_webExtensionController:(WKWebExtensionController *)controller createBookmarkWithParentIdentifier:(NSString *)parentId index:(NSNumber *)index url:(NSString *)url title:(NSString *)title forExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSObject<_WKWebExtensionBookmark> *, NSError *))completionHandler |
| { |
| NSMutableDictionary *newBookmark = [NSMutableDictionary dictionary]; |
| newBookmark[@"title"] = title; |
| if (url) |
| newBookmark[@"url"] = url; |
| NSString *newId = [NSString stringWithFormat:@"%ld", (long)self.nextMockBookmarkId]; |
| _nextMockBookmarkId++; |
| newBookmark[@"id"] = newId; |
| |
| if (!newBookmark[@"type"]) { |
| NSString *url = newBookmark[@"url"]; |
| newBookmark[@"type"] = (url && url.length > 0) ? @"bookmark" : @"folder"; |
| } |
| |
| if (parentId && ![parentId isEqualToString:@"0"]) { |
| NSMutableDictionary *parentDict = findParentInMockTree(self.mockBookmarks, parentId); |
| if (parentDict) { |
| NSMutableArray *children = [parentDict[@"children"] mutableCopy] ?: [NSMutableArray array]; |
| newBookmark[@"parentId"] = parentId; |
| newBookmark[@"index"] = index ?: @(children.count); |
| [children addObject:newBookmark]; |
| parentDict[@"children"] = children; |
| |
| completionHandler([[_MockBookmarkNode alloc] initWithDictionary:newBookmark], nil); |
| return; |
| } |
| } |
| |
| newBookmark[@"parentId"] = @"0"; |
| newBookmark[@"index"] = index ?: @(self.mockBookmarks.count); |
| [self.mockBookmarks addObject:newBookmark]; |
| |
| completionHandler([[_MockBookmarkNode alloc] initWithDictionary:newBookmark], nil); |
| } |
| @end |
| |
| namespace TestWebKitAPI { |
| |
| #pragma mark - Constants |
| |
| static constexpr auto *bookmarkOffManifest = @{ |
| @"manifest_version": @3, |
| @"name": @"bookmarkpermission off Test", |
| @"description": @"bookmarkpermission off Test", |
| @"version": @"1", |
| |
| @"permissions": @[], |
| @"background": @{ |
| @"scripts": @[ @"background.js", ], |
| @"type": @"module", |
| @"persistent": @NO, |
| }, |
| }; |
| |
| static constexpr auto *bookmarkOnManifest = @{ |
| @"manifest_version": @3, |
| @"name": @"bookmarkpermission on Test", |
| @"description": @"bookmarkpermission on Test", |
| @"version": @"1", |
| |
| @"permissions": @[ @"bookmarks" ], |
| @"background": @{ |
| @"scripts": @[ @"background.js", ], |
| @"type": @"module", |
| @"persistent": @NO, |
| }, |
| @"action": @{ |
| @"default_title": @"Test Action", |
| @"default_popup": @"popup.html", |
| }, |
| }; |
| |
| #pragma mark - Test Fixture |
| |
| class WKWebExtensionAPIBookmarks : public testing::Test { |
| protected: |
| WKWebExtensionAPIBookmarks() |
| { |
| bookmarkConfig = WKWebExtensionControllerConfiguration.nonPersistentConfiguration; |
| if (!bookmarkConfig.webViewConfiguration) |
| bookmarkConfig.webViewConfiguration = [[WKWebViewConfiguration alloc] init]; |
| |
| for (_WKFeature *feature in [WKPreferences _features]) { |
| if ([feature.key isEqualToString:@"WebExtensionBookmarksEnabled"]) |
| [bookmarkConfig.webViewConfiguration.preferences _setEnabled:YES forFeature:feature]; |
| } |
| } |
| |
| RetainPtr<NSMutableArray> uiProcessMockBookmarks; |
| NSInteger nextMockBookmarkId; |
| |
| void SetUp() override |
| { |
| testing::Test::SetUp(); |
| uiProcessMockBookmarks = adoptNS([NSMutableArray new]); |
| nextMockBookmarkId = 100; |
| } |
| |
| RetainPtr<TestWebExtensionManager> getManagerFor(NSArray<NSString *> *script, NSDictionary<NSString *, id> *manifest) |
| { |
| return getManagerFor(@{ @"background.js" : Util::constructScript(script) }, manifest); |
| } |
| |
| RetainPtr<TestWebExtensionManager> getManagerFor(NSDictionary<NSString *, id> *resources, NSDictionary<NSString *, id> *manifest) |
| { |
| return Util::parseExtension(manifest, resources, bookmarkConfig); |
| } |
| |
| void configureCreateBookmarkDelegate(TestWebExtensionManager *manager) |
| { |
| manager.internalDelegate.createBookmarkWithParentIdentifier = ^(NSString *parentId, NSNumber *index, NSString *url, NSString *title, void (^completionHandler)(NSObject<_WKWebExtensionBookmark>*, NSError*)) { |
| NSMutableDictionary *newBookmarkData = [NSMutableDictionary dictionary]; |
| newBookmarkData[@"title"] = title; |
| if (url) |
| newBookmarkData[@"url"] = url; |
| double dateAddedInMilliseconds = NSDate.date.timeIntervalSince1970 * 1000.0; |
| newBookmarkData[@"dateAdded"] = @(dateAddedInMilliseconds); |
| |
| if (!newBookmarkData[@"type"]) { |
| NSString *url = newBookmarkData[@"url"]; |
| newBookmarkData[@"type"] = (url && url.length > 0) ? @"bookmark" : @"folder"; |
| } |
| |
| NSString *newId = [NSString stringWithFormat:@"%ld", (long)this->nextMockBookmarkId]; |
| this->nextMockBookmarkId++; |
| newBookmarkData[@"id"] = newId; |
| |
| if (!newBookmarkData[@"type"]) { |
| NSString *url = newBookmarkData[@"url"]; |
| newBookmarkData[@"type"] = (url && url.length > 0) ? @"bookmark" : @"folder"; |
| } |
| |
| newBookmarkData[@"parentId"] = parentId; |
| if (parentId && ![parentId isEqualToString:@"0"]) { |
| NSMutableDictionary *parentDict = findParentInMockTree(this->uiProcessMockBookmarks.get(), parentId); |
| if (parentDict) { |
| NSMutableArray *children = [parentDict[@"children"] mutableCopy] ?: [NSMutableArray array]; |
| newBookmarkData[@"parentId"] = parentId; |
| newBookmarkData[@"index"] = index ?: @(children.count); |
| [children addObject:newBookmarkData]; |
| parentDict[@"children"] = children; |
| completionHandler(adoptNS([[_MockBookmarkNode alloc] initWithDictionary:newBookmarkData]).get(), nil); |
| return; |
| } |
| } |
| |
| newBookmarkData[@"index"] = index ?: @(this->uiProcessMockBookmarks.get().count); |
| [this->uiProcessMockBookmarks addObject:newBookmarkData]; |
| completionHandler(adoptNS([[_MockBookmarkNode alloc] initWithDictionary:newBookmarkData]).get(), nil); |
| }; |
| } |
| |
| void configureGetBookmarksDelegate(TestWebExtensionManager *manager) |
| { |
| manager.internalDelegate.bookmarksForExtensionContext = ^(void (^completionHandler)(NSArray<NSObject<_WKWebExtensionBookmark> *>*, NSError*)) { |
| NSMutableArray<NSObject<_WKWebExtensionBookmark>*> *results = [NSMutableArray array]; |
| for (NSDictionary *dict in this->uiProcessMockBookmarks.get()) |
| [results addObject:[[_MockBookmarkNode alloc] initWithDictionary:dict]]; |
| completionHandler(results, nil); |
| }; |
| } |
| |
| void configureRemoveBookmarksDelegate(TestWebExtensionManager *manager) |
| { |
| manager.internalDelegate.removeBookmarkWithIdentifier = ^(NSString *bookmarkId, BOOL removeFolderWithChildren, void (^completionHandler)(NSError *)) { |
| NSMutableDictionary *foundInfo = findBookmarkAndParentArrayInMockTree(uiProcessMockBookmarks.get(), bookmarkId); |
| |
| if (!foundInfo) { |
| completionHandler([NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Bookmark with ID '%@' not found.", bookmarkId] }]); |
| return; |
| } |
| |
| NSMutableDictionary *foundBookmarkDict = foundInfo[@"bookmark"]; |
| NSMutableArray *parentChildrenArray = foundInfo[@"parentChildren"]; |
| NSString *foundBookmarkType = foundBookmarkDict[@"type"]; |
| |
| if (!removeFolderWithChildren) { |
| if ([foundBookmarkType isEqualToString:@"folder"]) { |
| NSArray *folderChildren = foundBookmarkDict[@"children"]; |
| |
| if (folderChildren.count) { |
| completionHandler([NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Bookmark with ID '%@' is a non-empty folder and cannot be removed with bookmarks.remove(). Use bookmarks.removeTree().", bookmarkId] }]); |
| return; |
| } |
| } |
| } else { |
| if ([foundBookmarkType isEqualToString:@"bookmark"]) { |
| completionHandler([NSError errorWithDomain:NSCocoaErrorDomain code:NSFileWriteUnknownError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Can't remove a non-folder item '%@' with bookmarks.removeTree(). Use bookmarks.remove().", bookmarkId] }]); |
| return; |
| } |
| } |
| |
| if (parentChildrenArray) { |
| [parentChildrenArray removeObject:foundBookmarkDict]; |
| completionHandler(nil); |
| } else |
| completionHandler([NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: @"Failed to remove bookmark from top level (parent array not found)." }]); |
| }; |
| } |
| |
| void configureUpdateBookmarksDelegate(TestWebExtensionManager *manager) |
| { |
| manager.internalDelegate.updateBookmarkWithIdentifier = ^(NSString *bookmarkId, NSString *title, NSString *url, void (^completionHandler)(NSObject<_WKWebExtensionBookmark> *, NSError *)) { |
| NSMutableDictionary *bookmarkToUpdate = findBookmarkInMockTree(uiProcessMockBookmarks.get(), bookmarkId); |
| if (!bookmarkToUpdate) { |
| completionHandler(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Bookmark with ID '%@' not found.", bookmarkId] }]); |
| return; |
| } |
| |
| if (title) |
| bookmarkToUpdate[@"title"] = title; |
| |
| if (url) { |
| NSString *bookmarkType = bookmarkToUpdate[@"type"]; |
| if ([bookmarkType isEqualToString:@"folder"]) { |
| completionHandler(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Cannot update URL for a %@ (ID: %@).", bookmarkType, bookmarkId] }]); |
| return; |
| } |
| bookmarkToUpdate[@"url"] = url; |
| } |
| |
| _MockBookmarkNode *updatedMockNode = [[_MockBookmarkNode alloc] initWithDictionary:bookmarkToUpdate]; |
| completionHandler(updatedMockNode, nil); |
| }; |
| } |
| |
| void configureMoveBookmarksDelegate(TestWebExtensionManager *manager) |
| { |
| manager.internalDelegate.moveBookmarkWithIdentifier = ^(NSString *bookmarkId, NSString *toParentId, NSNumber *atIndex, void (^completionHandler)(NSObject<_WKWebExtensionBookmark> *, NSError *)) { |
| NSMutableDictionary *foundInfo = findBookmarkAndParentArrayInMockTree(uiProcessMockBookmarks.get(), bookmarkId); |
| if (!foundInfo) { |
| completionHandler(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Bookmark with ID '%@' not found for move.", bookmarkId] }]); |
| return; |
| } |
| |
| NSMutableDictionary *bookmarkToMove = foundInfo[@"bookmark"]; |
| NSMutableArray *oldParentChildrenArray = foundInfo[@"parentChildren"]; |
| |
| NSUInteger oldIndex = [oldParentChildrenArray indexOfObject:bookmarkToMove]; |
| if (oldIndex == NSNotFound) { |
| completionHandler(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: @"Bookmark found but not in its reported parent array." }]); |
| return; |
| } |
| |
| NSMutableArray *newParentChildrenArray = nil; |
| if (!toParentId) |
| newParentChildrenArray = uiProcessMockBookmarks.get(); |
| else { |
| NSMutableDictionary *newParent = findBookmarkInMockTree(uiProcessMockBookmarks.get(), toParentId); |
| if (!newParent || ![newParent[@"type"] isEqualToString:@"folder"]) { |
| completionHandler(nil, [NSError errorWithDomain:NSCocoaErrorDomain code:NSExecutableRuntimeMismatchError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"New parent folder with ID '%@' not found or not a folder.", toParentId] }]); |
| return; |
| } |
| newParentChildrenArray = newParent[@"children"]; |
| } |
| |
| NSUInteger rawTargetIndex = atIndex ? atIndex.unsignedIntegerValue : newParentChildrenArray.count; |
| [oldParentChildrenArray removeObjectAtIndex:oldIndex]; |
| |
| if (oldParentChildrenArray == newParentChildrenArray && oldIndex < rawTargetIndex) |
| rawTargetIndex--; |
| |
| NSUInteger finalTargetIndex = rawTargetIndex; |
| if (finalTargetIndex > newParentChildrenArray.count) |
| finalTargetIndex = newParentChildrenArray.count; |
| |
| bookmarkToMove[@"parentId"] = toParentId ?: @"0"; |
| NSUInteger targetIndex = atIndex ? atIndex.unsignedIntegerValue : newParentChildrenArray.count; |
| if (targetIndex > newParentChildrenArray.count) |
| targetIndex = newParentChildrenArray.count; |
| |
| [newParentChildrenArray insertObject:bookmarkToMove atIndex:targetIndex]; |
| bookmarkToMove[@"index"] = @(targetIndex); |
| |
| _MockBookmarkNode *movedMockNode = [[_MockBookmarkNode alloc] initWithDictionary:bookmarkToMove]; |
| completionHandler(movedMockNode, nil); |
| }; |
| } |
| |
| WKWebExtensionControllerConfiguration *bookmarkConfig; |
| }; |
| |
| #pragma mark - Common Tests |
| |
| TEST_F(WKWebExtensionAPIBookmarks, APISUnavailableWhenManifestDoesNotRequest) |
| { |
| auto *script = @[ |
| @"browser.test.assertDeepEq(browser.bookmarks, undefined)", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| Util::loadAndRunExtension(bookmarkOffManifest, @{ @"background.js": Util::constructScript(script) }, bookmarkConfig); |
| } |
| |
| #pragma mark - more Tests |
| |
| TEST_F(WKWebExtensionAPIBookmarks, APIAvailableWhenManifestRequests) |
| { |
| auto *script = @[ |
| @"browser.test.assertFalse(browser.bookmarks === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.create === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.getChildren === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.getRecent === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.getSubTree === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.getTree === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.get === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.move === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.remove === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.removeTree === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.search === undefined)", |
| @"browser.test.assertFalse(browser.bookmarks.update === undefined)", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| Util::loadAndRunExtension(bookmarkOnManifest, @{ @"background.js": Util::constructScript(script) }, bookmarkConfig); |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIDisallowsMissingArguments) |
| { |
| auto *script = @[ |
| @"browser.test.assertThrows(() => browser.bookmarks.create())", |
| @"browser.test.assertThrows(() => browser.bookmarks.getChildren())", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent())", |
| @"browser.test.assertThrows(() => browser.bookmarks.getSubTree())", |
| @"browser.test.assertThrows(() => browser.bookmarks.get())", |
| @"browser.test.assertThrows(() => browser.bookmarks.move())", |
| @"browser.test.assertThrows(() => browser.bookmarks.remove())", |
| @"browser.test.assertThrows(() => browser.bookmarks.removeTree())", |
| @"browser.test.assertThrows(() => browser.bookmarks.search())", |
| @"browser.test.assertThrows(() => browser.bookmarks.update())", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| Util::loadAndRunExtension(bookmarkOnManifest, @{ @"background.js": Util::constructScript(script) }, bookmarkConfig); |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIDisallowedIncorrectArguments) |
| { |
| auto *script = @[ |
| @"browser.test.assertThrows(() => browser.bookmarks.getChildren(123), /The 'id' value is invalid, because a string is expected/i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.getChildren({}), /The 'id' value is invalid, because a string is expected/i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent('not-a-number'), /The 'numberOfItems' value is invalid, because a number is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent({}), /The 'numberOfItems' value is invalid, because a number is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.get('test', 'test'), /The 'callback' value is invalid, because a function is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.move(123, {}), /The 'id' value is invalid, because a string is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.move('someId', 'not-an-object'), /The 'destination' value is invalid, because an object is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.remove(123), /The 'id' value is invalid, because a string is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.search(123, 'test'), /The 'callback' value is invalid, because a function is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.update(123, {}), /The 'id' value is invalid, because a string is expected./i)", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| Util::loadAndRunExtension(bookmarkOnManifest, @{ @"background.js": Util::constructScript(script) }, bookmarkConfig); |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPICheckgetRecent) |
| { |
| auto *script = @[ |
| @"browser.test.assertThrows(() => browser.bookmarks.getChildren(123), /The 'id' value is invalid, because a string is expected/i)", |
| @"browser.test.log('workingtest line1')", |
| @"browser.test.assertThrows(() => browser.bookmarks.getChildren({}), /The 'id' value is invalid, because a string is expected/i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent('not-a-number'), /The 'numberOfItems' value is invalid, because a number is expected./i)", |
| @"browser.test.log('workingtest line2')", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent({}), /The 'numberOfItems' value is invalid, because a number is expected./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent(-1), /The 'numberOfItems' value is invalid, because it must be at least 1./i)", |
| @"browser.test.assertThrows(() => browser.bookmarks.getRecent(0), /The 'numberOfItems' value is invalid, because it must be at least 1./i)", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| Util::loadAndRunExtension(bookmarkOnManifest, @{ @"background.js": Util::constructScript(script) }, bookmarkConfig); |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIGetRecent) |
| { |
| auto *script = @[ |
| @"let oldBm = await browser.bookmarks.create({title: 'Oldest Bookmark', url: 'http://example.com/1'})", |
| @"let midBm = await browser.bookmarks.create({title: 'Middle Bookmark', url: 'http://example.com/2'})", |
| @"let newBm = await browser.bookmarks.create({title: 'Newest Bookmark', url: 'http://example.com/3'})", |
| @"let recent = await browser.bookmarks.getRecent(2)", |
| @"browser.test.assertEq(2, recent.length, 'Should return exactly 2 bookmarks')", |
| @"browser.test.assertEq('Newest Bookmark', recent[0].title, 'First result should be the newest bookmark')", |
| @"browser.test.assertEq('Middle Bookmark', recent[1].title, 'Second result should be the middle bookmark')", |
| @"let newFolder = await browser.bookmarks.create({title: 'Newest Folder'})", |
| @"let recent2 = await browser.bookmarks.getRecent(5)", |
| @"browser.test.assertEq(3, recent2.length, 'Should adapt and return the max available which is 3')", |
| @"browser.test.assertEq('Newest Bookmark', recent2[0].title, 'First result should be the newest bookmark')", |
| @"browser.test.assertEq('Middle Bookmark', recent2[1].title, 'Second result should be the middle bookmark')", |
| @"browser.test.assertEq('Oldest Bookmark', recent2[2].title, 'Second result should be the middle bookmark')", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPICreate) |
| { |
| auto *script = @[ |
| @"let createdNode = await browser.bookmarks.create({title: 'My Test Bookmark', url: 'https://example.com/test1'})", |
| @"browser.test.assertEq('My Test Bookmark', createdNode.title, 'Title should match');", |
| @"browser.test.assertEq('https://example.com/test1', createdNode.url, 'URL should match');", |
| @"browser.test.assertEq('bookmark', createdNode.type, 'Type should be bookmark');", |
| @"let createdNode2 = await browser.bookmarks.create({title: 'My Test Folder'})", |
| @"browser.test.assertEq('My Test Folder', createdNode2.title, 'Title should match');", |
| @"browser.test.assertEq('folder', createdNode2.type, 'type should be folder because url is not specified');", |
| @"let createdNode3 = await browser.bookmarks.create({title: 'My Children Folder', parentId: createdNode2.id})", |
| @"browser.test.assertEq('My Children Folder', createdNode3.title, 'Title should match');", |
| @"browser.test.assertEq('folder', createdNode3.type, 'type should be folder because url is not specified');", |
| @"browser.test.assertEq(createdNode2.id, createdNode3.parentId, 'parentId should match');", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIGetTree) |
| { |
| auto *script = @[ |
| @"let bookmark1 = await browser.bookmarks.create({type: 'bookmark', title: 'Top Bookmark 1', url: 'http://example.com/bm1'});", |
| @"let folder1 = await browser.bookmarks.create({type: 'folder', title: 'Top Folder 1'});", |
| @"let bookmark2 = await browser.bookmarks.create({title: 'Child Bookmark 2', url: 'http://example.com/bm2', parentId: folder1.id});", |
| @"let bookmark3 = await browser.bookmarks.create({title: 'Top Bookmark 3', url: 'http://example.com/bm3'});", |
| @"let root = await browser.bookmarks.getTree();", |
| @"browser.test.assertTrue(Array.isArray(root), 'Root object should have a children array');", |
| @"browser.test.assertTrue(root.length >= 1, 'Root should have at least one child (default folder)');", |
| @"let foundBookmark1 = root.find(n => n.title === bookmark1.title);", |
| @"browser.test.assertEq('Top Bookmark 1', foundBookmark1.title, 'Bm1 title matches');", |
| @"browser.test.assertEq('http://example.com/bm1', foundBookmark1.url, 'Bm1 URL matches');", |
| @"browser.test.assertEq('bookmark', foundBookmark1.type, 'Bm1 type is bookmark');", |
| @"let foundFolder1 = root.find(n => n.title === folder1.title);", |
| @"browser.test.assertEq('Top Folder 1', foundFolder1.title, 'Folder1 title matches');", |
| @"browser.test.assertEq('folder', foundFolder1.type, 'Folder1 type is folder');", |
| @"browser.test.assertEq(bookmark2.title, foundFolder1.children[0].title, 'Folder1 should have children array');", |
| @"let foundBookmark2 = foundFolder1.children.find(n => n.title === bookmark2.title);", |
| @"browser.test.assertEq('Child Bookmark 2', foundBookmark2.title, 'Bm2 title matches');", |
| @"browser.test.assertEq('http://example.com/bm2', foundBookmark2.url, 'Bm2 URL matches');", |
| @"browser.test.assertEq('bookmark', foundBookmark2.type, 'Bm2 type is bookmark');", |
| @"browser.test.assertEq(folder1.id, foundBookmark2.parentId, 'Bm2 parentId matches Folder1');", |
| @"browser.test.notifyPass()", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPICreateAndGetTree) |
| { |
| auto *script = @[ |
| @"let folder = await browser.bookmarks.create({ title: 'Test Folder' });", |
| @"browser.test.assertEq(folder.title, 'Test Folder', 'Folder title should be correct');", |
| @"browser.test.assertTrue(!!folder.id, 'Folder should have an ID');", |
| @"let bookmark = await browser.bookmarks.create({ parentId: folder.id, title: 'WebKit.org', url: 'https://webkit.org/' });", |
| @"browser.test.assertEq(bookmark.title, 'WebKit.org', 'Bookmark title should be correct');", |
| @"browser.test.assertEq(bookmark.url, 'https://webkit.org/', 'Bookmark URL should be correct');", |
| @"let rootNode = await browser.bookmarks.getTree();", |
| @"browser.test.assertTrue(Array.isArray(rootNode), 'Root object should have a children array');", |
| @"browser.test.assertEq(rootNode.length, 1);", |
| @"browser.test.assertEq(rootNode[0].title, 'Test Folder');", |
| @"browser.test.assertTrue(Array.isArray(rootNode[0].children), 'Folder should have a children array');", |
| @"browser.test.assertEq(rootNode[0].children[0].title, 'WebKit.org', 'Child bookmark in tree should have correct title');", |
| @"browser.test.assertEq(rootNode[0].children[0].url, 'https://webkit.org/', 'Child bookmark in tree should have correct URL');", |
| @"let bookmark2 = await browser.bookmarks.create({ id: 'topLevelBookmark', title: 'Test Top Bookmark', url: 'https://coolbook.com/' });", |
| @"browser.test.assertEq(bookmark2.title, 'Test Top Bookmark', 'Bookmark title should be correct');", |
| @"browser.test.assertEq(bookmark2.url, 'https://coolbook.com/', 'Bookmark URL should be correct');", |
| @"let updatedRootNode = await browser.bookmarks.getTree();", |
| @"browser.test.assertEq(updatedRootNode.length, 2);", |
| @"browser.test.assertEq(updatedRootNode[0].title, 'Test Folder');", |
| @"browser.test.assertEq(updatedRootNode[1].title, 'Test Top Bookmark');", |
| @"browser.test.notifyPass();", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIGetSubTreeChildren) |
| { |
| auto *script = @[ |
| @"let folder = await browser.bookmarks.create({title: 'Test Folder' });", |
| @"browser.test.assertEq(folder.title, 'Test Folder', 'Folder title should be correct');", |
| @"browser.test.assertTrue(!!folder.id, 'Folder should have an ID');", |
| @"let bookmark = await browser.bookmarks.create({parentId: folder.id, title: 'WebKit.org', url: 'https://webkit.org/' });", |
| @"browser.test.assertEq(bookmark.title, 'WebKit.org', 'Bookmark title should be correct');", |
| @"browser.test.assertEq(bookmark.url, 'https://webkit.org/', 'Bookmark URL should be correct');", |
| @"let folder2 = await browser.bookmarks.create({parentId: folder.id, title: 'Folder 2'});", |
| @"browser.test.assertEq(folder2.title, 'Folder 2', 'Bookmark title should be correct');", |
| @"browser.test.assertEq(folder2.parentId, folder.id, 'Bookmark URL should be correct');", |
| @"let bookmark2 = await browser.bookmarks.create({parentId: folder2.id, title: 'Bookmark 2', url: 'https://bookmark2.org/' });", |
| @"browser.test.log(`Starting bookmark test right before...: ${JSON.stringify(folder.id)}`);", |
| @"let subtreeFolder1 = await browser.bookmarks.getSubTree(folder.id);", |
| @"browser.test.assertTrue(Array.isArray(subtreeFolder1), 'subtreeFolder1 should be an array');", |
| @"browser.test.assertEq(2, subtreeFolder1.length, 'subtreeFolder1 array should have 2 elements');", |
| @"browser.test.assertEq('Folder 2', subtreeFolder1[1].title, 'childs title should be Folder 2');", |
| @"browser.test.assertEq('folder', subtreeFolder1[1].type, 'type should be folder');", |
| @"browser.test.assertTrue(Array.isArray(subtreeFolder1[1].children), 'Folder 2 should have children');", |
| @"browser.test.assertEq(1, subtreeFolder1[1].children.length, 'folder 2 should have 1 child');", |
| @"browser.test.assertEq(bookmark2.title, subtreeFolder1[1].children[0].title, 'bookmark2 should be child of folder2');", |
| @"browser.test.assertEq(bookmark.id, subtreeFolder1[0].id, 'Second child is bookmark');", |
| @"let subtreeBookmark = await browser.bookmarks.getSubTree(bookmark.id);", |
| @"browser.test.assertTrue(Array.isArray(subtreeBookmark), 'subtreeBookmark should be an array');", |
| @"browser.test.assertEq(0, subtreeBookmark.length, 'subtreeBookmark array should have 1 element');", |
| @"let childrenFolder1 = await browser.bookmarks.getChildren(folder.id);", |
| @"browser.test.assertTrue(Array.isArray(childrenFolder1), 'childrenFolder1 should be an array');", |
| @"browser.test.assertEq('Folder 2', childrenFolder1[1].title, 'childs title should be Folder 2');", |
| @"browser.test.log(`Starting bookmark test right before...: ${JSON.stringify(folder2.id)}`);", |
| @"let getFolder1 = await browser.bookmarks.get([folder.id, folder2.id]);", |
| @"browser.test.assertTrue(Array.isArray(getFolder1), 'getFolder1 should be an array');", |
| @"browser.test.assertEq(2, getFolder1.length, 'getFolder1 array should have 2 elements');", |
| @"browser.test.assertEq('Folder 2', getFolder1[1].title, 'childs title should be Folder 2');", |
| @"browser.test.assertEq('Test Folder', getFolder1[0].title, 'childs title should be Folder 2');", |
| @"browser.test.notifyPass();", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIRemoveAndRemoveTree) |
| { |
| auto script = @[ |
| @"let folder1 = await browser.bookmarks.create({ title: 'Folder One' });", |
| @"let bookmarkA = await browser.bookmarks.create({ parentId: folder1.id, title: 'Bookmark A', url: 'http://example.com/a' });", |
| @"let folder2 = await browser.bookmarks.create({ parentId: folder1.id, title: 'Folder Two' });", |
| @"let bookmarkB = await browser.bookmarks.create({ parentId: folder2.id, title: 'Bookmark B', url: 'http://example.com/b' });", |
| @"let topLevelBookmark = await browser.bookmarks.create({ title: 'Top Level Bookmark', url: 'http://example.com/top' });", |
| @"let tree1 = await browser.bookmarks.getTree();", |
| @"browser.test.assertEq(2, tree1[0].children.length, 'folder1 should have 2 children');", |
| @"browser.test.assertEq('Top Level Bookmark', tree1[1].title, 'Second top-level is topBookmark');", |
| @"browser.test.assertEq('Bookmark A', tree1[0].children[0].title, 'bookmarkA is child of folder1');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.removeTree(bookmarkA.id),", |
| @"/Can't remove a non-folder item '.*' with bookmarks.removeTree\\(\\). Use bookmarks.remove\\(\\)./,", |
| @"'FAIL-1: removeTree on a bookmark should fail with the correct message');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.remove(folder2.id),", |
| @"/Bookmark with ID '.*' is a non-empty folder and cannot be removed with bookmarks.remove\\(\\). Use bookmarks.removeTree\\(\\)./,", |
| @"'FAIL-2: remove on a non-empty folder should fail with the correct message');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.remove('nonexistent-id-123'),", |
| @"/Bookmark with ID 'nonexistent-id-123' not found./,", |
| @"'FAIL-3a: remove on a non-existent ID should fail with the correct message');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.removeTree('nonexistent-id-456'),", |
| @"/Bookmark with ID 'nonexistent-id-456' not found./,", |
| @"'FAIL-3b: removeTree on a non-existent ID should fail with the correct message');", |
| |
| @"await browser.bookmarks.remove(bookmarkA.id);", |
| @"let tree2 = await browser.bookmarks.getTree();", |
| @"browser.test.assertEq(1, tree2[0].children.length, 'folder1 should now have 1 child after removing bookmarkA');", |
| @"browser.test.assertEq('Folder Two', tree2[0].children[0].title, 'folder2 should be the only child of folder1');", |
| @"await browser.bookmarks.removeTree(folder2.id);", |
| @"let tree3 = await browser.bookmarks.getTree();", |
| @"browser.test.assertEq(0, tree3[0].children.length, 'folder1 should now have 0 children after removing folder2');", |
| @"browser.test.assertEq(2, tree3.length, 'Tree should still have 2 top-level items (folder1 and topBookmark)');", |
| @"browser.test.assertEq('Folder One', tree3[0].title, 'folder1 is still present');", |
| @"browser.test.assertEq(0, tree3[0].children.length, 'folder1 is still present but has no children');", |
| @"await browser.bookmarks.remove(folder1.id);", |
| @"let tree4 = await browser.bookmarks.getTree();", |
| @"browser.test.assertEq(1, tree4.length, 'the top level now ONLY has the top level bookmark');", |
| @"browser.test.notifyPass();", |
| ]; |
| |
| auto resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| configureRemoveBookmarksDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIUpdate) |
| { |
| auto *script = @[ |
| @"let bm = await browser.bookmarks.create({ title: 'Initial Title', url: 'http://example.com/initial' });", |
| @"browser.test.assertEq('Initial Title', bm.title, 'Initial title check');", |
| @"browser.test.assertEq('http://example.com/initial', bm.url, 'Initial URL check');", |
| |
| @"let updatedBm1 = await browser.bookmarks.update(bm.id, { title: 'Updated Title' });", |
| @"browser.test.assertEq('Updated Title', updatedBm1.title, 'Title updated correctly');", |
| @"browser.test.assertEq('http://example.com/initial', updatedBm1.url, 'URL unchanged after title update');", |
| |
| @"let updatedBm2 = await browser.bookmarks.update(bm.id, { url: 'http://example.com/updated' });", |
| @"browser.test.assertEq('Updated Title', updatedBm2.title, 'Title unchanged after URL update');", |
| @"browser.test.assertEq('http://example.com/updated', updatedBm2.url, 'URL updated correctly');", |
| |
| @"let updatedBm3 = await browser.bookmarks.update(bm.id, { title: 'Final Title', url: 'http://example.com/final' });", |
| @"browser.test.assertEq('Final Title', updatedBm3.title, 'Final title updated correctly');", |
| @"browser.test.assertEq('http://example.com/final', updatedBm3.url, 'Final URL updated correctly');", |
| |
| @"let folder = await browser.bookmarks.create({ title: 'Initial Folder' });", |
| @"browser.test.assertEq('Initial Folder', folder.title, 'Initial title check');", |
| @"let updateFolder = await browser.bookmarks.update(folder.id, { title: 'Final Folder Title' });", |
| @"browser.test.assertEq('Final Folder Title', updateFolder.title, 'Final title updated correctly');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.update(folder.id, { url: 'http://example.com/should-fail' }),", |
| @"/Cannot update URL for a \\w+ \\(ID: .*\\)\\./,", |
| @"'FAIL: Updating a folder URL should be rejected with the correct error message');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.update('nonexistent-id', { title: 'New Title' }),", |
| @"/Bookmark with ID 'nonexistent-id' not found\\./,", |
| @"'FAIL: Updating a non-existent ID should be rejected with the correct error message');", |
| |
| @"browser.test.notifyPass();", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| configureUpdateBookmarksDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPIMove) |
| { |
| auto *script = @[ |
| @"let folderA = await browser.bookmarks.create({title: 'Folder A' });", |
| @"let folderB = await browser.bookmarks.create({title: 'Folder B' });", |
| @"let bmTop = await browser.bookmarks.create({title: 'Top Bookmark', url: 'http://top.com' });", |
| @"let bmA = await browser.bookmarks.create({title: 'Bookmark A', url: 'http://a.com', parentId: folderA.id });", |
| @"let bmC = await browser.bookmarks.create({title: 'Bookmark C', url: 'http://c.com', parentId: folderA.id });", |
| @"let bmB = await browser.bookmarks.create({title: 'Bookmark B', url: 'http://b.com', parentId: folderB.id });", |
| |
| @"let movedBm1 = await browser.bookmarks.move(bmA.id, { parentId: folderB.id });", |
| @"browser.test.assertEq(bmA.id, movedBm1.id, 'Moved bookmark ID should match');", |
| @"browser.test.assertEq(folderB.id, movedBm1.parentId, 'bmA parentId should be Folder B');", |
| @"browser.test.assertEq(1, movedBm1.index, 'bmA should be at index 1 in Folder B');", |
| |
| @"let bmD = await browser.bookmarks.create({ title: 'Bookmark D', url: 'http://d.com', parentId: folderA.id });", |
| @"let movedBm2 = await browser.bookmarks.move(bmC.id, { parentId: folderA.id, index: 1 });", |
| @"browser.test.assertEq(bmC.id, movedBm2.id, 'Moved bookmark ID should match');", |
| @"browser.test.assertEq(folderA.id, movedBm2.parentId, 'bmC parentId should be Folder A');", |
| @"browser.test.assertEq(1, movedBm2.index, 'bmC should be at index 1 in Folder A');", |
| |
| @"let movedFolder = await browser.bookmarks.move(folderB.id, { index: 0 });", |
| @"browser.test.assertEq(folderB.id, movedFolder.id, 'Moved folder ID should match');", |
| @"browser.test.assertEq(0, movedFolder.index, 'Folder B parentId should be null (root)');", |
| @"let movedFolder2 = await browser.bookmarks.move(folderB.id, { parentId: folderA.id });", |
| @"browser.test.assertEq(folderB.id, movedFolder2.id, 'Moved folder ID should match');", |
| @"browser.test.assertEq(folderA.id, movedFolder2.parentId, 'Folder B parentId should be null (root)');", |
| |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.move('nonexistent-id', { parentId: folderA.id }),", |
| @"/Bookmark with ID 'nonexistent-id' not found for move./,", |
| @"'FAIL-1: Moving a non-existent bookmark should reject with the correct message');", |
| |
| @"browser.test.log('Testing move to a non-folder parent...');", |
| @"await browser.test.assertRejects(", |
| @"browser.bookmarks.move(bmB.id, { parentId: bmD.id }),", |
| @"/New parent folder with ID '.*' not found or not a folder./,", |
| @"'FAIL-2: Moving to a non-folder parent should reject with the correct message');", |
| |
| @"browser.test.notifyPass();", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| configureMoveBookmarksDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| } |
| |
| TEST_F(WKWebExtensionAPIBookmarks, BookmarksAPISearch) |
| { |
| auto *script = @[ |
| @"let folderA = await browser.bookmarks.create({ id: 'folderA_id', title: 'Animals Folder' });", |
| @"let bm1 = await browser.bookmarks.create({ id: 'bm1_id', parentId: folderA.id, title: 'Dog', url: 'http://animals.com/dog' });", |
| @"let bm2 = await browser.bookmarks.create({ id: 'bm2_id', parentId: folderA.id, title: 'Cat', url: 'http://animals.com/cat' });", |
| @"let bm3 = await browser.bookmarks.create({ id: 'bm3_id', parentId: folderA.id, title: 'Bird', url: 'http://birds.org/sparrow' });", |
| |
| @"let folderB = await browser.bookmarks.create({ id: 'folderB_id', title: 'Plants Folder' });", |
| @"let bm4 = await browser.bookmarks.create({ id: 'bm4_id', parentId: folderB.id, title: 'Rose', url: 'http://flowers.com/rose' });", |
| @"let bm5 = await browser.bookmarks.create({ id: 'bm5_id', parentId: folderB.id, title: 'Lily', url: 'http://flowers.com/lily' });", |
| |
| @"let bm6 = await browser.bookmarks.create({ id: 'bm6_id', title: 'Wild Animal', url: 'http://nature.com/wildlife' });", |
| @"let bm7 = await browser.bookmarks.create({ id: 'bm7_id', title: 'My Favorite Dog Site', url: 'http://dogs.com/favorite' });", |
| @"let bm8 = await browser.bookmarks.create({ id: 'bm8_id', title: 'A Bookmark', url: 'http://a.com/bookmark' });", |
| @"let bm9 = await browser.bookmarks.create({ id: 'bm9_id', title: 'Another Bookmark', url: 'http://another.com/bookmark' });", |
| @"let bm10 = await browser.bookmarks.create({ id: 'bm10_id', title: 'Special Folder' });", |
| @"let bm11 = await browser.bookmarks.create({ id: 'bm11_id', title: 'Special Link', url: 'http://specialLink.com' });", |
| |
| @"let results1 = await browser.bookmarks.search('dog');", |
| @"browser.test.assertEq(2, results1.length, 'S1: Should find 2 bookmarks for dog');", |
| @"browser.test.assertEq(bm1.id, results1[0].id, 'S1: First result should be bm1');", |
| @"browser.test.assertEq(bm7.id, results1[1].id, 'S1: Second result should be bm7');", |
| |
| @"let results2 = await browser.bookmarks.search('dog site');", |
| @"browser.test.assertEq(1, results2.length, 'S2: Should find 1 bookmark for dog site');", |
| @"browser.test.assertEq(bm7.id, results2[0].id, 'S2: Result should be bm7');", |
| |
| @"let results3 = await browser.bookmarks.search('Animals');", |
| @"browser.test.assertEq(3, results3.length, 'S3: Should find 1 folder for Animals');", |
| @"browser.test.assertEq(folderA.id, results3[0].id, 'S3: Result should be folderA');", |
| @"browser.test.assertEq(bm1.id, results3[1].id, 'S3: Result should be bm1 since animals appears in the url');", |
| @"browser.test.assertEq(bm2.id, results3[2].id, 'S3: Result should be bm2 since animals appears in the url and is after bm1');", |
| |
| @"let results4 = await browser.bookmarks.search('nonexistent');", |
| @"browser.test.assertEq(0, results4.length, 'S4: Should find 0 bookmarks for nonexistent');", |
| |
| @"let results5 = await browser.bookmarks.search({ query: 'cat' });", |
| @"browser.test.assertEq(1, results5.length, 'S5: Should find 1 bookmark for query cat');", |
| @"browser.test.assertEq(bm2.id, results5[0].id, 'S5: Result should be bm2');", |
| |
| @"let results6 = await browser.bookmarks.search({ title: 'Bird' });", |
| @"browser.test.assertEq(1, results6.length, 'S6: Should find 1 bookmark for title Bird');", |
| @"browser.test.assertEq(bm3.id, results6[0].id, 'S6: Result should be bm3');", |
| @"let results6_partial = await browser.bookmarks.search({ title: 'Bir' });", |
| @"browser.test.assertEq(0, results6_partial.length, 'S6: Should find 0 bookmarks for partial title Bir');", |
| @"let results6_case = await browser.bookmarks.search({ title: 'bird' });", |
| @"browser.test.assertEq(0, results6_case.length, 'S6: Should find 0 bookmarks for case-sensitive title bird');", |
| |
| @"let results7 = await browser.bookmarks.search({ url: 'flowers.com' });", |
| @"browser.test.assertEq(0, results7.length, 'S7: Should find 2 bookmarks for url flowers.com');", |
| @"let results8 = await browser.bookmarks.search('flowers.com');", |
| @"browser.test.assertEq(2, results8.length, 'S8: Should find 2 bookmarks for url flowers.com');", |
| @"browser.test.assertEq(bm4.id, results8[0].id, 'S8: First result should be bm4');", |
| @"browser.test.assertEq(bm5.id, results8[1].id, 'S8: Second result should be bm5');", |
| |
| @"let results9 = await browser.bookmarks.search({ title: 'Dog', url: 'http://animals.com/dog' });", |
| @"browser.test.assertEq(1, results9.length, 'S9: Should find 1 bookmark for title Dog and url animals.com');", |
| @"browser.test.assertEq(bm1.id, results9[0].id, 'S9: Result should be bm1');", |
| |
| @"let results9p2 = await browser.bookmarks.search({ title: 'Dog', url: 'https://animals.com/dog' });", |
| @"browser.test.assertEq(0, results9p2.length, 'S9: Should find 0 bookmarks with https');", |
| |
| @"let results10 = await browser.bookmarks.search({ query: 'nature', title: 'Wild Animal' });", |
| @"browser.test.assertEq(1, results10.length, 'S10: Should find 1 bookmark for query animal and title Wild Animal');", |
| @"browser.test.assertEq(bm6.id, results10[0].id, 'S10: Result should be bm6');", |
| |
| @"let results11 = await browser.bookmarks.search({});", |
| @"browser.test.assertEq(13, results11.length, 'S11: Should return all 10 items for empty query');", |
| |
| @"let results12 = await browser.bookmarks.search({ query: 'bookmark A' });", |
| @"browser.test.assertEq(2, results12.length, 'S12: Should find 2 bookmarks for query bookmark a');", |
| @"browser.test.assertEq(bm8.id, results12[0].id, 'S12: First result should be bm8');", |
| @"browser.test.assertEq(bm9.id, results12[1].id, 'S12: Second result should be bm9');", |
| |
| @"let results13 = await browser.bookmarks.search({ query: 'Special' });", |
| @"browser.test.assertEq(2, results13.length, 'S13: Should find 2 bookmarks for query Speical');", |
| @"browser.test.assertEq(bm10.id, results13[0].id, 'S13: First result should be bm10');", |
| @"browser.test.assertEq(bm11.id, results13[1].id, 'S13: Second result should be bm11');", |
| @"let results14 = await browser.bookmarks.search({ query: 'Special', url: 'http://specialLink.com' });", |
| @"browser.test.assertEq(1, results14.length, 'S14: Should find 1 bookmarks for query Speical');", |
| @"browser.test.assertEq(bm11.id, results14[0].id, 'S14: First result should be bm11');", |
| |
| @"browser.test.notifyPass();", |
| ]; |
| |
| auto *resources = @{ @"background.js": Util::constructScript(script) }; |
| |
| auto manager = getManagerFor(resources, bookmarkOnManifest); |
| |
| configureCreateBookmarkDelegate(manager.get()); |
| configureGetBookmarksDelegate(manager.get()); |
| |
| [manager loadAndRun]; |
| } |
| |
| } |
| #endif |
| |