The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -T

# This script contains a full test of the Node and EventTarget interfaces,
# since H:D:Attr implements them itself, and does not inherit
# from H:D:Node.

use strict; use warnings; use lib 't';

use Scalar::Util 'refaddr';
use HTML::DOM;

# -------------------------#
use tests 5; # constructors

my $doc = new HTML::DOM;
isa_ok $doc, 'HTML::DOM';

my $elem = $doc->createElement('a');
isa_ok $elem, 'HTML::DOM::Element';

$elem->setAttribute(href => 'about:blank');

my $attr = $elem->getAttributeNode('href');
isa_ok $attr, 'HTML::DOM::Attr';
ok $attr->DOES('HTML::DOM::Node'), '$attr does HTML::DOM::Node';
isa_ok $attr, 'HTML::DOM::EventTarget', '$attr';


# -------------------------#
use tests 1; # overloading

is $attr, 'about:blank', '""';

# -------------------------#
use tests 1; # name

is name $attr, 'href', 'name';


# -------------------------#
use tests 41; # specified

{
	for(
		[qw[ br clear none ]],
		[qw[ td colspan 1 ]],
		[qw[ th colspan 1 ]],
		[qw[ form enctype application/x-www-form-urlencoded ]],
		[qw[ frame frameborder 1 ]],
		[qw[ iframe frameborder 1 ]],
		[qw[ form method GET ]],
		[qw[ td rowspan 1 ]],
		[qw[ th rowspan 1 ]],
		[qw[ frame scrolling auto ]],
		[qw[ iframe scrolling auto ]],
		[qw[ area shape rect ]],
		[qw[ a shape rect ]],
		[qw[ col span 1 ]],
		[qw[ colgroup span 1 ]],
		[qw[ input type TEXT ]],
		[qw[ button type submit ]],
		[qw[ param valuetype DATA ]],
	) {
		my $elem = $doc->createElement($$_[0]);
		my $attr = $elem->getAttributeNode($$_[1]);
		ok !$attr->specified, "@$_[0,1] !specified";
		is $attr->value, $$_[2], "default value of @$_[0,1]";
	}
	my $doc = new HTML::DOM;
	$doc->write('
		<!DOCTYPE HTML PUBLIC
			"-//W3C//DTD HTML 4.01 Transitional//EN">
		<title></title>
	');$doc->close;
	ok !specified{$doc->documentElement->getAttributeNode('version')},
		"html version !specified";
	is $doc->documentElement->getAttributeNode('version')->value,
		'-//W3C//DTD HTML 4.01 Transitional//EN',
		"default value of html version";

	my $elem = $doc->createElement('br');
	ok specified{
		removeAttributeNode$elem getAttributeNode$elem 'clear'
	}, "specified after removal";
	$elem->attr('clear','left');
	ok specified{getAttributeNode$elem 'clear'},
		'specified when explicit';
	$elem->attr('clear','none');
	ok specified{getAttributeNode$elem 'clear'},
		'specified when explicit, even when eq default';
}

# -------------------------#
use tests 3; # value

is value $attr, 'about:blank', 'get value';
is $attr->value('javascript:window.close()'), 'about:blank',
	'return value of setting the value is the old value';
is $elem->getAttribute('href'), 'javascript:window.close()',
	'setting the value works';

# -------------------------#
use tests 15; # Node interface attributes

# HTML::DOM::Attr implements all the Node interface itself, and does not
# inherit from HTML::DOM::Node.

is nodeName $attr, 'href', 'nodeName';
is nodeValue $attr, 'javascript:window.close()', 'nodeValue';
cmp_ok nodeType $attr, '==', HTML::DOM::Node::ATTRIBUTE_NODE, 'nodeType';
is_deeply[parentNode$attr],[],'parentNode';

my $children = childNodes $attr;
my @children = childNodes $attr; # list context
is $children->length, 1, 'number of child nodes';
is scalar @children, 1, 'number of child nodes (list context)';
isa_ok my $text_node = $children->[0], 'HTML::DOM::Text', 'child node';
&cmp_ok(@children, '==', $text_node,
	'(childNodes $attr)[0] is the same as childNodes $attr ->[0]');
is $text_node->data, 'javascript:window.close()',
	'data contained by child node';

cmp_ok firstChild $attr, '==', $text_node, 'firstChild';
cmp_ok  lastChild $attr, '==', $text_node,  'lastChild';
is_deeply[previousSibling$attr],[],'previousSibling';
is_deeply[    nextSibling$attr],[],    'nextSibling';
is_deeply[attributes$attr],[],'attributes';
cmp_ok  ownerDocument $attr, '==', $doc,  'ownerDocument';


# -------------------------#
use tests 26; # Node interface methods

eval { insertBefore $attr };
isa_ok $@, 'HTML::DOM::Exception',
	'$@ (after insertBefore)';
cmp_ok $@, '==', 
	HTML::DOM::Exception::NO_MODIFICATION_ALLOWED_ERR,
	'insertBefore throws a "no modification allowed" error';

{ # replaceChild
	$elem->appendChild(my $new_text =
		createTextNode $doc 'http://www.perl.org/');
	cmp_ok $text_node, '==', $attr->replaceChild(
			$new_text, $text_node
		), 'replaceChild returns the replaced node';
	is scalar(()=childNodes$elem), 0,
		'replaceChild removes from the tree first';

	my $frag = createDocumentFragment $doc;

	eval { $attr->replaceChild($frag, $new_text); };
	isa_ok $@, 'HTML::DOM::Exception',
		'$@ (after replaceChild with a frag that does not have ' .
		'exactly one child node)';
	cmp_ok $@, '==',
		HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
		'replaceChild with with a frag that does not have ' .
		'exactly one child node throws a ' .
		'hierarchy error';	

	$frag->appendChild(createTextNode $doc 'lalala');
	$attr->replaceChild($frag, $new_text);
	is $attr->value, 'lalala',
		'replaceChild(frag,node) inserts the frag\'s children';

	eval {
		$attr->replaceChild(
			(createAttribute $doc 'ddk'), $attr->firstChild
		);
	};
	isa_ok $@, 'HTML::DOM::Exception',
		'$@ (after replaceChild with wrong node type)';
	cmp_ok $@, '==',
		HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
		'replaceChild with wrong node type throws a ' .
		'hierarchy error';
	
	appendChild $frag createElement $doc 'div';
	eval { $attr->replaceChild($frag, firstChild $attr); };
	isa_ok $@, 'HTML::DOM::Exception',
		'$@ (after replaceChild with a frag containing the wrong' .
		' node type)';
	cmp_ok $@, '==',
		HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
		'replaceChild with a frag containing the wrong' .
		' node type throws a ' .
		'hierarchy error';

	my $another_doc = new HTML::DOM;
	my $another_node = createTextNode $another_doc 'ddk';
	ok eval {
		$attr->replaceChild(
			$another_node,
			(childNodes $attr)[0],
		);
		1
	}, 'replaceChild with wrong doc no longer dies';
	is $another_node->ownerDocument, $doc,
		'replaceChild with wrong doc changes the owner doc';

	eval {
		$attr-> replaceChild(
			$doc->createTextNode('ddk'), $text_node
		);
	};
	isa_ok $@, 'HTML::DOM::Exception',
		'$@ (after replaceChild with a bad refChild)';
	cmp_ok $@, '==', HTML::DOM::Exception::NOT_FOUND_ERR,
		'replaceChild with a 2nd arg that\'s not a child of ' .
		'this node throws a "not found" error';
}

eval { removeChild $attr };
isa_ok $@, 'HTML::DOM::Exception',
	'$@ (after removeChild)';
cmp_ok $@, '==', 
	HTML::DOM::Exception::NO_MODIFICATION_ALLOWED_ERR,
	'removeChild throws a "no modification allowed" error';

eval { appendChild $attr };
isa_ok $@, 'HTML::DOM::Exception',
	'$@ (after appendChild)';
cmp_ok $@, '==', 
	HTML::DOM::Exception::NO_MODIFICATION_ALLOWED_ERR,
	'appendChild throws a "no modification allowed" error';

ok hasChildNodes $attr, 'hasChildNodes';

my $clone = cloneNode $attr; # shallow

cmp_ok refaddr $attr, '!=', refaddr $clone, 'cloneNode makes a new object';
cmp_ok +(childNodes $attr)[0], '!=', (childNodes $clone)[0],
	'shallow clone works ignores its deep arg';
is_deeply [parentNode $clone], [], 'clones are orphans';

$clone = cloneNode $attr 1; # deep

cmp_ok refaddr $attr, '!=', refaddr $clone,
	'deep cloneNode makes a new object';
cmp_ok +(childNodes $attr)[0], '!=', (childNodes $clone)[0],
	'deep clone works';
is_deeply [parentNode $clone], [], 'deep clones are parentless';


# -------------------------#
use tests 4; # ownerElement

{
	my $elem = $doc->createElement('a');
	my $attr = $doc->createAttribute('href');
	$elem->setAttributeNode($attr);
	is $attr->ownerElement, $elem,
		'ownerElement after setAttributeNode';

	my $nother_attr = $doc->createAttribute('href');
	$elem->setAttributeNode($nother_attr);
	is +()=$attr->ownerElement, 0,
		'ownerElement after setAttributeNode replaces it';

	$elem->removeAttributeNode($nother_attr);
	is $nother_attr->ownerElement, undef,
		'removeAttributeNode updates ownerElement';

	$elem->setAttribute('target', '_blank');
	is $elem->getAttributeNode('target')->ownerElement, $elem,
		'ownerElement of autovivified attr';

}

# -------------------------#
use tests 4; # XML namespace stuff and normal eyes

is +()=$attr->$_, 0, $_ for qw / namespaceURI prefix localName normalize /;


# -------------------------#
use tests 1; # hasAttributes

ok !$attr->hasAttributes, 'hasAttrbitues';

# -------------------------#
use tests 2; # booleanness

{
	my $attr = $doc->createAttribute('foo');
	ok !"$attr",
	    'make sure our booleanness test is actually doing something';
	ok $attr, 'boooleannness';
}

# -------------------------#
use tests 2; # isSupported

ok $attr->isSupported('hTML', '1.0'), 'isSupported';
ok!$attr->isSupported('onfun') ,'isn’tSupported';

# -------------------------#
use tests 2; # relationship to the child text node (bug fixed in 0.017)
{
	is refaddr $attr->firstChild->parentNode, refaddr $attr,
	  "an attr's text node's parent is the attr after replaceChild";
	$attr = $doc->createAttribute('foo');
	is refaddr $attr->firstChild->parentNode, refaddr $attr,
		"a new attr's text node's parent is the attr";
}