//  HTMLNodeTests.m
//
//  Public domain. https://github.com/nolanw/HTMLReader

#import <XCTest/XCTest.h>
#import "HTMLComment.h"
#import "HTMLDocument.h"
#import "HTMLTextNode.h"

@interface HTMLNodeTests : XCTestCase

@end

@implementation HTMLNodeTests
{
    HTMLDocument *_document;
}

static NSArray *nodeChildClasses;

+ (void)setUp
{
    [super setUp];
    nodeChildClasses = @[ [HTMLComment class], [HTMLTextNode class] ];
}

- (void)setUp
{
    [super setUp];
    _document = [HTMLDocument new];
}

- (void)testDocumentType
{
    HTMLDocumentType *doctype = [HTMLDocumentType new];
    
    XCTAssertNil(doctype.document);
    XCTAssertNil(_document.documentType);
    _document.documentType = doctype;
    XCTAssertEqualObjects(_document.documentType, doctype);
    XCTAssertEqualObjects(doctype.document, _document);
    
    HTMLDocumentType *otherDoctype = [HTMLDocumentType new];
    _document.documentType = otherDoctype;
    XCTAssertEqualObjects(_document.documentType, otherDoctype);
    XCTAssertEqualObjects(otherDoctype.document, _document);
    XCTAssertNil(doctype.document);
    
    _document.documentType = nil;
    XCTAssertNil(_document.documentType);
    XCTAssertNil(otherDoctype.document);
}

- (void)testElementAttributes
{
    HTMLElement *element = [HTMLElement new];
    
    XCTAssertTrue(element.attributes.count == 0);
    element[@"class"] = @"bursty";
    XCTAssertEqualObjects(element.attributes.allKeys, (@[ @"class" ]));
    XCTAssertEqualObjects(element.attributes[@"class"], @"bursty");
    XCTAssertEqualObjects(element[@"class"], @"bursty");
    
    element[@"id"] = @"shovel";
    XCTAssertEqualObjects(element.attributes.allKeys[1], @"id");
    XCTAssertEqualObjects(element[@"id"], @"shovel");
    
    element[@"style"] = @"blink";
    XCTAssertEqualObjects(element.attributes.allKeys[1], @"id");
    element[@"id"] = @"maven";
    XCTAssertEqualObjects(element.attributes.allKeys[1], @"id");
    
    XCTAssertEqualObjects(element.attributes.allKeys[0], @"class");
    [element removeAttributeWithName:@"class"];
    XCTAssertNil(element[@"class"]);
    XCTAssertEqualObjects(element.attributes.allKeys[0], @"id");
}

- (void)testNode
{
    HTMLComment *comment = [HTMLComment new];
    
    XCTAssertNil(comment.document);
    XCTAssertTrue(_document.children.count == 0);
    [[_document mutableChildren] addObject:comment];
    XCTAssertEqualObjects(comment.document, _document);
    XCTAssertEqualObjects(_document.children.array, (@[ comment ]));
    [[_document mutableChildren] removeObject:comment];
    XCTAssertNil(comment.document);
    XCTAssertTrue(_document.children.count == 0);
    
    HTMLElement *element = [HTMLElement new];
    XCTAssertNil(comment.parentElement);
    comment.parentElement = element;
    XCTAssertEqualObjects(comment.parentElement, element);
    
    XCTAssertNil(comment.document);
    [[_document mutableChildren] addObject:element];
    XCTAssertEqualObjects(comment.document, _document);
    [[_document mutableChildren] removeObject:element];
    XCTAssertNil(comment.document);
}

- (void)testAddRemoveChild
{
    HTMLComment *comment = [HTMLComment new];
    
    XCTAssertNil(comment.document);
    XCTAssertTrue(_document.children.count == 0);
    [_document addChild:comment];
    XCTAssertEqualObjects(comment.document, _document);
    XCTAssertEqualObjects(_document.children.array, (@[ comment ]));
    [_document removeChild:comment];
    XCTAssertNil(comment.document);
    XCTAssertTrue(_document.children.count == 0);
    
    [_document removeChild:comment];
    XCTAssertTrue(_document.children.count == 0);
    
    HTMLElement *element = [HTMLElement new];
    [_document addChild:comment];
    [_document addChild:element];
    XCTAssertEqualObjects(_document.children.array, (@[ comment, element ]));
    [_document addChild:comment];
    XCTAssertEqualObjects(_document.children.array, (@[ comment, element ]));
    
    HTMLTextNode *text = [HTMLTextNode new];
    [_document removeChild:text];
    XCTAssertEqualObjects(_document.children.array, (@[ comment, element ]));
}

- (void)testRemoveFromParentNode
{
    HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p" attributes:nil];
    HTMLElement *h1 = [[HTMLElement alloc] initWithTagName:@"h1" attributes:nil];
    [[p mutableChildren] addObject:h1];
    XCTAssertTrue(p.children.count == 1);
    XCTAssertNotNil(h1.parentNode);
    [h1 removeFromParentNode];
    XCTAssertTrue(p.children.count == 0);
    XCTAssertNil(h1.parentNode);
}

- (void)testTextContent
{
    HTMLElement *root = [[HTMLElement alloc] initWithTagName:@"body" attributes:nil];
    XCTAssertEqualObjects(root.textContent, @"");
    
    HTMLComment *comment = [[HTMLComment alloc] initWithData:@"shhh"];
    comment.parentElement = root;
    XCTAssertEqualObjects(root.textContent, @"");
    XCTAssertEqualObjects(comment.textContent, @"shhh");
    
    [[root mutableChildren] addObject:[[HTMLTextNode alloc] initWithData:@"  "]];
    HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p" attributes:nil];
    [[root mutableChildren] addObject:p];
    [[p mutableChildren] addObject:[[HTMLTextNode alloc] initWithData:@"hello"]];
    [[root mutableChildren] addObject:[[HTMLTextNode alloc] initWithData:@" sup sup sup"]];
    XCTAssertEqualObjects(root.textContent, @"  hello sup sup sup");
    XCTAssertEqualObjects(p.textContent, @"hello");
    
    root.textContent = @"now what";
    XCTAssertEqualObjects(root.textContent, @"now what");
    XCTAssertEqualObjects([root.children.array valueForKey:@"class"], (@[ [HTMLTextNode class] ]));
    XCTAssertNil(p.parentElement);
    XCTAssertNil(comment.parentNode);
}

- (void)testTextComponents
{
    HTMLElement *dd = [[HTMLElement alloc] initWithTagName:@"dd" attributes:nil];
    XCTAssertEqualObjects(dd.textComponents, (@[ ]));
    
    [[dd mutableChildren] addObject:[[HTMLTextNode alloc] initWithData:@"\n  Some Description\n  "]];
    HTMLElement *dl = [[HTMLElement alloc] initWithTagName:@"dl" attributes:nil];
    [[dl mutableChildren] addObject:[[HTMLTextNode alloc] initWithData:@"…"]];
    [[dd mutableChildren] addObject:dl];
    [[dd mutableChildren] addObject:[[HTMLTextNode alloc] initWithData:@"\n"]];
    XCTAssertEqualObjects(dd.textComponents, (@[ @"\n  Some Description\n  ", @"\n" ]));
}

- (void)testClassAttribute
{
    HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p" attributes:@{ @"class": @"unboring" }];
    XCTAssertFalse([p hasClass:@"boring"]);
    [p toggleClass:@"boring"];
    XCTAssertTrue([p hasClass:@"boring"]);
    [p toggleClass:@"unboring"];
    XCTAssertTrue([p hasClass:@"boring"]);
    
    HTMLElement *div = [[HTMLElement alloc] initWithTagName:@"div" attributes:nil];
    [div toggleClass:@"hello"];
    XCTAssertTrue([div hasClass:@"hello"]);
}

- (void)testTreeDeallocates
{
    __weak HTMLElement *weakP;
    @autoreleasepool {
        HTMLElement *p = [[HTMLElement alloc] initWithTagName:@"p" attributes:nil];
        weakP = p;
        [p.mutableChildren addObject:[[HTMLElement alloc] initWithTagName:@"br" attributes:nil]];
        
        XCTAssertNotNil(weakP);
    }
    
    XCTAssertNil(weakP);
}

@end
