appium-webdriveragent 9.0.4 → 9.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +33 -27
- package/WebDriverAgentLib/Categories/XCUIElement+FBFind.m +3 -1
- package/WebDriverAgentLib/Categories/XCUIElement+FBIsVisible.m +7 -3
- package/WebDriverAgentLib/Categories/XCUIElement+FBResolve.m +7 -5
- package/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m +17 -15
- package/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +23 -17
- package/WebDriverAgentLib/Info.plist +2 -2
- package/WebDriverAgentLib/Routing/FBElementCache.m +11 -1
- package/WebDriverAgentLib/Routing/FBResponsePayload.m +33 -28
- package/WebDriverAgentLib/Utilities/FBXPath.m +18 -14
- package/WebDriverAgentLib/Utilities/LRUCache/LRUCache.h +7 -0
- package/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +8 -0
- package/WebDriverAgentLib/Utilities/NSPredicate+FBFormat.m +4 -2
- package/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +29 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [9.0.6](https://github.com/appium/WebDriverAgent/compare/v9.0.5...v9.0.6) (2025-02-28)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* optimize LRU cache ([#985](https://github.com/appium/WebDriverAgent/issues/985)) ([46dc417](https://github.com/appium/WebDriverAgent/commit/46dc417da9f4a843838b414c0b154d6f478dbc0b))
|
|
6
|
+
|
|
7
|
+
## [9.0.5](https://github.com/appium/WebDriverAgent/compare/v9.0.4...v9.0.5) (2025-02-26)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* add autorelease pool to drain temporary objects ([#983](https://github.com/appium/WebDriverAgent/issues/983)) ([f92f1cd](https://github.com/appium/WebDriverAgent/commit/f92f1cde0fe914086103a110844bbe3bc0e3c4a6))
|
|
12
|
+
|
|
1
13
|
## [9.0.4](https://github.com/appium/WebDriverAgent/compare/v9.0.3...v9.0.4) (2025-02-21)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
|
@@ -238,9 +238,11 @@ NSDictionary<NSString *, NSString *> *customExclusionAttributesMap(void) {
|
|
|
238
238
|
if ([childElements count]) {
|
|
239
239
|
info[@"children"] = [[NSMutableArray alloc] init];
|
|
240
240
|
for (id<FBXCElementSnapshot> childSnapshot in childElements) {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
@autoreleasepool {
|
|
242
|
+
[info[@"children"] addObject:[self dictionaryForElement:childSnapshot
|
|
243
|
+
recursive:YES
|
|
244
|
+
excludedAttributes:excludedAttributes]];
|
|
245
|
+
}
|
|
244
246
|
}
|
|
245
247
|
}
|
|
246
248
|
return info;
|
|
@@ -262,9 +264,11 @@ NSDictionary<NSString *, NSString *> *customExclusionAttributesMap(void) {
|
|
|
262
264
|
} else {
|
|
263
265
|
NSMutableArray *children = [[NSMutableArray alloc] init];
|
|
264
266
|
for (id<FBXCElementSnapshot> childSnapshot in snapshot.children) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
[
|
|
267
|
+
@autoreleasepool {
|
|
268
|
+
NSDictionary *childInfo = [self accessibilityInfoForElement:childSnapshot];
|
|
269
|
+
if ([childInfo count]) {
|
|
270
|
+
[children addObject: childInfo];
|
|
271
|
+
}
|
|
268
272
|
}
|
|
269
273
|
}
|
|
270
274
|
if ([children count]) {
|
|
@@ -420,31 +424,33 @@ NSDictionary<NSString *, NSString *> *customExclusionAttributesMap(void) {
|
|
|
420
424
|
[invocation setSelector:selector];
|
|
421
425
|
[invocation setArgument:&auditTypes atIndex:2];
|
|
422
426
|
BOOL (^issueHandler)(id) = ^BOOL(id issue) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
427
|
+
@autoreleasepool {
|
|
428
|
+
NSString *auditType = @"";
|
|
429
|
+
NSDictionary *valuesToNamesMap = auditTypeValuesToNames();
|
|
430
|
+
NSNumber *auditTypeValue = [issue valueForKey:@"auditType"];
|
|
431
|
+
if (nil != auditTypeValue) {
|
|
432
|
+
auditType = valuesToNamesMap[auditTypeValue] ?: [auditTypeValue stringValue];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
id extractedElement = extractIssueProperty(issue, @"element");
|
|
436
|
+
|
|
437
|
+
id<FBXCElementSnapshot> elementSnapshot = [extractedElement fb_cachedSnapshot] ?: [extractedElement fb_takeSnapshot:NO];
|
|
438
|
+
NSDictionary *elementAttributes = elementSnapshot
|
|
434
439
|
? [self.class dictionaryForElement:elementSnapshot
|
|
435
440
|
recursive:NO
|
|
436
441
|
excludedAttributes:customAttributesToExclude]
|
|
437
442
|
: @{};
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
443
|
+
|
|
444
|
+
[resultArray addObject:@{
|
|
445
|
+
@"detailedDescription": extractIssueProperty(issue, @"detailedDescription") ?: @"",
|
|
446
|
+
@"compactDescription": extractIssueProperty(issue, @"compactDescription") ?: @"",
|
|
447
|
+
@"auditType": auditType,
|
|
448
|
+
@"element": [extractedElement description] ?: @"",
|
|
449
|
+
@"elementDescription": [extractedElement debugDescription] ?: @"",
|
|
450
|
+
@"elementAttributes": elementAttributes ?: @{},
|
|
451
|
+
}];
|
|
452
|
+
return YES;
|
|
453
|
+
}
|
|
448
454
|
};
|
|
449
455
|
[invocation setArgument:&issueHandler atIndex:3];
|
|
450
456
|
[invocation setArgument:&error atIndex:4];
|
|
@@ -121,7 +121,9 @@
|
|
|
121
121
|
{
|
|
122
122
|
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id<FBXCElementSnapshot> snapshot,
|
|
123
123
|
NSDictionary<NSString *,id> * _Nullable bindings) {
|
|
124
|
-
|
|
124
|
+
@autoreleasepool {
|
|
125
|
+
return [[FBXCElementSnapshotWrapper wdNameWithSnapshot:snapshot] isEqualToString:accessibilityId];
|
|
126
|
+
}
|
|
125
127
|
}];
|
|
126
128
|
return [self fb_descendantsMatchingPredicate:predicate
|
|
127
129
|
shouldReturnAfterFirstMatch:shouldReturnAfterFirstMatch];
|
|
@@ -25,8 +25,10 @@ NSNumber* _Nullable fetchSnapshotVisibility(id<FBXCElementSnapshot> snapshot)
|
|
|
25
25
|
|
|
26
26
|
- (BOOL)fb_isVisible
|
|
27
27
|
{
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
@autoreleasepool {
|
|
29
|
+
id<FBXCElementSnapshot> snapshot = [self fb_takeSnapshot:NO];
|
|
30
|
+
return [FBXCElementSnapshotWrapper ensureWrapped:snapshot].fb_isVisible;
|
|
31
|
+
}
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
@end
|
|
@@ -64,7 +66,9 @@ NSNumber* _Nullable fetchSnapshotVisibility(id<FBXCElementSnapshot> snapshot)
|
|
|
64
66
|
NSMutableDictionary *updatedValue = [NSMutableDictionary dictionaryWithDictionary:self.additionalAttributes ?: @{}];
|
|
65
67
|
[updatedValue setObject:attributeValue forKey:FB_XCAXAIsVisibleAttribute];
|
|
66
68
|
self.snapshot.additionalAttributes = updatedValue.copy;
|
|
67
|
-
|
|
69
|
+
@autoreleasepool {
|
|
70
|
+
return [attributeValue boolValue];
|
|
71
|
+
}
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
NSLog(@"Cannot determine visiblity of %@ natively: %@. Defaulting to: %@",
|
|
@@ -38,11 +38,13 @@ static char XCUIELEMENT_IS_RESOLVED_NATIVELY_KEY;
|
|
|
38
38
|
return self;
|
|
39
39
|
}
|
|
40
40
|
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K = %@", FBStringify(FBXCElementSnapshotWrapper, fb_uid), uid];
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
result
|
|
45
|
-
|
|
41
|
+
@autoreleasepool {
|
|
42
|
+
XCUIElementQuery *query = [self.application.fb_query descendantsMatchingType:XCUIElementTypeAny];
|
|
43
|
+
XCUIElement *result = [query matchingPredicate:predicate].allElementsBoundByIndex.firstObject;
|
|
44
|
+
if (nil != result) {
|
|
45
|
+
result.fb_isResolvedNatively = @NO;
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
46
48
|
}
|
|
47
49
|
return self;
|
|
48
50
|
}
|
|
@@ -178,23 +178,25 @@ const CGFloat FBScrollTouchProportion = 0.75f;
|
|
|
178
178
|
FBXCElementSnapshotWrapper *scrollViewWrapped = [FBXCElementSnapshotWrapper ensureWrapped:scrollView];
|
|
179
179
|
// Scrolling till cell is visible and get current value of frames
|
|
180
180
|
while (![self fb_isEquivalentElementSnapshotVisible:prescrollSnapshot] && scrollCount < maxScrollCount) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
[scrollViewWrapped fb_scrollLeftByNormalizedDistance:normalizedScrollDistance
|
|
186
|
-
inApplication:self.application];
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
scrollDirection == FBXCUIElementScrollDirectionVertical ?
|
|
190
|
-
[scrollViewWrapped fb_scrollDownByNormalizedDistance:normalizedScrollDistance
|
|
181
|
+
@autoreleasepool {
|
|
182
|
+
if (targetCellIndex < visibleCellIndex) {
|
|
183
|
+
scrollDirection == FBXCUIElementScrollDirectionVertical ?
|
|
184
|
+
[scrollViewWrapped fb_scrollUpByNormalizedDistance:normalizedScrollDistance
|
|
191
185
|
inApplication:self.application] :
|
|
192
|
-
|
|
193
|
-
|
|
186
|
+
[scrollViewWrapped fb_scrollLeftByNormalizedDistance:normalizedScrollDistance
|
|
187
|
+
inApplication:self.application];
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
scrollDirection == FBXCUIElementScrollDirectionVertical ?
|
|
191
|
+
[scrollViewWrapped fb_scrollDownByNormalizedDistance:normalizedScrollDistance
|
|
192
|
+
inApplication:self.application] :
|
|
193
|
+
[scrollViewWrapped fb_scrollRightByNormalizedDistance:normalizedScrollDistance
|
|
194
|
+
inApplication:self.application];
|
|
195
|
+
}
|
|
196
|
+
scrollCount++;
|
|
197
|
+
// Wait for scroll animation
|
|
198
|
+
[self fb_waitUntilStableWithTimeout:FBConfiguration.animationCoolOffTimeout];
|
|
194
199
|
}
|
|
195
|
-
scrollCount++;
|
|
196
|
-
// Wait for scroll animation
|
|
197
|
-
[self fb_waitUntilStableWithTimeout:FBConfiguration.animationCoolOffTimeout];
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
if (scrollCount >= maxScrollCount) {
|
|
@@ -46,22 +46,26 @@
|
|
|
46
46
|
|
|
47
47
|
- (id<FBXCElementSnapshot>)fb_takeSnapshot:(BOOL)inDepth
|
|
48
48
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (nil
|
|
56
|
-
hintText =
|
|
49
|
+
__block id<FBXCElementSnapshot> snapshot = nil;
|
|
50
|
+
@autoreleasepool {
|
|
51
|
+
NSError *error = nil;
|
|
52
|
+
snapshot = inDepth
|
|
53
|
+
? [self.fb_query fb_uniqueSnapshotWithError:&error]
|
|
54
|
+
: (id<FBXCElementSnapshot>)[self snapshotWithError:&error];
|
|
55
|
+
if (nil == snapshot) {
|
|
56
|
+
NSString *hintText = @"Make sure the application UI has the expected state";
|
|
57
|
+
if (nil != error && [error.localizedDescription containsString:@"Identity Binding"]) {
|
|
58
|
+
hintText = [NSString stringWithFormat:@"%@. You could also try to switch the binding strategy using the 'boundElementsByIndex' setting for the element lookup", hintText];
|
|
59
|
+
}
|
|
60
|
+
NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@",
|
|
61
|
+
self.description, hintText];
|
|
62
|
+
if (nil != error) {
|
|
63
|
+
reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription];
|
|
64
|
+
}
|
|
65
|
+
@throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
|
|
57
66
|
}
|
|
58
|
-
NSString *reason = [NSString stringWithFormat:@"The previously found element \"%@\" is not present in the current view anymore. %@",
|
|
59
|
-
self.description, hintText];
|
|
60
|
-
if (nil != error) {
|
|
61
|
-
reason = [NSString stringWithFormat:@"%@. Original error: %@", reason, error.localizedDescription];
|
|
62
|
-
}
|
|
63
|
-
@throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
|
|
64
67
|
}
|
|
68
|
+
self.lastSnapshot = snapshot;
|
|
65
69
|
return self.lastSnapshot;
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -78,9 +82,11 @@
|
|
|
78
82
|
}
|
|
79
83
|
NSMutableArray<NSString *> *matchedIds = [NSMutableArray new];
|
|
80
84
|
for (id<FBXCElementSnapshot> snapshot in snapshots) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
@autoreleasepool {
|
|
86
|
+
NSString *uid = [FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot];
|
|
87
|
+
if (nil != uid) {
|
|
88
|
+
[matchedIds addObject:uid];
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
NSMutableArray<XCUIElement *> *matchedElements = [NSMutableArray array];
|
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
<key>CFBundlePackageType</key>
|
|
16
16
|
<string>FMWK</string>
|
|
17
17
|
<key>CFBundleShortVersionString</key>
|
|
18
|
-
<string>9.0.
|
|
18
|
+
<string>9.0.6</string>
|
|
19
19
|
<key>CFBundleSignature</key>
|
|
20
20
|
<string>????</string>
|
|
21
21
|
<key>CFBundleVersion</key>
|
|
22
|
-
<string>9.0.
|
|
22
|
+
<string>9.0.6</string>
|
|
23
23
|
<key>NSPrincipalClass</key>
|
|
24
24
|
<string/>
|
|
25
25
|
</dict>
|
|
@@ -73,7 +73,17 @@ const int ELEMENT_CACHE_SIZE = 1024;
|
|
|
73
73
|
@throw [NSException exceptionWithName:FBStaleElementException reason:reason userInfo:@{}];
|
|
74
74
|
}
|
|
75
75
|
if (checkStaleness) {
|
|
76
|
-
|
|
76
|
+
@try {
|
|
77
|
+
[element fb_takeSnapshot:NO];
|
|
78
|
+
} @catch (NSException *exception) {
|
|
79
|
+
// if the snapshot method threw FBStaleElementException (implying the element is stale) we need to explicitly remove it from the cache, PR: https://github.com/appium/WebDriverAgent/pull/985
|
|
80
|
+
if ([exception.name isEqualToString:FBStaleElementException]) {
|
|
81
|
+
@synchronized (self.elementCache) {
|
|
82
|
+
[self.elementCache removeObjectForKey:uuid];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
@throw exception;
|
|
86
|
+
}
|
|
77
87
|
}
|
|
78
88
|
return element;
|
|
79
89
|
}
|
|
@@ -103,35 +103,40 @@ id<FBResponsePayload> FBResponseWithStatus(FBCommandStatus *status)
|
|
|
103
103
|
|
|
104
104
|
inline NSDictionary *FBDictionaryResponseWithElement(XCUIElement *element, BOOL compact)
|
|
105
105
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
__block NSDictionary *elementResponse = nil;
|
|
107
|
+
@autoreleasepool {
|
|
108
|
+
id<FBXCElementSnapshot> snapshot = element.lastSnapshot ?: element.fb_cachedSnapshot ?: [element fb_takeSnapshot:YES];
|
|
109
|
+
NSDictionary *compactResult = FBToElementDict((NSString *)[FBXCElementSnapshotWrapper wdUIDWithSnapshot:snapshot]);
|
|
110
|
+
if (compact) {
|
|
111
|
+
elementResponse = compactResult;
|
|
112
|
+
return elementResponse;
|
|
113
|
+
}
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
115
|
+
NSMutableDictionary *result = compactResult.mutableCopy;
|
|
116
|
+
FBXCElementSnapshotWrapper *wrappedSnapshot = [FBXCElementSnapshotWrapper ensureWrapped:snapshot];
|
|
117
|
+
NSArray *fields = [FBConfiguration.elementResponseAttributes componentsSeparatedByString:@","];
|
|
118
|
+
for (NSString *field in fields) {
|
|
119
|
+
// 'name' here is the w3c-approved identifier for what we mean by 'type'
|
|
120
|
+
if ([field isEqualToString:@"name"] || [field isEqualToString:@"type"]) {
|
|
121
|
+
result[field] = wrappedSnapshot.wdType;
|
|
122
|
+
} else if ([field isEqualToString:@"text"]) {
|
|
123
|
+
result[field] = FBFirstNonEmptyValue(wrappedSnapshot.wdValue, wrappedSnapshot.wdLabel) ?: [NSNull null];
|
|
124
|
+
} else if ([field isEqualToString:@"rect"]) {
|
|
125
|
+
result[field] = wrappedSnapshot.wdRect;
|
|
126
|
+
} else if ([field isEqualToString:@"enabled"]) {
|
|
127
|
+
result[field] = @(wrappedSnapshot.wdEnabled);
|
|
128
|
+
} else if ([field isEqualToString:@"displayed"]) {
|
|
129
|
+
result[field] = @(wrappedSnapshot.wdVisible);
|
|
130
|
+
} else if ([field isEqualToString:@"selected"]) {
|
|
131
|
+
result[field] = @(wrappedSnapshot.wdSelected);
|
|
132
|
+
} else if ([field isEqualToString:@"label"]) {
|
|
133
|
+
result[field] = wrappedSnapshot.wdLabel ?: [NSNull null];
|
|
134
|
+
} else if ([field hasPrefix:arbitraryAttrPrefix]) {
|
|
135
|
+
NSString *attributeName = [field substringFromIndex:[arbitraryAttrPrefix length]];
|
|
136
|
+
result[field] = [wrappedSnapshot fb_valueForWDAttributeName:attributeName] ?: [NSNull null];
|
|
137
|
+
}
|
|
134
138
|
}
|
|
139
|
+
elementResponse = result.copy;
|
|
135
140
|
}
|
|
136
|
-
return
|
|
141
|
+
return elementResponse;
|
|
137
142
|
}
|
|
@@ -364,7 +364,7 @@ static NSString *const topNodeIndexPath = @"top";
|
|
|
364
364
|
{
|
|
365
365
|
NSAssert((indexPath == nil && elementStore == nil) || (indexPath != nil && elementStore != nil), @"Either both or none of indexPath and elementStore arguments should be equal to nil", nil);
|
|
366
366
|
|
|
367
|
-
id<FBXCElementSnapshot> currentSnapshot;
|
|
367
|
+
__block id<FBXCElementSnapshot> currentSnapshot;
|
|
368
368
|
NSArray<id<FBXCElementSnapshot>> *children;
|
|
369
369
|
if ([root isKindOfClass:XCUIElement.class]) {
|
|
370
370
|
XCUIElement *element = (XCUIElement *)root;
|
|
@@ -373,7 +373,9 @@ static NSString *const topNodeIndexPath = @"top";
|
|
|
373
373
|
// then the snapshot retrieval operation might freeze and time out
|
|
374
374
|
[element.application fb_waitUntilStableWithTimeout:FBConfiguration.animationCoolOffTimeout];
|
|
375
375
|
}
|
|
376
|
-
|
|
376
|
+
@autoreleasepool {
|
|
377
|
+
currentSnapshot = [element fb_takeSnapshot:YES];
|
|
378
|
+
}
|
|
377
379
|
children = currentSnapshot.children;
|
|
378
380
|
} else {
|
|
379
381
|
currentSnapshot = (id<FBXCElementSnapshot>)root;
|
|
@@ -400,18 +402,20 @@ static NSString *const topNodeIndexPath = @"top";
|
|
|
400
402
|
}
|
|
401
403
|
|
|
402
404
|
for (NSUInteger i = 0; i < [children count]; i++) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
405
|
+
@autoreleasepool {
|
|
406
|
+
id<FBXCElementSnapshot> childSnapshot = [children objectAtIndex:i];
|
|
407
|
+
NSString *newIndexPath = (indexPath != nil) ? [indexPath stringByAppendingFormat:@",%lu", (unsigned long)i] : nil;
|
|
408
|
+
if (elementStore != nil && newIndexPath != nil) {
|
|
409
|
+
[elementStore setObject:childSnapshot forKey:(id)newIndexPath];
|
|
410
|
+
}
|
|
411
|
+
rc = [self writeXmlWithRootElement:[FBXCElementSnapshotWrapper ensureWrapped:childSnapshot]
|
|
412
|
+
indexPath:newIndexPath
|
|
413
|
+
elementStore:elementStore
|
|
414
|
+
includedAttributes:includedAttributes
|
|
415
|
+
writer:writer];
|
|
416
|
+
if (rc < 0) {
|
|
417
|
+
return rc;
|
|
418
|
+
}
|
|
415
419
|
}
|
|
416
420
|
}
|
|
417
421
|
|
|
@@ -54,6 +54,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
54
54
|
*/
|
|
55
55
|
- (NSArray *)allObjects;
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
Removes the object associated with the specified key from the cache.
|
|
59
|
+
|
|
60
|
+
@param key The key identifying the object to remove.
|
|
61
|
+
*/
|
|
62
|
+
- (void)removeObjectForKey:(id<NSCopying>)key;
|
|
63
|
+
|
|
57
64
|
@end
|
|
58
65
|
|
|
59
66
|
NS_ASSUME_NONNULL_END
|
|
@@ -59,8 +59,10 @@
|
|
|
59
59
|
NSPredicate *wdPredicate = [self.class fb_formatSearchPredicate:input];
|
|
60
60
|
return [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject,
|
|
61
61
|
NSDictionary<NSString *,id> * _Nullable bindings) {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
@autoreleasepool {
|
|
63
|
+
FBXCElementSnapshotWrapper *wrappedSnapshot = [FBXCElementSnapshotWrapper ensureWrapped:evaluatedObject];
|
|
64
|
+
return [wdPredicate evaluateWithObject:wrappedSnapshot];
|
|
65
|
+
}
|
|
64
66
|
}];
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -96,4 +96,33 @@
|
|
|
96
96
|
XCTAssertEqualObjects(@[@(count)], cache.allObjects);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
- (void)testRemoveExistingObjectForKey {
|
|
100
|
+
LRUCache *cache = [[LRUCache alloc] initWithCapacity:3];
|
|
101
|
+
[cache setObject:@"foo" forKey:@"bar"];
|
|
102
|
+
[cache setObject:@"foo2" forKey:@"bar2"];
|
|
103
|
+
[cache setObject:@"foo3" forKey:@"bar3"];
|
|
104
|
+
[self assertArray:@[@"foo3", @"foo2", @"foo"] equalsTo:cache.allObjects];
|
|
105
|
+
[cache removeObjectForKey:@"bar2"];
|
|
106
|
+
XCTAssertNil([cache objectForKey:@"bar2"]);
|
|
107
|
+
[self assertArray:@[@"foo3", @"foo"] equalsTo:cache.allObjects];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
- (void)testRemoveNonExistingObjectForKey {
|
|
111
|
+
LRUCache *cache = [[LRUCache alloc] initWithCapacity:2];
|
|
112
|
+
[cache setObject:@"foo" forKey:@"bar"];
|
|
113
|
+
[cache removeObjectForKey:@"nonExisting"];
|
|
114
|
+
XCTAssertNotNil([cache objectForKey:@"bar"]);
|
|
115
|
+
[self assertArray:@[@"foo"] equalsTo:cache.allObjects];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
- (void)testRemoveAndInsertFlow {
|
|
119
|
+
LRUCache *cache = [[LRUCache alloc] initWithCapacity:2];
|
|
120
|
+
[cache setObject:@"foo" forKey:@"bar"];
|
|
121
|
+
[cache setObject:@"foo2" forKey:@"bar2"];
|
|
122
|
+
[cache removeObjectForKey:@"bar"];
|
|
123
|
+
XCTAssertNil([cache objectForKey:@"bar"]);
|
|
124
|
+
[cache setObject:@"foo3" forKey:@"bar3"];
|
|
125
|
+
[self assertArray:@[@"foo3", @"foo2"] equalsTo:cache.allObjects];
|
|
126
|
+
}
|
|
127
|
+
|
|
99
128
|
@end
|