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

# Note: Some attributes are supposed to have their values normalised when
# accessed through the DOM 0 interface. For this reason, some attributes,
# particularly ‘align’, have weird capitalisations of their values when
# they are set. This is intentional.

use strict; use warnings; use lib 't';
our $tests;
BEGIN { ++$INC{'tests.pm'} }
sub tests'VERSION { $tests += pop };
use Test::More;
plan tests => $tests;

use HTML::DOM;
my $doc = new HTML::DOM;

# Each call to test_attr or test_event runs 3 tests.

sub test_attr {
	my ($obj, $attr, $val, $new_val) = @_;
	my $attr_name = (ref($obj) =~ /[^:]+\z/g)[0] . "'s $attr";

	# I get the attribute first before setting it, because at one point
	# I had it setting it to undef with no arg.
	is $obj->$attr,          $val,     "get $attr_name";
	is $obj->$attr($new_val),$val, "set/get $attr_name";
	is $obj->$attr,$new_val,     ,     "get $attr_name again";
}

# A useful value for testing boolean attributes:
{package false; use overload 'bool' => sub {0}, '""'=>sub{"oenuueo"};}
my $false = bless [], 'false';

# -------------------------#
use tests 87; # HTMLTableElement
{
	is ref(
		my $table = $doc->createElement('table'),
	), 'HTML::DOM::Element::Table',
		"class for table";

	is +()=$table->caption, 0, 'table->caption returns null';
	is +()=$table->tHead, 0, 'table->thead returns null';
	is +()=$table->tFoot, 0, 'table->tfoot returns null';
	isa_ok my $rows = $table->rows, 'HTML::DOM::Collection',
		'table->rows';
	isa_ok my $tbs = $table->tBodies, 'HTML::DOM::Collection',
		'table->tBodies';
	is +()=$table->rows, 0, '()=table->rows returns nothing';
	is +()=$table->tBodies, 0, '()=table->tBodies returns nothing';
	$table->appendChild(my $tbody = $doc->createElement('tbody'));
	is $#$tbs, 0, 'number of tbodies';
	is $tbs->[0], $tbody, 'contents of table->tBodies';
	$tbody->appendChild(my $row = $doc->createElement('tr'));
	is $#$rows, 0, 'number of rows';
	is $rows->[0], $row, 'contents of table->rows';

	# make sure caption tHead etc. are not recrusive:
	$row->appendChild(my $cell = $doc->createElement('td'));
	$cell->appendChild(my $subt=$doc->createElement('table'));
	$subt->push_content(
		map $doc->createElement($_), 'caption', 'thead', 'tfoot'
	);

	is $table->caption, undef, 'table->caption is not recursive';
	is $table->tHead, undef, 'table->tHead is not recursive';
	is $table->tFoot, undef, 'table->tFoot is not recursive';
	is $table->tBodies->length, 1, 'table->tBodies is not recursiev';
	is $rows->length, 1, 'table->rows is not recursive';

	ok !eval{$table->caption($doc->createElement('a'));1},
		'caption dies when set to a non-caption element';
	cmp_ok $@, '==', HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
		'caption throws the right error';
	$table->caption(my $captain = $doc->createElement('caption'));	
	is +($table->content_list)[0], $captain,
		'setting table->caption adds the element below the table';
	test_attr $table, caption => $captain,
		$doc->createElement('caption');

	ok !eval{$table->tHead($doc->createElement('a'));1},
		'tHead dies when set to a non-caption element';
	cmp_ok $@, '==', HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
		'tHead throws the right error';
	$table->tHead(my $th = $doc->createElement('thead'));	
	is +($table->content_list)[1], $th,
		'setting table->tHead adds the element below the table';
	test_attr $table, tHead => $th,
		$doc->createElement('thead');

	ok !eval{$table->tFoot($doc->createElement('a'));1},
		'tFoot dies when set to a non-caption element';
	cmp_ok $@, '==', HTML::DOM::Exception::HIERARCHY_REQUEST_ERR,
		'tFoot throws the right error';
	$table->tFoot(my $tf = $doc->createElement('tfoot'));	
	is +($table->content_list)[2], $tf,
		'setting table->tFoot adds the element below the table';
	test_attr $table, tFoot => $tf,
		$doc->createElement('tfoot');

	$table->attr(align => 'LEft');
	$table->attr(bgcolor => 'red');
	$table->attr(border => '2');
	$table->attr(cellpadding => '3');
	$table->attr(cellspacing => '4');
	$table->attr(frame => '5');
	$table->attr(rules => 'noNe');
	$table->attr(summary => 'left');
	$table->attr(width => '80');
	
	test_attr $table, qw/align left right /;
	test_attr $table, qw/bgColor red blue /;
	test_attr $table, qw/border 2 20 /;
	test_attr $table, qw/cellPadding 3 30 /;
	test_attr $table, qw/cellSpacing 4 40 /;
	test_attr $table, qw/frame 5 50 /;
	test_attr $table, qw/rules none lots /;
	test_attr $table, qw/summary left still-here /;
	test_attr $table, qw/width 80 800 /;

	is $table->createTHead, $table->tHead,
		'createTHead returns the existing thead';
	is $table->createTFoot, $table->tFoot,
		'createTFoot returns the existing foot';
	is $table->createCaption, $table->caption,
		'createCaption returns the existing caption';

	is +()=$table->deleteTHead, 0, 'return val of table->deleteTHead';
	is +()=$table->deleteTFoot, 0, 'return val of table->deleteTFoot';
	is +()=$table->deleteCaption, 0, 'retval of table->deleteCaption';

	is $table->tHead, undef, 'result of table->deleteTHead';
	is $table->tFoot, undef, 'result of table->deleteTFoot';
	is $table->caption, undef, 'result of table->deleteCaption';
	
	is $table->createTHead, $table->childNodes->[0],
		'createTHead creates and returns a new table header';
	is $table->createTFoot, $table->childNodes->[1],
		'createTFoot creates and returns a new table footer';
	is $table->createCaption, $table->childNodes->[0],
		'createCaption creates and returns a new table caption';

	isa_ok $row = $table->insertRow(0), 'HTML::DOM::Element::TR',
		'table->insertRow(0)';
	is $row, $rows->[0], 'result of insertRow(0)';
	is $table->insertRow(1), $rows->[1], 'result of insertRow(1)';
	is @$rows, 3, 'number of rows after insertRow';
	
	my $last_row = $rows->[-1];
	is +()=$table->deleteRow(1), 0, 'retval of table->deleteRow';
	is_deeply \@$rows, [$row,$last_row],
		'effect of table->deleteRow';

	(my $doc = new HTML::DOM)->write('
		<table><tbody><tr><tbody><tr></table>
	'); $doc->close;
	my $new_table = $doc->getElementsByTagName('table')->[0];
	$row = $new_table->insertRow(1);
	is $new_table->tBodies->[1]->childNodes->[0], $row,
	    'insertRow inserts in the same section as the following row';
	is $new_table->insertRow(-1), $new_table->rows->[-1],
		'insertRow(-1)';
	is $new_table->insertRow($new_table->rows->length),
		$new_table->rows->[-1], 'insertRow(number of rows)';
	ok !eval{$new_table->insertRow(-2);1},
		'insertRow(negative number less than -1)';
	cmp_ok $@, '==', HTML::DOM::Exception::INDEX_SIZE_ERR,
		'insertRow with neg num too small throws the right error';
	ok !eval{$new_table->insertRow(328);1},
		'insertRow(beeg number)';
	cmp_ok $@, '==', HTML::DOM::Exception::INDEX_SIZE_ERR,
		'insertRow with big number throws the right error';
}

# -------------------------#
use tests 4; # HTMLTableCaptionElement
{
	is ref(
		my $elem = $doc->createElement('caption'),
	), 'HTML::DOM::Element::Caption',
		"class for caption";

	$elem->attr(align => 'lEft');
	test_attr $elem, qw/align left right /;
}

# -------------------------#
use tests 20; # HTMLTableColElement
{
	my $elem;
	is ref(
		$elem = $doc->createElement($_),
	), 'HTML::DOM::Element::TableColumn',
		"class for $_" for qw/ col colgroup /;

	$elem->attr(align => 'LeFt');
	$elem->attr(char => '.');
	$elem->attr(charoff => '8');
	$elem->attr(span => '9');
	$elem->attr(vAlign => 'toP');
	$elem->attr(width => '10');
	no warnings 'qw';
	test_attr $elem, qw/align left right /;
	test_attr $elem, qw/ch . , /;
	test_attr $elem, qw/chOff 8 80 /;
	test_attr $elem, qw/span 9 90 /;
	test_attr $elem, qw/vAlign top bottom /;
	test_attr $elem, qw/width 10 100 /;
}

# -------------------------#
use tests 32; # HTMLTableSectionElement
{
	my $elem;
	is ref(
		$elem = $doc->createElement($_),
	), 'HTML::DOM::Element::TableSection',
		"class for $_" for qw/ thead tbody tfoot /;

	$elem->attr(align => 'LefT');
	$elem->attr(char => '.');
	$elem->attr(charoff => '8');
	$elem->attr(vAlign => 'tOp');
	no warnings 'qw';
	test_attr $elem, qw/align left right /;
	test_attr $elem, qw/ch . , /;
	test_attr $elem, qw/chOff 8 80 /;
	test_attr $elem, qw/vAlign top bottom /;

	isa_ok my $rows = $elem->rows, 'HTML::DOM::Collection',
		'table section ->rows';
	is +()=$elem->rows, 0,'table section ->rows returning null';
	$elem->appendChild(my $row = $doc->createElement('tr'));
	is @$rows, 1, 'number of rows in table section when there is one';
	is join('',$elem->rows), $row,
	    'table section ->rows in list context when there is one row';
	$row->appendChild(my $cell = $doc->createElement('td'));
	$cell->appendChild(my $subt = $doc->createElement('table'));
	$subt->insertRow();
	is @$rows, 1, 'table section ->rows is not recursive';

	isa_ok $row = $elem->insertRow(0), 'HTML::DOM::Element::TR',
		'table section ->insertRow';
	is $row, $rows->[0], 'result of table section ->insertRow(0)';
	is $elem->insertRow(1), $rows->[1],
		'result of table section ->insertRow(1)';
	is @$rows, 3, 'number of rows after table section ->insertRow';
	
	my $last_row = $rows->[-1];
	is +()=$elem->deleteRow(1), 0, 'retval of table sect ->deleteRow';
	is_deeply \@$rows, [$row,$last_row],
		'effect of table section ->deleteRow';

	(my $doc = new HTML::DOM)->write('
		<table><tbody><tr><tr></table>
	'); $doc->close;
	$elem =$doc->getElementsByTagName('table')->[0]->firstChild;
	is $elem->insertRow(-1), $elem->rows->[-1],
		'table section ->insertRow(-1)';
	is $elem->insertRow($elem->rows->length),
		$elem->rows->[-1],
		'table section ->insertRow(no. of rows)';
	ok !eval{$elem->insertRow(-2);1},
		'table section ->insertRow(negative number less than -1)';
	cmp_ok $@, '==', HTML::DOM::Exception::INDEX_SIZE_ERR,
		'table section ->insertRow(neg) throws the right error';
	ok !eval{$elem->insertRow(328);1},
		'table section ->insertRow(beeg number)';
	cmp_ok $@, '==', HTML::DOM::Exception::INDEX_SIZE_ERR,
	   'table section ->insertRow(big number) throws the right error';
}

# -------------------------#
use tests 36; # HTMLTableRowElement
{
	is ref(
		my $row = $doc->createElement('tr'),
	), 'HTML::DOM::Element::TR',
		"class for tr";

	my $table = $doc->createElement('table');
	$table->appendChild(my $tb = $doc->createElement('tbody'));
	$tb->insertRow;
	$table->appendChild($tb = $doc->createElement('tbody'));
	$tb->appendChild($row);

	is $row->rowIndex, 1, 'rowIndex';
	is $row->sectionRowIndex, 0, 'sectionRowIndex';

	isa_ok my $cells = $row->cells, 'HTML::DOM::Collection',
		'cells';
	is +()=$row->cells, 0,'cells returning null';
	$row->appendChild(my $cell = $doc->createElement('th'));
	is @$cells, 1, 'number of cells when there is one';
	is join('',$row->cells), $cell,
	    'cels in list context when there is one row';
	$cell->appendChild(my $subt = $doc->createElement('table'));
	$subt->insertRow()->appendChild($doc->createElement('td'));
	is @$cells, 1, 'cells is not recursive';

	$row->attr(align => 'LEFt');
	$row->attr(bgcolor => 'red');
	$row->attr(char => '.');
	$row->attr(charoff => '8');
	$row->attr(vAlign => 'Top');
	no warnings 'qw';
	test_attr $row, qw/align left right /;
	test_attr $row, qw/bgColor red green /;
	test_attr $row, qw/ch . , /;
	test_attr $row, qw/chOff 8 80 /;
	test_attr $row, qw/vAlign top bottom /;

	isa_ok $cell = $row->insertCell(0),
		'HTML::DOM::Element::TableCell',
		'insertCell';
	is $cell->tag, 'td', 'tag of cell inserted by insertCell';
	is $cell, $cells->[0], 'result of insertCell(0)';
	is $row->insertCell(1), $cells->[1],
		'result of insertCell(1)';
	is @$cells, 3, 'number of cells after insertCell';
	
	my $last_cell = $cells->[-1];
	is +()=$row->deleteCell(1), 0, 'retval of deleteCell';
	is_deeply \@$cells, [$cell,$last_cell],
		'effect of deleteCell';

	is $row->insertCell(-1), $cells->[-1],
		'insertCell(-1)';
	is $row->insertCell($cells->length),
		$cells->[-1],
		'insertCell(no. of rows)';
	ok !eval{$row->insertCell(-2);1},
		'insertCell(negative number less than -1)';
	cmp_ok $@, '==', HTML::DOM::Exception::INDEX_SIZE_ERR,
		'insertCell(neg) throws the right error';
	ok !eval{$row->insertCell(328);1},
		'insertCell(beeg number)';
	cmp_ok $@, '==', HTML::DOM::Exception::INDEX_SIZE_ERR,
	   'insertCell(big number) throws the right error';
}

# -------------------------#
use tests 47; # HTMLTableCellElement
{
	my $cell;
	is ref(
		$cell = $doc->createElement($_),
	), 'HTML::DOM::Element::TableCell',
		"class for $_" for 'td', 'th';

	my $row = $doc->createElement('tr');
	$row->appendChild($cell);
	is $cell->cellIndex, 0, 'cellIndex';

	$cell->attr(abbr => 'evi');
	$cell->attr(align => 'lEfT');
	$cell->attr(axis => 'allies');
	$cell->attr(bgcolor => 'red');
	$cell->attr(char => '.');
	$cell->attr(charoff => '8');
	$cell->attr(colspan => '9');
	$cell->attr(headers => '9');
	$cell->attr(height => '10');
	$cell->attr(nowrap => '10');
	$cell->attr(rowspan => '11');
	$cell->attr(scope => 'roW');
	$cell->attr(vAlign => 'TOp');
	$cell->attr(width => '12');
	no warnings 'qw';
	test_attr $cell, qw/abbr evi ation /;
	test_attr $cell, qw/align left right /;
	test_attr $cell, qw/axis allies whatevere /;
	test_attr $cell, qw/bgColor red green /;
	test_attr $cell, qw/ch . , /;
	test_attr $cell, qw/chOff 8 80 /;
	test_attr $cell, qw/colSpan 9 90 /;
	test_attr $cell, qw/headers 9 23322323puuoeoeeo /;
	test_attr $cell, qw/height 10 1100 /;
	ok $cell->noWrap             ,      'get TableCell’s noWrap';
	ok $cell->noWrap(0),         ,  'set/get TableCell’s noWrap';
	ok!$cell->noWrap             ,      'get TableCell’s noWrap again';
	test_attr $cell, qw/rowSpan 11 110 /;
	test_attr $cell, qw/scope row col /;
	test_attr $cell, qw/vAlign top bottom /;
	test_attr $cell, qw/width 12 234 /;

	$cell->noWrap(1);
	is $cell->getAttribute('nowrap'), 'nowrap',
	 'table cell’s nowrap is set to "nowrap" when true';
	$cell->noWrap($false);
	is $cell->attr('nowrap'), undef,
	 'table cell’s nowrap is deleted when set to false';

}