From 3a426fd37d478fb8ee93d6de613e55888c4fa02b Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Tue, 20 Aug 2024 11:32:24 -0700 Subject: [PATCH] refactor!: Add CDVSettingsDictionary class (#1458) This will replace the extension category on NSDictionary, which has always felt a little bit hacky and has caused issues in weird circumstances due to requiring special build flags to work properly. --- .../Classes/Private/CDVCommandDelegateImpl.m | 2 +- .../CDVWebViewEngine/CDVWebViewEngine.m | 14 +- .../Classes/Public/CDVSettingsDictionary.m | 159 ++++++++++++ CordovaLib/Classes/Public/CDVViewController.m | 5 +- .../Public/NSDictionary+CordovaPreferences.m | 2 + .../CordovaLib.xcodeproj/project.pbxproj | 15 ++ CordovaLib/include/Cordova/CDV.h | 5 + .../include/Cordova/CDVCommandDelegate.h | 3 +- CordovaLib/include/Cordova/CDVPlugin.h | 1 + .../include/Cordova/CDVSettingsDictionary.h | 136 +++++++++++ .../include/Cordova/CDVViewController.h | 11 +- .../Cordova/NSDictionary+CordovaPreferences.h | 13 +- .../CDVSettingsDictionarySwiftTests.swift | 38 +++ .../CDVSettingsDictionaryTests.m | 227 ++++++++++++++++++ tests/CordovaLibTests/CDVWebViewEngineTest.m | 10 +- .../CordovaLibTests.xcodeproj/project.pbxproj | 6 + 16 files changed, 627 insertions(+), 20 deletions(-) create mode 100644 CordovaLib/Classes/Public/CDVSettingsDictionary.m create mode 100644 CordovaLib/include/Cordova/CDVSettingsDictionary.h create mode 100644 tests/CordovaLibTests/CDVSettingsDictionarySwiftTests.swift create mode 100644 tests/CordovaLibTests/CDVSettingsDictionaryTests.m diff --git a/CordovaLib/Classes/Private/CDVCommandDelegateImpl.m b/CordovaLib/Classes/Private/CDVCommandDelegateImpl.m index bdb35dadd5..b019b2d079 100644 --- a/CordovaLib/Classes/Private/CDVCommandDelegateImpl.m +++ b/CordovaLib/Classes/Private/CDVCommandDelegateImpl.m @@ -173,7 +173,7 @@ - (void)runInBackground:(void (^)(void))block dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block); } -- (NSDictionary*)settings +- (CDVSettingsDictionary*)settings { return _viewController.settings; } diff --git a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m index 948c2f4b8e..9523002666 100644 --- a/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m +++ b/CordovaLib/Classes/Private/Plugins/CDVWebViewEngine/CDVWebViewEngine.m @@ -20,7 +20,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import "CDVWebViewEngine.h" #import "CDVWebViewUIDelegate.h" #import -#import +#import #import #import @@ -75,7 +75,7 @@ - (nullable instancetype)initWithFrame:(CGRect)frame return [self initWithFrame:frame configuration:nil]; } -- (WKWebViewConfiguration*) createConfigurationFromSettings:(NSDictionary*)settings +- (WKWebViewConfiguration*) createConfigurationFromSettings:(CDVSettingsDictionary*)settings { WKWebViewConfiguration* configuration; if (_configuration) { @@ -170,7 +170,7 @@ - (void)pluginInitialize { // viewController would be available now. we attempt to set all possible delegates to it, by default CDVViewController* vc = (CDVViewController*)self.viewController; - NSDictionary* settings = self.commandDelegate.settings; + CDVSettingsDictionary* settings = self.commandDelegate.settings; NSString *scheme = [settings cordovaSettingForKey:@"scheme"]; @@ -365,7 +365,7 @@ - (BOOL) canLoadRequest:(NSURLRequest*)request return YES; } -- (void)updateSettings:(NSDictionary*)settings +- (void)updateSettings:(CDVSettingsDictionary*)settings { WKWebView* wkWebView = (WKWebView*)_engineWebView; @@ -410,7 +410,7 @@ - (void)updateSettings:(NSDictionary*)settings - (void)updateWithInfo:(NSDictionary*)info { NSDictionary* scriptMessageHandlers = [info objectForKey:kCDVWebViewEngineScriptMessageHandlers]; - NSDictionary* settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences]; + id settings = [info objectForKey:kCDVWebViewEngineWebViewPreferences]; id navigationDelegate = [info objectForKey:kCDVWebViewEngineWKNavigationDelegate]; id uiDelegate = [info objectForKey:kCDVWebViewEngineWKUIDelegate]; @@ -435,8 +435,10 @@ - (void)updateWithInfo:(NSDictionary*)info wkWebView.UIDelegate = uiDelegate; } - if (settings && [settings isKindOfClass:[NSDictionary class]]) { + if (settings && [settings isKindOfClass:[CDVSettingsDictionary class]]) { [self updateSettings:settings]; + } else if (settings && [settings isKindOfClass:[NSDictionary class]]) { + [self updateSettings:[[CDVSettingsDictionary alloc] initWithDictionary:settings]]; } } diff --git a/CordovaLib/Classes/Public/CDVSettingsDictionary.m b/CordovaLib/Classes/Public/CDVSettingsDictionary.m new file mode 100644 index 0000000000..0c9e5059eb --- /dev/null +++ b/CordovaLib/Classes/Public/CDVSettingsDictionary.m @@ -0,0 +1,159 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 + +@interface CDVSettingsDictionary () { + // Ideally this should not be mutable, but we've got legacy API that allows + // plugins to set values in here, so this is the world we have to live in + NSMutableDictionary *_dict; +} +@end + +@implementation CDVSettingsDictionary + +- (instancetype)init +{ + return [self initWithDictionary:@{}]; +} + +- (instancetype)initWithDictionary:(NSDictionary *)dict +{ + self = [super init]; + if (self != nil) { + if ([dict isKindOfClass:[NSMutableDictionary class]]) { + _dict = (NSMutableDictionary *)dict; + } else { + _dict = [NSMutableDictionary dictionaryWithDictionary:dict]; + } + } + return self; +} + +- (instancetype)initWithObjects:(const id _Nonnull [ _Nullable ])objects forKeys:(const id _Nonnull [ _Nullable ])keys count:(NSUInteger)cnt +{ + self = [self init]; + if (self != nil) { + _dict = [NSMutableDictionary dictionaryWithObjects:objects forKeys:keys count:cnt]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCoder:coder]; + + if (dict != nil) { + self = [self initWithDictionary:dict]; + } else { + self = [self initWithDictionary:@{}]; + } + return self; +} + ++ (BOOL)supportsSecureCoding +{ + return YES; +} + +- (Class)classForCoder +{ + return [self class]; +} + +- (id)forwardingTargetForSelector:(SEL)selector +{ + return _dict; +} + +- (NSUInteger)count +{ + return _dict.count; +} + +- (id)objectForKey:(NSString *)key +{ + return [_dict objectForKey:[key lowercaseString]]; +} + +- (NSEnumerator *)keyEnumerator +{ + return [_dict keyEnumerator]; +} + +- (id)cordovaSettingForKey:(NSString *)key +{ + return [self objectForKey:key]; +} + +- (BOOL)cordovaBoolSettingForKey:(NSString *)key defaultValue:(BOOL)defaultValue +{ + BOOL value = defaultValue; + + id prefObj = [self objectForKey:key]; + if (prefObj == nil) { +#ifdef DEBUG + NSLog(@"The preference key \"%@\" is not defined and will default to \"%@\"", key, (defaultValue ? @"TRUE" : @"FALSE")); +#endif + return value; + } + + if ([prefObj isKindOfClass:NSString.class]) { + prefObj = [prefObj lowercaseString]; + + if ([prefObj isEqualToString:@"true"] || [prefObj isEqualToString:@"1"] || [prefObj isEqualToString:@"yes"]) { + return YES; + } else if ([prefObj isEqualToString:@"false"] || [prefObj isEqualToString:@"0"] || [prefObj isEqualToString:@"no"]) { + return NO; + } + } else if ([prefObj isKindOfClass:NSNumber.class] && ([prefObj isEqual:@YES] || [prefObj isEqual:@NO])) { + return [prefObj isEqual:@YES]; + } + + return value; +} + +- (CGFloat)cordovaFloatSettingForKey:(NSString *)key defaultValue:(CGFloat)defaultValue +{ + CGFloat value = defaultValue; + + id prefObj = [self objectForKey:key]; + if (prefObj != nil) { + value = [prefObj floatValue]; + } + + return value; +} + +- (void)setObject:(id)value forKey:(NSString *)key +{ + [_dict setObject:value forKey:[key lowercaseString]]; +} + +- (void)setObject:(id)value forKeyedSubscript:(NSString *)key +{ + [_dict setObject:value forKey:[key lowercaseString]]; +} + +- (void)setCordovaSetting:(id)value forKey:(NSString *)key +{ + [self setObject:value forKey:key]; +} + +@end diff --git a/CordovaLib/Classes/Public/CDVViewController.m b/CordovaLib/Classes/Public/CDVViewController.m index 767a9a2df0..dfaa18db0b 100644 --- a/CordovaLib/Classes/Public/CDVViewController.m +++ b/CordovaLib/Classes/Public/CDVViewController.m @@ -27,6 +27,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import #import "CDVPlugin+Private.h" #import +#import #import #import "CDVCommandDelegateImpl.h" @@ -49,7 +50,7 @@ @interface CDVViewController () { } @property (nonatomic, readwrite, strong) NSXMLParser* configParser; -@property (nonatomic, readwrite, strong) NSMutableDictionary* settings; +@property (nonatomic, readwrite, strong) CDVSettingsDictionary* settings; @property (nonatomic, readwrite, strong) NSMutableDictionary* pluginObjects; @property (nonatomic, readwrite, strong) NSMutableArray* startupPluginNames; @property (nonatomic, readwrite, strong) NSDictionary* pluginsMap; @@ -178,7 +179,7 @@ - (void)loadSettings // Get the plugin dictionary, allowList and settings from the delegate. self.pluginsMap = delegate.pluginsDict; self.startupPluginNames = delegate.startupPluginNames; - self.settings = delegate.settings; + self.settings = [[CDVSettingsDictionary alloc] initWithDictionary:delegate.settings]; // And the start folder/page. if(self.wwwFolderName == nil){ diff --git a/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m b/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m index a60004d936..3a1e8bc340 100644 --- a/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m +++ b/CordovaLib/Classes/Public/NSDictionary+CordovaPreferences.m @@ -19,7 +19,9 @@ Licensed to the Apache Software Foundation (ASF) under one @import Foundation; +#define __CORDOVA_SILENCE_HEADER_DEPRECATIONS #import +#undef __CORDOVA_SILENCE_HEADER_DEPRECATIONS @implementation NSDictionary (CordovaPreferences) diff --git a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj index 5e16a89598..ee7300f5a1 100644 --- a/CordovaLib/CordovaLib.xcodeproj/project.pbxproj +++ b/CordovaLib/CordovaLib.xcodeproj/project.pbxproj @@ -113,6 +113,10 @@ 9052DE8E2150D06B008E83D4 /* CDVIntentAndNavigationFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 3093E2211B16D6A3003F381A /* CDVIntentAndNavigationFilter.h */; }; 9052DE8F2150D06B008E83D4 /* CDVHandleOpenURL.h in Headers */ = {isa = PBXBuildFile; fileRef = 7ED95CF81AB9028C008C4574 /* CDVHandleOpenURL.h */; }; 9059F51C26F2CE2400B3B2B7 /* CDVURLSchemeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 2F4D42BA23F218BA00501999 /* CDVURLSchemeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9068B5332C6DFE2000B13532 /* CDVSettingsDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 9068B5322C6DFE2000B13532 /* CDVSettingsDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9068B5342C6DFE2000B13532 /* CDVSettingsDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = 9068B5322C6DFE2000B13532 /* CDVSettingsDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 9068B5362C6E007400B13532 /* CDVSettingsDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 9068B5352C6E007400B13532 /* CDVSettingsDictionary.m */; }; + 9068B5372C6E007400B13532 /* CDVSettingsDictionary.m in Sources */ = {isa = PBXBuildFile; fileRef = 9068B5352C6E007400B13532 /* CDVSettingsDictionary.m */; }; 90B382512AEB72DD00F3F4D7 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 902D0BC12AEB64EB009C68E5 /* PrivacyInfo.xcprivacy */; }; 90DE61742B8F11D300810C2E /* Cordova.h in Headers */ = {isa = PBXBuildFile; fileRef = C0C01EB41E3911D50056E6CB /* Cordova.h */; settings = {ATTRIBUTES = (Public, ); }; }; A3B082D41BB15CEA00D8DC35 /* CDVGestureHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */; }; @@ -193,6 +197,8 @@ 902D0BC12AEB64EB009C68E5 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 9036843B2C6EB06500A3338C /* CDVAllowList.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CDVAllowList.h; sourceTree = ""; }; 9036843C2C6EB06500A3338C /* CDVAllowList.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CDVAllowList.m; sourceTree = ""; }; + 9068B5322C6DFE2000B13532 /* CDVSettingsDictionary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CDVSettingsDictionary.h; sourceTree = ""; }; + 9068B5352C6E007400B13532 /* CDVSettingsDictionary.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CDVSettingsDictionary.m; sourceTree = ""; }; A3B082D21BB15CEA00D8DC35 /* CDVGestureHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDVGestureHandler.h; sourceTree = ""; }; A3B082D31BB15CEA00D8DC35 /* CDVGestureHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVGestureHandler.m; sourceTree = ""; }; C0C01EB21E3911D50056E6CB /* Cordova.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Cordova.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -326,6 +332,7 @@ 7ED95D1E1AB9029B008C4574 /* CDVPlugin+Resources.m */, 7ED95D201AB9029B008C4574 /* CDVPlugin.m */, 7ED95D221AB9029B008C4574 /* CDVPluginResult.m */, + 9068B5352C6E007400B13532 /* CDVSettingsDictionary.m */, 7ED95D251AB9029B008C4574 /* CDVTimer.m */, 4E23F8F523E16E96006CD852 /* CDVWebViewProcessPoolFactory.m */, 7ED95D2B1AB9029B008C4574 /* CDVViewController.m */, @@ -361,6 +368,7 @@ 7ED95D1F1AB9029B008C4574 /* CDVPlugin.h */, 7ED95D211AB9029B008C4574 /* CDVPluginResult.h */, 7ED95D231AB9029B008C4574 /* CDVScreenOrientationDelegate.h */, + 9068B5322C6DFE2000B13532 /* CDVSettingsDictionary.h */, 7ED95D241AB9029B008C4574 /* CDVTimer.h */, 7ED95D2A1AB9029B008C4574 /* CDVViewController.h */, 4E23F8F923E16E96006CD852 /* CDVWebViewProcessPoolFactory.h */, @@ -391,6 +399,7 @@ C0C01EB61E3911D50056E6CB /* Cordova.h in Headers */, C0C01EBB1E39131A0056E6CB /* CDV.h in Headers */, C0C01EBC1E39131A0056E6CB /* CDVAppDelegate.h in Headers */, + 9068B5332C6DFE2000B13532 /* CDVSettingsDictionary.h in Headers */, C0C01EBD1E39131A0056E6CB /* CDVAvailability.h in Headers */, C0C01EBE1E39131A0056E6CB /* CDVAvailabilityDeprecated.h in Headers */, C0C01EBF1E39131A0056E6CB /* CDVCommandDelegate.h in Headers */, @@ -430,6 +439,7 @@ 7ED95D351AB9029B008C4574 /* CDV.h in Headers */, 7ED95D361AB9029B008C4574 /* CDVAppDelegate.h in Headers */, 7ED95D381AB9029B008C4574 /* CDVAvailability.h in Headers */, + 9068B5342C6DFE2000B13532 /* CDVSettingsDictionary.h in Headers */, 7ED95D391AB9029B008C4574 /* CDVAvailabilityDeprecated.h in Headers */, 7ED95D3A1AB9029B008C4574 /* CDVCommandDelegate.h in Headers */, 7ED95D3B1AB9029B008C4574 /* CDVCommandDelegateImpl.h in Headers */, @@ -506,6 +516,7 @@ 0867D690FE84028FC02AAC07 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1010; TargetAttributes = { C0C01EB11E3911D50056E6CB = { @@ -559,6 +570,7 @@ 9052DE742150D040008E83D4 /* CDVConfigParser.m in Sources */, 9052DE752150D040008E83D4 /* CDVInvokedUrlCommand.m in Sources */, 9052DE762150D040008E83D4 /* CDVPlugin+Resources.m in Sources */, + 9068B5362C6E007400B13532 /* CDVSettingsDictionary.m in Sources */, 9052DE772150D040008E83D4 /* CDVPlugin.m in Sources */, 9052DE782150D040008E83D4 /* CDVPluginResult.m in Sources */, 9036843F2C6EB06500A3338C /* CDVAllowList.m in Sources */, @@ -589,6 +601,7 @@ 7ED95D401AB9029B008C4574 /* CDVConfigParser.m in Sources */, 7ED95D421AB9029B008C4574 /* CDVInvokedUrlCommand.m in Sources */, 7ED95D441AB9029B008C4574 /* CDVPlugin+Resources.m in Sources */, + 9068B5372C6E007400B13532 /* CDVSettingsDictionary.m in Sources */, 2F4D42BD23F218BA00501999 /* CDVURLSchemeHandler.m in Sources */, 7ED95D461AB9029B008C4574 /* CDVPlugin.m in Sources */, 9036843E2C6EB06500A3338C /* CDVAllowList.m in Sources */, @@ -638,6 +651,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -708,6 +722,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; diff --git a/CordovaLib/include/Cordova/CDV.h b/CordovaLib/include/Cordova/CDV.h index 83913dc9d1..08fcd1008e 100644 --- a/CordovaLib/include/Cordova/CDV.h +++ b/CordovaLib/include/Cordova/CDV.h @@ -17,6 +17,8 @@ under the License. */ +#define __CORDOVA_SILENCE_HEADER_DEPRECATIONS + #import #import #import @@ -28,6 +30,7 @@ #import #import #import +#import #import #import #import @@ -35,3 +38,5 @@ #import #import #import + +#undef __CORDOVA_SILENCE_HEADER_DEPRECATIONS diff --git a/CordovaLib/include/Cordova/CDVCommandDelegate.h b/CordovaLib/include/Cordova/CDVCommandDelegate.h index 2efe4a4882..ff830e184a 100644 --- a/CordovaLib/include/Cordova/CDVCommandDelegate.h +++ b/CordovaLib/include/Cordova/CDVCommandDelegate.h @@ -22,12 +22,13 @@ @class CDVPlugin; @class CDVPluginResult; +@class CDVSettingsDictionary; typedef NSURL* (^ UrlTransformerBlock)(NSURL*); @protocol CDVCommandDelegate -@property (nonatomic, readonly) NSDictionary* settings; +@property (nonatomic, readonly) CDVSettingsDictionary* settings; @property (nonatomic, copy) UrlTransformerBlock urlTransformer; - (NSString*)pathForResource:(NSString*)resourcepath; diff --git a/CordovaLib/include/Cordova/CDVPlugin.h b/CordovaLib/include/Cordova/CDVPlugin.h index cc3a1bf0e4..cff1caf2d0 100644 --- a/CordovaLib/include/Cordova/CDVPlugin.h +++ b/CordovaLib/include/Cordova/CDVPlugin.h @@ -23,6 +23,7 @@ #import #import #import +#import #import #import diff --git a/CordovaLib/include/Cordova/CDVSettingsDictionary.h b/CordovaLib/include/Cordova/CDVSettingsDictionary.h new file mode 100644 index 0000000000..e197db7ee1 --- /dev/null +++ b/CordovaLib/include/Cordova/CDVSettingsDictionary.h @@ -0,0 +1,136 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + A dictionary-like interface providing access to the preference settings for a Cordova web view. + */ +@interface CDVSettingsDictionary : NSDictionary + +/** + The number of entries in the dictionary. + */ +@property(readonly) NSUInteger count; + +/** + Initializes a newly allocated dictionary by placing in it the keys and values + contained in another given dictionary. + + - Parameters: + - dict: A dictionary containing the keys and values with which to initialize + the new dictionary. + - Returns: An initialized dictionary containing the keys and values found in `dict`. + */ +- (instancetype)initWithDictionary:(NSDictionary *)dict NS_DESIGNATED_INITIALIZER; + +/** + Returns the value associated with a given key. + + - Parameters: + - key: The key for which to return the corresponding value. + - Returns: The value associated with `key`, or `nil` if no value is associated with `key`. + */ +- (id)objectForKey:(NSString *)key; + +/** + Provides an enumerator to access the keys in the dictionary. + + - Returns: An enumerator object that lets you access each key in the dictionary. + */ +- (NSEnumerator *)keyEnumerator; + +/** + Returns the value associated with a given key. + + - Parameters: + - key: The key for which to return the corresponding value. + - Returns: The value associated with `key`, or `nil` if no value is associated with `key`. + */ +- (id)cordovaSettingForKey:(NSString *)key; + +/** + Returns the boolean value associated with a given key, or the given default + value if the key is not found. + + - Parameters: + - key: The key for which to return the corresponding value. + - defaultValue: The default value to return if the key is missing. + - Returns: The value associated with `key`, or the provided default value. + */ +- (BOOL)cordovaBoolSettingForKey:(NSString *)key defaultValue:(BOOL)defaultValue; + +/** + Returns the floating-point numeric value associated with a given key, or the + given default value if the key is not found. + + - Parameters: + - key: The key for which to return the corresponding value. + - defaultValue: The default value to return if the key is missing. + - Returns: The value associated with `key`, or the provided default value. + */ +- (CGFloat)cordovaFloatSettingForKey:(NSString *)key defaultValue:(CGFloat)defaultValue; + +/** + Adds a preference with the given name and value to the dictionary. + + > Warning: Use of this method is highly discouraged. Preferences should be set + > and customized by app authors in the Cordova XML configuration file, not + > changed at runtime by plugins. + + - Parameters: + - value: The value to be stored with the given key. + - key: The preference name. + */ +- (void)setObject:(id)value forKey:(NSString *)key; + +/** + Adds a preference with the given name and value to the dictionary. + + You shouldn’t need to call this method directly. Instead, this method is called + when setting an object for a key using subscripting. + + > Warning: Use of this method is highly discouraged. Preferences should be set + > and customized by app authors in the Cordova XML configuration file, not + > changed at runtime by plugins. + + - Parameters: + - value: The value to be stored with the given key. + - key: The preference name. + */ +- (void)setObject:(id)value forKeyedSubscript:(NSString *)key; + +/** + Adds a preference with the given name and value to the dictionary. + + > Warning: Use of this method is highly discouraged. Preferences should be set + > and customized by app authors in the Cordova XML configuration file, not + > changed at runtime by plugins. + + - Parameters: + - value: The value to be stored with the given key. + - key: The preference name. + */ +- (void)setCordovaSetting:(id)value forKey:(NSString *)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/CordovaLib/include/Cordova/CDVViewController.h b/CordovaLib/include/Cordova/CDVViewController.h index 760ab1035e..7549fc4759 100644 --- a/CordovaLib/include/Cordova/CDVViewController.h +++ b/CordovaLib/include/Cordova/CDVViewController.h @@ -24,6 +24,7 @@ #import #import #import +#import #import #import @@ -37,7 +38,15 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, readonly, strong) NSMutableDictionary* pluginObjects; @property (nonatomic, readonly, strong) NSDictionary* pluginsMap; -@property (nonatomic, readonly, strong) NSMutableDictionary* settings; + +/** + The Cordova preferences for this view. + + This is a dictionary populated from the preference key/value pairs in the + Cordova XML configuration file. + */ +@property (nonatomic, readonly, strong) CDVSettingsDictionary* settings; + @property (nonatomic, readonly, strong) NSXMLParser* configParser; @property (nonatomic, readwrite, copy) NSString* appScheme; diff --git a/CordovaLib/include/Cordova/NSDictionary+CordovaPreferences.h b/CordovaLib/include/Cordova/NSDictionary+CordovaPreferences.h index 9be2be2dc3..0d41c83d48 100644 --- a/CordovaLib/include/Cordova/NSDictionary+CordovaPreferences.h +++ b/CordovaLib/include/Cordova/NSDictionary+CordovaPreferences.h @@ -19,17 +19,22 @@ #import #import +#import + +#ifndef __CORDOVA_SILENCE_HEADER_DEPRECATIONS +#warning "Use CDVSettingsDictionary.h and the CDVSettingsDictionary class instead" +#endif @interface NSDictionary (CordovaPreferences) -- (id)cordovaSettingForKey:(NSString*)key; -- (BOOL)cordovaBoolSettingForKey:(NSString*)key defaultValue:(BOOL)defaultValue; -- (CGFloat)cordovaFloatSettingForKey:(NSString*)key defaultValue:(CGFloat)defaultValue; +- (id)cordovaSettingForKey:(NSString*)key CDV_DEPRECATED(8, "Use CDVSettingsDictionary"); +- (BOOL)cordovaBoolSettingForKey:(NSString*)key defaultValue:(BOOL)defaultValue CDV_DEPRECATED(8, "Use CDVSettingsDictionary"); +- (CGFloat)cordovaFloatSettingForKey:(NSString*)key defaultValue:(CGFloat)defaultValue CDV_DEPRECATED(8, "Use CDVSettingsDictionary"); @end @interface NSMutableDictionary (CordovaPreferences) -- (void)setCordovaSetting:(id)value forKey:(NSString*)key; +- (void)setCordovaSetting:(id)value forKey:(NSString*)key CDV_DEPRECATED(8, "Use CDVSettingsDictionary"); @end diff --git a/tests/CordovaLibTests/CDVSettingsDictionarySwiftTests.swift b/tests/CordovaLibTests/CDVSettingsDictionarySwiftTests.swift new file mode 100644 index 0000000000..a64f76b1bf --- /dev/null +++ b/tests/CordovaLibTests/CDVSettingsDictionarySwiftTests.swift @@ -0,0 +1,38 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 + +let testSettings = [ + "disallowoverscroll": true +]; + +class CDVSettingsDictionarySwiftTests: XCTestCase +{ + func testInitWithSwiftDictionary() + { + let dict = CDVSettingsDictionary(dictionary:testSettings); + + XCTAssertEqual(1, dict.count); + XCTAssertEqual(true, dict["DisallowOverscroll"] as? Bool); + + dict.setObject(false, forKey:"DisallowOverScroll"); + XCTAssertEqual(false, dict["disallowoverscroll"] as? Bool); + } +} diff --git a/tests/CordovaLibTests/CDVSettingsDictionaryTests.m b/tests/CordovaLibTests/CDVSettingsDictionaryTests.m new file mode 100644 index 0000000000..59d5b6f70c --- /dev/null +++ b/tests/CordovaLibTests/CDVSettingsDictionaryTests.m @@ -0,0 +1,227 @@ +/* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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 +#import + +static NSDictionary *testSettings; + +@interface CDVSettingsDictionary (Testing) +// Not actually implemented, but should forward to the internal dictionary +- (void)removeObjectForKey:(NSString *)key; +@end + +@interface CDVSettingsDictionaryTests : XCTestCase +@end + +@implementation CDVSettingsDictionaryTests + +- (void)setUp +{ + [super setUp]; + + testSettings = @{ + @"test": @"CDVSettingsDictionary Test", + @"minimumfontsize": @1.1, + @"disallowoverscroll": @YES, + @"mediatypesrequiringuseractionforplayback": @"all" + }; +} + +- (void)tearDown +{ + [super tearDown]; +} + +- (void)testEmptyInit +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] init]; + + XCTAssertEqual(0, dict.count, @"Newly initialized settings dictionary is not empty"); + XCTAssertEqualObjects(@[], [dict allKeys], @"Newly initialized settings dictionary had keys"); + XCTAssertEqualObjects(@[], [dict allValues], @"Newly initialized settings dictionary had values"); + XCTAssertNil([dict objectForKey:@"Test"], @"Value found with objectForKey:"); + XCTAssertNil([dict cordovaSettingForKey:@"Test"], @"Value found with objectForKey:"); +} + +- (void)testInitWithDictionary +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] initWithDictionary:testSettings]; + + XCTAssertEqual(4, dict.count, @"Incorrect dictionary length"); + XCTAssertEqual(4, [[dict allKeys] count], @"Incorrect number of keys"); + XCTAssertEqual(4, [[dict allValues] count], @"Incorrect number of values"); + XCTAssertNotNil([dict objectForKey:@"Test"], @"Value not found with objectForKey:"); + XCTAssertNotNil([dict cordovaSettingForKey:@"Test"], @"Value not found with objectForKey:"); + XCTAssertTrue([dict isEqualToDictionary:testSettings], @"Not equal to creating dictionary"); +} + +- (void)testInitWithCoder +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] initWithDictionary:testSettings]; + + NSError *err = nil; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dict requiringSecureCoding:YES error:&err]; + XCTAssertNil(err); + + err = nil; + id result = [NSKeyedUnarchiver unarchivedObjectOfClass:[CDVSettingsDictionary class] fromData:data error:&err]; + XCTAssertNil(err); + + XCTAssertTrue([dict isEqualToDictionary:result], @"Not equal to creating dictionary"); +} + +- (void)testInitWithObjectsForKeys +{ + CDVSettingsDictionary* dict = [[CDVSettingsDictionary alloc] initWithObjects:[testSettings allValues] forKeys:[testSettings allKeys]]; + XCTAssertTrue([dict isEqualToDictionary:testSettings], @"Not equal to creating dictionary"); +} + +- (void)testCreateWithDictionary +{ + CDVSettingsDictionary *dict = [CDVSettingsDictionary dictionaryWithDictionary:testSettings]; + XCTAssertTrue([dict isEqualToDictionary:testSettings], @"Not equal to creating dictionary"); +} + +- (void)testKeyAccessCaseInsensitive +{ + CDVSettingsDictionary* dict = [[CDVSettingsDictionary alloc] initWithDictionary:testSettings]; + + XCTAssertEqualObjects(@YES, [dict objectForKey:@"DisallowOverscroll"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@YES, [dict objectForKey:@"disallowoverscroll"], @"Lowercase key name failed to match"); + + XCTAssertEqualObjects(@YES, [dict cordovaSettingForKey:@"DisallowOverscroll"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@YES, [dict cordovaSettingForKey:@"disallowoverscroll"], @"Lowercase key name failed to match"); + + XCTAssertEqualObjects(@YES, dict[@"DisallowOverscroll"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@YES, dict[@"disallowoverscroll"], @"Lowercase key name failed to match"); +} + +- (void)testSetObjectForKeyCaseInsensitive +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] init]; + + [dict setObject:@NO forKey:@"AllowInlineMediaPlayback"]; + XCTAssertEqualObjects(@NO, [dict objectForKey:@"AllowInlineMediaPlayback"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@NO, [dict objectForKey:@"allowinlinemediaplayback"], @"Lowercase key name failed to match"); + + [dict setObject:@YES forKey:@"allowinlinemediaplayback"]; + XCTAssertEqualObjects(@YES, [dict cordovaSettingForKey:@"AllowInlineMediaPlayback"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@YES, [dict cordovaSettingForKey:@"allowinlinemediaplayback"], @"Lowercase key name failed to match"); + + XCTAssertEqual(1, dict.count, @"Incorrect dictionary length"); +} + +- (void)testSetCordovaSettingForKeyCaseInsensitive +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] init]; + + [dict setCordovaSetting:@NO forKey:@"AllowInlineMediaPlayback"]; + XCTAssertEqualObjects(@NO, [dict cordovaSettingForKey:@"AllowInlineMediaPlayback"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@NO, [dict cordovaSettingForKey:@"allowinlinemediaplayback"], @"Lowercase key name failed to match"); + + [dict setCordovaSetting:@YES forKey:@"allowinlinemediaplayback"]; + XCTAssertEqualObjects(@YES, [dict objectForKey:@"AllowInlineMediaPlayback"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@YES, [dict objectForKey:@"allowinlinemediaplayback"], @"Lowercase key name failed to match"); + + XCTAssertEqual(1, dict.count, @"Incorrect dictionary length"); +} + +- (void)testSubscriptSetForKeyCaseInsensitive +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] init]; + + dict[@"AllowInlineMediaPlayback"] = @NO; + XCTAssertEqualObjects(@NO, [dict cordovaSettingForKey:@"AllowInlineMediaPlayback"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@NO, [dict cordovaSettingForKey:@"allowinlinemediaplayback"], @"Lowercase key name failed to match"); + + dict[@"allowinlinemediaplayback"] = @YES; + XCTAssertEqualObjects(@YES, [dict objectForKey:@"AllowInlineMediaPlayback"], @"Uppercase key name failed to match"); + XCTAssertEqualObjects(@YES, [dict objectForKey:@"allowinlinemediaplayback"], @"Lowercase key name failed to match"); + + XCTAssertEqual(1, dict.count, @"Incorrect dictionary length"); +} + +- (void)testMessageForwardingToDictionary +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] initWithDictionary:testSettings]; + + XCTAssertEqual(4, dict.count, @"Incorrect dictionary length"); + [dict removeObjectForKey:@"test"]; + XCTAssertEqual(3, dict.count, @"Incorrect dictionary length after removal"); +} + +- (void)testGetWithBoolDefaultValue +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] initWithDictionary:@{ + @"btruthy": @YES, + @"bfalsy": @NO, + @"itruthy": @1, + @"ifalsy": @0, + @"struthy": @"true", + @"sfalsy": @"false", + @"sbtruthy": @"yes", + @"sbfalsy": @"no", + @"sitruthy": @"1", + @"sifalsy": @"0", + + @"nonbool": @"some string" + }]; + + XCTAssertTrue([dict cordovaBoolSettingForKey:@"bTruthy" defaultValue:NO]); + XCTAssertTrue([dict cordovaBoolSettingForKey:@"iTruthy" defaultValue:NO]); + XCTAssertTrue([dict cordovaBoolSettingForKey:@"sTruthy" defaultValue:NO]); + XCTAssertTrue([dict cordovaBoolSettingForKey:@"sbTruthy" defaultValue:NO]); + XCTAssertTrue([dict cordovaBoolSettingForKey:@"siTruthy" defaultValue:NO]); + + XCTAssertFalse([dict cordovaBoolSettingForKey:@"bFalsy" defaultValue:YES]); + XCTAssertFalse([dict cordovaBoolSettingForKey:@"iFalsy" defaultValue:YES]); + XCTAssertFalse([dict cordovaBoolSettingForKey:@"sFalsy" defaultValue:YES]); + XCTAssertFalse([dict cordovaBoolSettingForKey:@"sbFalsy" defaultValue:YES]); + XCTAssertFalse([dict cordovaBoolSettingForKey:@"siFalsy" defaultValue:YES]); + + XCTAssertTrue([dict cordovaBoolSettingForKey:@"nonBool" defaultValue:YES]); + XCTAssertTrue([dict cordovaBoolSettingForKey:@"nonExistentKey" defaultValue:YES]); + + XCTAssertFalse([dict cordovaBoolSettingForKey:@"nonBool" defaultValue:NO]); + XCTAssertFalse([dict cordovaBoolSettingForKey:@"nonExistentKey" defaultValue:NO]); +} + +- (void)testGetWithFloatDefaultValue +{ + CDVSettingsDictionary *dict = [[CDVSettingsDictionary alloc] initWithDictionary:@{ + @"floatvalue": @3.14, + @"nonfloat": @"some string", + @"boolvalue": @YES + }]; + + XCTAssertEqualWithAccuracy(3.14, [dict cordovaFloatSettingForKey:@"FloatValue" defaultValue:0.0], 0.001); + + // NSString floatValue always returns 0.0 for non-numeric strings + XCTAssertEqualWithAccuracy(0.0, [dict cordovaFloatSettingForKey:@"nonFloat" defaultValue:0.0], 0.001); + XCTAssertEqualWithAccuracy(0.0, [dict cordovaFloatSettingForKey:@"nonFloat" defaultValue:1.0], 0.001); + + // NSBoolean floatValue converts YES to 1.0 + XCTAssertEqualWithAccuracy(1.0, [dict cordovaFloatSettingForKey:@"BoolValue" defaultValue:0.0], 0.001); + + XCTAssertEqualWithAccuracy(0.0, [dict cordovaFloatSettingForKey:@"NonExistentKey" defaultValue:0.0], 0.001); + XCTAssertEqualWithAccuracy(1.0, [dict cordovaFloatSettingForKey:@"NonExistentKey" defaultValue:1.0], 0.001); +} + +@end diff --git a/tests/CordovaLibTests/CDVWebViewEngineTest.m b/tests/CordovaLibTests/CDVWebViewEngineTest.m index 962d9434d1..ee9971d8f9 100644 --- a/tests/CordovaLibTests/CDVWebViewEngineTest.m +++ b/tests/CordovaLibTests/CDVWebViewEngineTest.m @@ -21,7 +21,7 @@ Licensed to the Apache Software Foundation (ASF) under one #import #import "CDVWebViewEngine.h" #import "CDVWebViewProcessPoolFactory.h" -#import +#import #import @interface CDVWebViewEngineTest : XCTestCase @@ -42,7 +42,7 @@ - (BOOL)shouldReloadWebView:(NSURL*)location title:(NSString*)title; @interface CDVViewController () // expose property as readwrite, for test purposes -@property (nonatomic, readwrite, strong) NSMutableDictionary* settings; +@property (nonatomic, readwrite, strong) CDVSettingsDictionary* settings; @end @@ -130,7 +130,7 @@ - (void) testConfigurationFromSettings { self.viewController = [[CDVViewController alloc] init]; // generate the app settings - NSDictionary* settings = @{ + CDVSettingsDictionary* settings = [[CDVSettingsDictionary alloc] initWithDictionary:@{ [@"MinimumFontSize" lowercaseString] : @1.1, // default is 0.0 [@"AllowInlineMediaPlayback" lowercaseString] : @YES, // default is NO [@"MediaTypesRequiringUserActionForPlayback" lowercaseString] : @"all", // default is none @@ -138,9 +138,9 @@ - (void) testConfigurationFromSettings { [@"AllowsAirPlayForMediaPlayback" lowercaseString] : @NO, // default is YES [@"DisallowOverscroll" lowercaseString] : @YES, // so bounces is to be NO. defaults to NO [@"WKWebViewDecelerationSpeed" lowercaseString] : @"fast" // default is 'normal' - }; + }]; // this can be set because of the Category at the top of the file - self.viewController.settings = [settings mutableCopy]; + self.viewController.settings = settings; // app settings are read after you register the plugin [self.viewController registerPlugin:self.plugin withClassName:NSStringFromClass([self.plugin class])]; diff --git a/tests/CordovaLibTests/CordovaLibTests.xcodeproj/project.pbxproj b/tests/CordovaLibTests/CordovaLibTests.xcodeproj/project.pbxproj index c67c0f5a0c..6a74217c0d 100644 --- a/tests/CordovaLibTests/CordovaLibTests.xcodeproj/project.pbxproj +++ b/tests/CordovaLibTests/CordovaLibTests.xcodeproj/project.pbxproj @@ -44,6 +44,8 @@ 902530FC29A6DC53004AF1CF /* CDVPluginInitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 902530FA29A6DC53004AF1CF /* CDVPluginInitTests.m */; }; 908630C829A6D003002963FA /* SwiftInitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 908630C729A6D003002963FA /* SwiftInitPlugin.swift */; }; 908630C929A6D003002963FA /* SwiftInitPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 908630C729A6D003002963FA /* SwiftInitPlugin.swift */; }; + 90A775DD2C6F257500FC5BB0 /* CDVSettingsDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */; }; + 90A775DE2C6F257500FC5BB0 /* CDVSettingsDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */; }; C0FA7C9B1E4BB6420077B045 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 303A4073152124BB00182201 /* main.m */; }; C0FA7C9C1E4BB6420077B045 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 303A4077152124BB00182201 /* AppDelegate.m */; }; C0FA7C9D1E4BB6420077B045 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 303A407A152124BB00182201 /* ViewController.m */; }; @@ -123,6 +125,7 @@ 7EF33BD61911ABA20048544E /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 902530FA29A6DC53004AF1CF /* CDVPluginInitTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CDVPluginInitTests.m; sourceTree = ""; }; 908630C729A6D003002963FA /* SwiftInitPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftInitPlugin.swift; sourceTree = ""; }; + 90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CDVSettingsDictionaryTests.m; sourceTree = ""; }; C0FA7CAA1E4BB6420077B045 /* CordovaFrameworkApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CordovaFrameworkApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; C0FA7CAC1E4BB6B30077B045 /* Cordova.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cordova.framework; path = "../Debug-iphonesimulator/Cordova.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; C0FA7CC71E4BBBBE0077B045 /* CordovaFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CordovaFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -239,6 +242,7 @@ EB3B34F4161B585D003DBE7D /* CordovaLibTests */ = { isa = PBXGroup; children = ( + 90A775DC2C6F256D00FC5BB0 /* CDVSettingsDictionaryTests.m */, 902530FA29A6DC53004AF1CF /* CDVPluginInitTests.m */, 4E23F90123E175AD006CD852 /* CDVWebViewEngineTest.m */, 30D1B08B15A2B36D0060C291 /* CDVBase64Tests.m */, @@ -467,6 +471,7 @@ files = ( 3035621714104C34006C2D43 /* CDVAllowListTests.m in Sources */, 686357BA141002F200DF4CF2 /* CDVPluginResultJSONSerializationTests.m in Sources */, + 90A775DE2C6F257500FC5BB0 /* CDVSettingsDictionaryTests.m in Sources */, 902530FB29A6DC53004AF1CF /* CDVPluginInitTests.m in Sources */, 30D1B08C15A2B36D0060C291 /* CDVBase64Tests.m in Sources */, EBA3554615A731F100F4DE24 /* CDVFakeFileManager.m in Sources */, @@ -496,6 +501,7 @@ C0FA7CB61E4BBBBE0077B045 /* CDVPluginResultJSONSerializationTests.m in Sources */, 902530FC29A6DC53004AF1CF /* CDVPluginInitTests.m in Sources */, C0FA7CB91E4BBBBE0077B045 /* CDVBase64Tests.m in Sources */, + 90A775DD2C6F257500FC5BB0 /* CDVSettingsDictionaryTests.m in Sources */, C0FA7CBA1E4BBBBE0077B045 /* CDVFakeFileManager.m in Sources */, C0FA7CBB1E4BBBBE0077B045 /* CDVViewControllerTest.m in Sources */, C0FA7CBC1E4BBBBE0077B045 /* CDVInvokedUrlCommandTests.m in Sources */,