From 7f25aacaf1b5e5294e5f75dfabcabe9247fbe3e9 Mon Sep 17 00:00:00 2001 From: iska Date: Sat, 6 Jun 2015 19:00:02 +0200 Subject: [PATCH] Add test cases for the Tree Walker class --- HTMLKit.xcodeproj/project.pbxproj | 6 + HTMLKit/HTMLTreeWalker.m | 6 +- HTMLKitTests/HTMLTreeWalkerTests.m | 503 +++++++++++++++++++++++++++++ 3 files changed, 512 insertions(+), 3 deletions(-) create mode 100644 HTMLKitTests/HTMLTreeWalkerTests.m diff --git a/HTMLKit.xcodeproj/project.pbxproj b/HTMLKit.xcodeproj/project.pbxproj index ff7ddb5..1ea6aea 100644 --- a/HTMLKit.xcodeproj/project.pbxproj +++ b/HTMLKit.xcodeproj/project.pbxproj @@ -69,6 +69,8 @@ 624717181B22333200C11912 /* HTMLNodeTraversal.h in Headers */ = {isa = PBXBuildFile; fileRef = 624717161B22333200C11912 /* HTMLNodeTraversal.h */; }; 624717191B22333200C11912 /* HTMLNodeTraversal.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717171B22333200C11912 /* HTMLNodeTraversal.m */; }; 6247171A1B22333200C11912 /* HTMLNodeTraversal.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717171B22333200C11912 /* HTMLNodeTraversal.m */; }; + 6247171C1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */; }; + 6247171D1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */; }; 624717B81B21FE5400B38302 /* HTMLNodeFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */; }; 624717B91B21FE5400B38302 /* HTMLNodeFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */; }; 624717BC1B22009200B38302 /* HTMLTreeWalker.h in Headers */ = {isa = PBXBuildFile; fileRef = 624717BA1B22009200B38302 /* HTMLTreeWalker.h */; }; @@ -221,6 +223,7 @@ 624493AB19CD0CBE00BCDDF4 /* HTMLToken.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLToken.m; sourceTree = ""; }; 624717161B22333200C11912 /* HTMLNodeTraversal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLNodeTraversal.h; sourceTree = ""; }; 624717171B22333200C11912 /* HTMLNodeTraversal.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeTraversal.m; sourceTree = ""; }; + 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTreeWalkerTests.m; sourceTree = ""; }; 624717B71B21FE5400B38302 /* HTMLNodeFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLNodeFilter.m; sourceTree = ""; }; 624717BA1B22009200B38302 /* HTMLTreeWalker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HTMLTreeWalker.h; sourceTree = ""; }; 624717BB1B22009200B38302 /* HTMLTreeWalker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HTMLTreeWalker.m; sourceTree = ""; }; @@ -379,6 +382,7 @@ isa = PBXGroup; children = ( 623916C61AC7209E0066B4FE /* HTMLKitNodeIteratorTests.m */, + 6247171B1B2240B800C11912 /* HTMLTreeWalkerTests.m */, 624FC3791AE591D80015DDF9 /* HTMLKitNodesTests.m */, 62EC7AE51AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m */, ); @@ -794,6 +798,7 @@ files = ( 6239755B1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */, 623CAF9E1AD88BEA00E34C32 /* HTMLKitParserPerformance.m in Sources */, + 6247171D1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */, 62EC7AE71AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */, 624B9FB61AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */, 6236738F1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */, @@ -849,6 +854,7 @@ files = ( 6239755A1AC362CA007E26F1 /* HTMLKitTreeConstructionTests.m in Sources */, 623CAF9D1AD88BEA00E34C32 /* HTMLKitParserPerformance.m in Sources */, + 6247171C1B2240B800C11912 /* HTMLTreeWalkerTests.m in Sources */, 62EC7AE61AEEAC6F0015D3BE /* HTMLKitMutationAlgorithmsTests.m in Sources */, 624B9FB51AE0391400646C4C /* HTMLKitOrderedDictionaryTests.m in Sources */, 6236738E1AC0CE2500FF89B3 /* HTMLKitTokenizerPerformance.m in Sources */, diff --git a/HTMLKit/HTMLTreeWalker.m b/HTMLKit/HTMLTreeWalker.m index 0d27f0e..7224425 100644 --- a/HTMLKit/HTMLTreeWalker.m +++ b/HTMLKit/HTMLTreeWalker.m @@ -93,7 +93,7 @@ typedef NS_ENUM(short, HTMLTreeWalkerSiblingsType) HTMLNode *sibling = (type == HTMLTreeWalkerChildrenTypeFirst) ? node.nextSibling : node.previousSibling; if (sibling != nil) { node = sibling; - continue; + break; } HTMLNode *parent = node.parentNode; @@ -135,8 +135,8 @@ typedef NS_ENUM(short, HTMLTreeWalkerSiblingsType) return node; } - HTMLNode *sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.firstChiledNode : node.lastChildNode; - if (sibling == nil) { + sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.firstChiledNode : node.lastChildNode; + if (result == HTMLNodeFilterReject || sibling == nil) { sibling = (type == HTMLTreeWalkerSiblingsTypeNext) ? node.nextSibling : node.previousSibling; } } diff --git a/HTMLKitTests/HTMLTreeWalkerTests.m b/HTMLKitTests/HTMLTreeWalkerTests.m new file mode 100644 index 0000000..256ea32 --- /dev/null +++ b/HTMLKitTests/HTMLTreeWalkerTests.m @@ -0,0 +1,503 @@ + // + // HTMLTreeWalkerTests.m + // HTMLKit + // + // Created by Iska on 05/06/15. + // Copyright (c) 2015 BrainCookie. All rights reserved. + // + +#import +#import "HTMLTreeWalker.h" +#import "HTMLDOM.h" + +@interface HTMLTreeWalkerTests : XCTestCase + +@end + +@implementation HTMLTreeWalkerTests + +#pragma mark - Asserts + +#define AssertElementWithId(input, id) \ + do { \ + HTMLNode *node = input;\ + XCTAssertEqual(node.nodeType, HTMLNodeElement);\ + XCTAssertEqualObjects(node.asElement[@"id"], id);\ + } while(0) + +#define AssertTextWithValue(input, value) \ + do { \ + HTMLNode *node = input;\ + XCTAssertEqual(node.nodeType, HTMLNodeText);\ + XCTAssertEqualObjects(node.textContent, value);\ + } while(0) + +#define AssertCommentWithValue(input, value) \ + do { \ + HTMLNode *node = input;\ + XCTAssertEqual(node.nodeType, HTMLNodeComment);\ + XCTAssertEqualObjects(node.textContent, value);\ + } while(0) + +#pragma mark - Tests + +- (void)testTreeWalkerInit +{ + HTMLNode *root = self.basicWalkingDOM; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root]; + + XCTAssertNotNil(walker); + XCTAssertNotNil(walker.root); + XCTAssertNotNil(walker.currentNode); + XCTAssertNil(walker.filter); + XCTAssertEqual(walker.whatToShow, HTMLNodeFilterShowAll); + + XCTAssertEqualObjects(walker.root, root); + XCTAssertEqualObjects(walker.root, walker.currentNode); +} + +#pragma mark - Basic Walking + +- (HTMLNode *)basicWalkingDOM +{ + // Tree structure: + // #a + // | + // +----+----+ + // | | + // "b" #c + // | + // +----+----+ + // | | + // #d + // | + // +----+----+ + // | | | + // "e" #f "i" + // | + // +--+--+ + // | | + // "g" + + HTMLElement *div = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"a"}]; + + [div appendNode:[[HTMLText alloc] initWithData:@"b"]]; + + HTMLElement *c = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"c"}]; + [div appendNode:c]; + + HTMLElement *d = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"d"}]; + [c appendNode:d]; + [c appendNode:[[HTMLComment alloc] initWithData:@"j"]]; + + [d appendNode:[[HTMLText alloc] initWithData:@"e"]]; + + HTMLElement *f = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"f"}]; + [d appendNode:f]; + [d appendNode:[[HTMLText alloc] initWithData:@"i"]]; + + [f appendNode:[[HTMLText alloc] initWithData:@"g"]]; + [f appendNode:[[HTMLComment alloc] initWithData:@"h"]]; + + return div; +} + +- (void)testBasicWalking +{ + HTMLNode *root = self.basicWalkingDOM; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root]; + + HTMLNode *f = root.lastChildNode.firstChiledNode.childNodes[1]; + + AssertElementWithId(walker.currentNode, @"a"); + XCTAssertNil(walker.parentNode); + AssertElementWithId(walker.currentNode, @"a"); + + AssertTextWithValue(walker.firstChild, @"b"); + AssertTextWithValue(walker.currentNode, @"b"); + + AssertElementWithId(walker.nextSibling, @"c"); + AssertElementWithId(walker.currentNode, @"c"); + + AssertCommentWithValue(walker.lastChild, @"j"); + AssertCommentWithValue(walker.currentNode, @"j"); + + AssertElementWithId(walker.previousSibling, @"d"); + AssertElementWithId(walker.currentNode, @"d"); + + AssertTextWithValue(walker.nextNode, @"e"); + AssertTextWithValue(walker.currentNode, @"e"); + + AssertElementWithId(walker.parentNode, @"d"); + AssertElementWithId(walker.currentNode, @"d"); + + AssertElementWithId(walker.previousNode, @"c"); + AssertElementWithId(walker.currentNode, @"c"); + + XCTAssertNil(walker.nextSibling); + AssertElementWithId(walker.currentNode, @"c"); + + walker.currentNode = f; + XCTAssertEqualObjects(walker.currentNode, f); +} + +#pragma mark - Current Node + +- (HTMLDocument *)currentNodeDOM +{ + HTMLDocument *document = [HTMLDocument documentWithString: + @"

" + @"

"]; + + return document; +} + +- (void)testThatTreeWalkerParentHasNoEffectCurrentNodeWhenParentIsNotUnderRoot +{ + HTMLDocument *document = self.currentNodeDOM; + HTMLNode *first = document.body.firstChiledNode; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:first + showOptions:HTMLNodeFilterShowElement + filter:nil]; + + AssertElementWithId(walker.currentNode, @"first"); + XCTAssertNil(walker.parentNode); + AssertElementWithId(walker.currentNode, @"first"); +} + +- (void)testThatSettingCurrentNodeToNodesNotUnderRootIsHandledCorrectly +{ + HTMLDocument *document = self.currentNodeDOM; + HTMLNode *first = document.body.firstChiledNode; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:first + showOptions:HTMLNodeFilterShowElement|HTMLNodeFilterShowComment + filter:nil]; + walker.currentNode = document.documentElement; + XCTAssertNil(walker.parentNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); + + walker.currentNode = document.documentElement; + XCTAssertEqualObjects(walker.nextNode, document.documentElement.firstChiledNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement.firstChiledNode); + + walker.currentNode = document.documentElement; + XCTAssertNil(walker.previousNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); + + walker.currentNode = document.documentElement; + XCTAssertEqualObjects(walker.firstChild, document.documentElement.firstChiledNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement.firstChiledNode); + + walker.currentNode = document.documentElement; + XCTAssertEqualObjects(walker.lastChild, document.documentElement.lastChildNode); + XCTAssertEqualObjects(walker.currentNode, document.documentElement.lastChildNode); + + walker.currentNode = document.documentElement; + XCTAssertNil(walker.nextSibling); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); + + walker.currentNode = document.documentElement; + XCTAssertNil(walker.previousSibling); + XCTAssertEqualObjects(walker.currentNode, document.documentElement); +} + +#pragma mark - Filter + +- (HTMLElement *)filterBasicDOM +{ + HTMLElement *root = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"root"}]; + + HTMLElement *a1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"A1"}]; + HTMLElement *b1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B1"}]; + HTMLElement *b2 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B2"}]; + + [root appendNode:a1]; + [a1 appendNode:b1]; + [a1 appendNode:b2]; + + return root; +} + +- (void)testTreeWalkerNilFilter +{ + HTMLElement *root = self.filterBasicDOM; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:nil]; + + AssertElementWithId(walker.currentNode, @"root"); + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.currentNode, @"A1"); + AssertElementWithId(walker.nextNode, @"B1"); + AssertElementWithId(walker.currentNode, @"B1"); +} + +- (void)testTreeWalkerWithFilter +{ + HTMLElement *root = self.filterBasicDOM; + + id filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B1"]) { + return HTMLNodeFilterSkip; + } + return HTMLNodeFilterAccept; + }]; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.currentNode, @"root"); + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.currentNode, @"A1"); + AssertElementWithId(walker.nextNode, @"B2"); + AssertElementWithId(walker.currentNode, @"B2"); +} + +#pragma mark - Filter Skip + +- (HTMLElement *)filterDOM +{ + HTMLElement *root = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"root"}]; + + HTMLElement *a1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"A1"}]; + HTMLElement *b1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B1"}]; + HTMLElement *b2 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B2"}]; + HTMLElement *b3 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"B3"}]; + HTMLElement *c1 = [[HTMLElement alloc] initWithTagName:@"div" attributes:@{@"id": @"C1"}]; + + [root appendNode:a1]; + [a1 appendNode:b1]; + [a1 appendNode:b2]; + [a1 appendNode:b3]; + [b1 appendNode:c1]; + + return root; +} + +- (id)skipB1Filter +{ + id filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B1"]) { + return HTMLNodeFilterSkip; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +- (id)skipB2Filter +{ + id filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B2"]) { + return HTMLNodeFilterSkip; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +- (id)rejectB1Filter +{ + id filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B1"]) { + return HTMLNodeFilterReject; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +- (id)rejectB2Filter +{ + id filter = [HTMLNodeFilterBlock filterWithBlock:^HTMLNodeFilterValue(HTMLNode *node) { + if ([node.asElement[@"id"] isEqualToString:@"B2"]) { + return HTMLNodeFilterReject; + } + return HTMLNodeFilterAccept; + }]; + return filter; +} + +static HTMLElement * (^ FindElementById)(HTMLNode *, NSString *) = ^ HTMLElement * (HTMLNode *root, NSString *id) { + for (HTMLNode *node in [root nodeIteratorWithShowOptions:HTMLNodeFilterShowElement filter:nil]) { + if ([node.asElement[@"id"] isEqualToString:id]) { + return node.asElement; + } + } + return nil; +}; + +- (void)testThatFilterSkipsNextNode +{ + HTMLElement *root = self.filterDOM; + id filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.nextNode, @"A1"); + AssertElementWithId(walker.nextNode, @"C1"); + AssertElementWithId(walker.nextNode, @"B2"); + AssertElementWithId(walker.nextNode, @"B3"); +} + +- (void)testThatFilterSkipsFirstChild +{ + HTMLElement *root = self.filterDOM; + id filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"C1"); +} + +- (void)testThatFilterSkipsNextSibling +{ + HTMLElement *root = self.filterDOM; + id filter = self.skipB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"B1"); + AssertElementWithId(walker.nextSibling, @"B3"); +} + +- (void)testThatFilterSkipsParentNode +{ + HTMLElement *root = self.filterDOM; + id filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"C1"); + AssertElementWithId(walker.parentNode, @"A1"); +} + +- (void)testThatFilterSkipsPreviousSibling +{ + HTMLElement *root = self.filterDOM; + id filter = self.skipB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousSibling, @"B1"); +} + +- (void)testThatFilterSkipsPreviousNode +{ + HTMLElement *root = self.filterDOM; + id filter = self.skipB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousNode, @"B2"); + AssertElementWithId(walker.previousNode, @"C1"); + AssertElementWithId(walker.previousNode, @"A1"); +} + +#pragma mark - Filter Reject + +- (void)testThatFilterRejectsNextNode +{ + HTMLElement *root = self.filterDOM; + id filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.nextNode, @"A1"); + AssertElementWithId(walker.nextNode, @"B2"); + AssertElementWithId(walker.nextNode, @"B3"); +} + +- (void)testThatFilterRejectsFirstChild +{ + HTMLElement *root = self.filterDOM; + id filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"B2"); +} + +- (void)testThatFilterRejectsNextSibling +{ + HTMLElement *root = self.filterDOM; + id filter = self.rejectB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + AssertElementWithId(walker.firstChild, @"A1"); + AssertElementWithId(walker.firstChild, @"B1"); + AssertElementWithId(walker.nextSibling, @"B3"); +} + +- (void)testThatFilterRejectsParentNode +{ + HTMLElement *root = self.filterDOM; + id filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"C1"); + AssertElementWithId(walker.parentNode, @"A1"); +} + +- (void)testThatFilterRejectsPreviousSibling +{ + HTMLElement *root = self.filterDOM; + id filter = self.rejectB2Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousSibling, @"B1"); +} + +- (void)testThatFilterRejectsPreviousNode +{ + HTMLElement *root = self.filterDOM; + id filter = self.rejectB1Filter; + + HTMLTreeWalker *walker = [[HTMLTreeWalker alloc] initWithNode:root + showOptions:HTMLNodeFilterShowElement + filter:filter]; + + walker.currentNode = FindElementById(root, @"B3"); + AssertElementWithId(walker.previousNode, @"B2"); + AssertElementWithId(walker.previousNode, @"A1"); +} + + +@end