#!/usr/bin/perl -w
use Test::More;
use strict;
BEGIN
{
plan tests => 203;
chdir 't' if -d 't';
use lib '../lib';
use_ok ("Graph::Easy::Node") or die($@);
use_ok ("Graph::Easy") or die($@);
use_ok ("Graph::Easy::As_ascii") or die($@);
};
can_ok ("Graph::Easy::Node", qw/
new
as_html as_ascii
error
class
dimensions
name
sorted_successors
successors
predecessors
has_predecessors
has_as_predecessor
has_as_successor
connections
edges
edges_to
incoming
outgoing
width
background
height
columns
rows
size
flow
angle
parent
pos
offset
x
y
class
title
link
shape
default_attribute
del_attribute
set_attribute
get_attribute
set_attributes
attribute
default_attribute
color_attribute
get_attributes
border_attribute
group add_to_group
origin
is_multicelled
nodes_sharing_start
nodes_sharing_end
as_html
_place _do_place _check_place _place_children find_grandparent
_near_places _allowed_places
/);
#############################################################################
my $node = Graph::Easy::Node->new();
is (ref($node), 'Graph::Easy::Node');
is ($node->error(), '', 'no error yet');
is ($node->x(), undef, 'x == undef');
is ($node->y(), undef, 'y == undef');
is ($node->label(), 'Node #0', 'label');
is ($node->name(), 'Node #0', 'name');
is ($node->class(), 'node', 'class node');
is ($node->title(), '', 'no title per default');
is (join(",", $node->pos()), "0,0", 'pos = 0,0');
is ($node->width(), undef, 'w = undef'); # no graph => thus no width yet
is ($node->height(), undef, 'h == undef');
is ($node->shape(), 'rect', 'default shape is "rect"');
is ($node->border_attribute(), '', 'border_attribute()');
is ($node->connections(), 0, 'no connections yet');
is ($node->is_multicelled(), 0, 'no multicelled');
is ($node->rows(), 1, '1 row');
is ($node->columns(), 1, '1 column');
# These are inherited:
is ($node->attribute('border'), '', 'attribute("border")');
is ($node->attribute('border-style'), 'solid', 'attribute("border-style")');
is (join(",",$node->dimensions()), "7,1", 'dimensions = (7,1)');
is ($node->origin(), undef, 'not clustered');
is (join(",",$node->offset()), '0,0', 'not clustered');
is (scalar $node->successors(), undef, 'no outgoing links');
is (scalar $node->sorted_successors(), 0, 'no outgoing links');
is (scalar $node->predecessors(), undef, 'no incoming links');
is (scalar $node->incoming(), undef, 'no incoming links');
is (scalar $node->outgoing(), undef, 'no outgoing links');
my $edge = Graph::Easy::Node->new();
$edge->set_attribute('class' => 'edge');
is ($edge->class(), 'node.edge', 'class edge');
is ($edge->border_attribute(), '', 'border_attribute()');
my $other = Graph::Easy::Node->new();
is (scalar $node->edges_to($other), undef, 'no graph, no links');
is (scalar $node->edges(), undef, 'no graph, no edges');
#############################################################################
# predecessors(), successors(), connections() and edges_to() tests
my $graph = Graph::Easy->new( );
$other = Graph::Easy::Node->new( 'Name' );
$edge = $graph->add_edge ($node, $other);
is ($node->{graph}, $graph, "node's graph points to \$graph");
is ($other->{graph}, $graph, "other's graph points to \$graph");
is ($node->successors(), 1, '1 outgoing');
is (scalar $node->sorted_successors(), 1, '1 outgoing');
is ($node->predecessors(), 0, '0 incoming');
is (scalar $node->edges_to($other), 1, '1 link to $other');
is ($node->connections(), 1, '1 connection');
is (scalar $node->edges(), 1, '1 edge');
is ($node->has_as_successor($other), 1, 'node -> other');
is ($node->has_as_successor($node), 0, '! node -> node');
is ($node->has_as_predecessor($node), 0, '! node -> node');
is ($node->has_as_predecessor($other), 0, '! node -> node');
is ($other->has_as_successor($other), 0, '! other -> node');
is ($other->has_as_successor($node), 0, '! other -> other');
is ($other->has_as_predecessor($node), 1, ' node -> other');
is ($other->has_as_predecessor($other), 0, '! other -> other');
my @E = $node->edges_to($other);
is (scalar @E, 1, '1 link to $other');
is ($E[0], $edge, 'first link to $other is $edge');
@E = $node->edges();
is ($E[0], $edge, '1 edge');
is ($other->successors(), 0, '0 outgoing');
is (scalar $other->sorted_successors(), 0, '0 outgoing');
is ($other->predecessors(), 1, '1 incoming');
is ($other->connections(), 1, '1 connection');
$graph->add_edge('First', 'Name');
@E = $node->edges_to($other);
is (scalar @E, 1, '1 link to $other');
is ($E[0], $edge, 'first link to $other is $edge');
$graph->add_edge('Name', 'Name');
#############################################################################
# as_txt/as_html
my $r = 'colspan=4 rowspan=4';
use_ok ('Graph::Easy::As_txt');
can_ok ('Graph::Easy::Node', qw/attributes_as_txt as_txt as_pure_txt/);
is ($node->as_txt(), '[ Node \#0 ]', 'as_txt');
is ($node->as_html(), " <td $r class='node'>Node #0</td>\n",
'as_html');
# no quoting of () nec.
$node->{name} = 'Frankfurt (Oder)';
is ($node->as_txt(), '[ Frankfurt (Oder) ]', 'as_txt');
is ($node->as_html(), " <td $r class='node'>Frankfurt (Oder)</td>\n",
'as_html');
# quoting of |
$node->{name} = 'Frankfurt |-|';
is ($node->as_txt(), '[ Frankfurt \|-\| ]', 'as_txt');
is ($node->as_html(), " <td $r class='node'>Frankfurt |-|</td>\n",
'as_html');
# quoting of [] and {}
$node->{name} = 'Frankfurt [ { #1 } ]';
is ($node->as_txt(), '[ Frankfurt \[ \{ \#1 \} \] ]', 'as_txt');
is ($node->as_html(), " <td $r class='node'>Frankfurt [ { #1 } ]</td>\n",
'as_html');
# quoting of &, < and >
$node->{name} = 'Frankfurt < & >';
is ($node->as_txt(), '[ Frankfurt < & > ]', 'as_txt');
is ($node->as_html(), " <td $r class='node'>Frankfurt < & ></td>\n",
'as_html');
#############################################################################
# as_txt with labels
$node->set_attribute('label', 'thelabel');
$node->{name} = 'name';
is ($node->as_txt(), '[ name ] { label: thelabel; }', 'as_txt');
# reset node for next tests
$node->{name} = 'Node #0';
$node->del_attribute('label');
# test setting after deletion
$node->set_attribute('label', 'my label');
is ($node->as_txt(), '[ Node \#0 ] { label: my label; }', 'as_txt');
# reset node for next tests
$node->del_attribute('label');
#############################################################################
# as_txt/as_html w/ subclass and attributes
$node->{class} = 'node.cities';
is ($node->as_txt(), '[ Node \#0 ] { class: cities; }', 'as_txt');
is ($node->as_html(), " <td $r class='node_cities'>Node #0</td>\n",
'as_html');
is ($node->as_pure_txt(), '[ Node \#0 ]', 'as_txt_node');
$node->set_attribute ( 'color', 'blue' );
is ($node->as_txt(), '[ Node \#0 ] { color: blue; class: cities; }', 'as_txt');
is ($node->as_html(), " <td $r class='node_cities' style=\"color: #0000ff\">Node #0</td>\n",
'as_html');
is ($node->as_pure_txt(), '[ Node \#0 ]', 'as_pure_txt');
$node->set_attributes ( { color => 'purple' } );
is ($node->as_txt(), '[ Node \#0 ] { color: purple; class: cities; }', 'as_txt');
is ($node->as_html(), " <td $r class='node_cities' style=\"color: #800080\">Node #0</td>\n",
'as_html');
is ($node->as_pure_txt(), '[ Node \#0 ]', 'as_pure_txt');
#############################################################################
# set_attributes(class => foo)
$node->set_attributes ( { class => 'foo', color => 'orange' } );
is ($node->class(), 'node.foo', 'class set correctly');
is ($node->sub_class(), 'foo', 'class set correctly');
is ($node->attribute('color'), 'orange', 'color set correctly');
is ($node->as_txt(), '[ Node \#0 ] { color: orange; class: foo; }', 'as_txt');
is ($node->as_html(), " <td $r class='node_foo' style=\"color: #ffa500\">Node #0</td>\n",
'as_html');
$node->set_attribute ( 'class', 'bar' );
is ($node->as_txt(), '[ Node \#0 ] { color: orange; class: bar; }', 'as_txt');
is ($node->as_html(), " <td $r class='node_bar' style=\"color: #ffa500\">Node #0</td>\n",
'as_html');
#############################################################################
# set_attribute() with encoded entities (%3a etc) and quotation marks
foreach my $l (
'http://bloodgate.com/',
'"http://bloodgate.com/"',
'"http%3a//bloodgate.com/"',
)
{
$node->set_attribute('link', $l);
is ($node->as_txt(),
'[ Node \#0 ] { color: orange; link: http://bloodgate.com/; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='http://bloodgate.com/' style=\"color: #ffa500\">Node #0</a></td>\n",
'as_html');
}
foreach my $l (
'perl/',
'"perl/"',
)
{
$node->set_attribute('link', $l);
is ($node->as_txt(),
'[ Node \#0 ] { color: orange; link: perl/; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='/wiki/index.php/perl/' style=\"color: #ffa500\">Node #0</a></td>\n",
'as_html');
}
$node->set_attribute('link', "test test&");
is ($node->as_txt(),
'[ Node \#0 ] { color: orange; link: test test&; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='/wiki/index.php/test+test&' style=\"color: #ffa500\">Node #0</a></td>\n",
'as_html');
$node->set_attribute('color', "\\#801010");
is ($node->as_txt(),
'[ Node \#0 ] { color: #801010; link: test test&; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='/wiki/index.php/test+test&' style=\"color: #801010\">Node #0</a></td>\n",
'as_html');
# test quotation marks in link:
$node->set_attribute('link', "test'test");
is ($node->as_txt(),
'[ Node \#0 ] { color: #801010; link: test\'test; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='/wiki/index.php/test%27test' style=\"color: #801010\">Node #0</a></td>\n",
'as_html');
# quotation mark at the end (but not at the start)
$node->set_attribute('link', "test'");
is ($node->as_txt(),
'[ Node \#0 ] { color: #801010; link: test\'; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='/wiki/index.php/test%27' style=\"color: #801010\">Node #0</a></td>\n",
'as_html');
#############################################################################
# multicelled nodes
is ($node->is_multicelled(), 0, 'no multicelled');
is (join (",",$node->size()), '1,1', 'size 1,1');
$node->set_attribute('size', '5,3');
$node->_calc_size();
is (join (",",$node->size()), '5,3', 'size 5,3');
is ($node->is_multicelled(), 1, 'is multicelled');
is ($node->attribute('size'), '5,3', 'attribute("size")');
$node->set_attribute('size', '1,1');
$node->_calc_size();
is ($node->{att}->{rows}, 1, 'rows still present');
is ($node->{att}->{columns}, 1, 'columns still present');
is ($node->as_txt(), "[ Node \\#0 ] { color: #801010; link: test'; class: bar; }",
'size not in output');
$node->del_attribute('size');
is (exists $node->{att}->{rows} ? 1 : 0, 0, 'rows no longer present');
is (exists $node->{att}->{columns} ? 1 : 0, 0, 'columns no longer present');
#############################################################################
# skipping of attributes (should not appear in HTML)
$node->set_attribute('link', "test test&");
$node->set_attribute('flow','right');
$node->set_attribute('point-style','diamond');
is ($node->as_txt(),
'[ Node \#0 ] { color: #801010; flow: right; link: test test&; pointstyle: diamond; class: bar; }', 'as_txt');
is ($node->as_html(),
" <td $r class='node_bar'><a href='/wiki/index.php/test+test&' style=\"color: #801010\">Node #0</a></td>\n",
'as_html');
#############################################################################
# group tests
is ($node->group(), undef, 'no groups yet');
use Graph::Easy::Group;
my $group = Graph::Easy::Group->new( { name => 'foo' } );
$node->add_to_group($group);
is ($node->group(), $group, 'group foo');
is ($node->attribute('group'), $group->{name}, 'group foo');
#############################################################################
# title tests
$node->set_attribute('title', "foo title");
is ($node->title(), 'foo title', 'foo title');
$node->del_attribute('title');
$node->set_attribute('autotitle', 'none');
is ($node->title(), '', 'no title if autotitle: none');
$node->set_attribute('autotitle', 'name');
is ($node->title(), $node->name(), 'title equals name');
$node->set_attribute('autotitle', 'label');
is ($node->title(), $node->name(), 'title equals name');
$node->set_attribute('label', 'label');
is ($node->title(), 'label', 'title equals label');
$node->set_attribute('link', '');
$node->set_attribute('autotitle', 'link');
is ($node->title(), '', 'title "" if no link');
$node->set_attribute('link', 'http://bloodgate.com/');
is ($node->title(), $node->link(), 'title eq link');
$node->set_attribute('title','my title');
is ($node->title(), 'my title', 'title will override autotitle');
#############################################################################
# invisible nodes, and nodes with shape none
$node = Graph::Easy::Node->new( { name => "anon 0", label => 'X' } );
$node->set_attribute('shape', "invisible");
is ($node->as_ascii(), "", 'invisible text node');
$node->set_attribute('shape', "none");
$node->_correct_size();
is ($node->as_ascii(), " \n X \n ", 'no border for shape "none"');
#############################################################################
# as_ascii() and label vs name (bug until v0.16)
$node = Graph::Easy::Node->new( { name => "Node #01234", label => 'label' } );
is ($node->label(), 'label', 'node label eq "label"');
$node->_correct_size();
is ($node->width(), '9', 'width 9 (length("label") + 2 (padding) + 2 (border)');
is ($node->height(), '3', 'height 3');
like ($node->as_ascii(), qr/label/, 'as_ascii uses label, not name');
#############################################################################
# node placement (unclustered)
$node = Graph::Easy::Node->new();
my $cells = { };
my $parent = { cells => $cells };
is ($node->_do_place(1,1,$parent), 1, 'node can be placed');
is ($cells->{"1,1"}, $node, 'node was really placed');
is (scalar keys %$cells, 1, 'one entry');
is ($node->_do_place(1,1,$parent), 0, 'node cannot be placed again');
is ($cells->{"1,1"}, $node, 'node still there placed');
is (scalar keys %$cells, 1, 'one entry');
#############################################################################
# outgoing/incoming
$graph = Graph::Easy->new();
my ($A,$B);
($A,$B, $edge) = $graph->add_edge('A','B');
is ($A->incoming(), 0, 'no incoming');
is ($B->outgoing(), 0, 'no outgoing');
is ($B->incoming(), 1, 'one incoming');
is ($A->outgoing(), 1, 'one outgoing');
my $C;
($B,$C, $edge) = $graph->add_edge('B', 'C');
is ($B->incoming(), 1, 'one incoming');
is ($C->incoming(), 1, 'one incoming');
is ($A->outgoing(), 1, 'one outgoing');
is ($B->outgoing(), 1, 'one outgoing');
$graph->add_edge('A', 'C');
is ($C->incoming(), 2, 'two incoming');
is ($A->outgoing(), 2, 'one outgoing');
$graph->add_edge('C', 'C');
is ($C->incoming(), 3, 'C -> C');
is ($C->outgoing(), 1, 'C -> C');
#############################################################################
# _allowed_places()
$graph = Graph::Easy->new();
($A,$B, $edge) = $graph->add_edge('A','B');
my @allowed = $A->_allowed_places ( [ 0,0, 0,1, 0,2, 0,3 ], [ 0,0, 0,2, 1,2 ]);
is_deeply (\@allowed, [ 0,0, 0,2 ], '_allowed_places');
@allowed = $A->_allowed_places ( [ 0,0, 0,1, 0,2, 0,3 ], [ ]);
is_deeply (\@allowed, [ ], '_allowed_places');
@allowed = $A->_allowed_places ( [ 0,0, 0,1, 0,2, 0,3 ], [ 3,1, 1,2, 0,4 ]);
is_deeply (\@allowed, [ ], '_allowed_places');
@allowed = $A->_allowed_places ( [ 0,0, 0,1, 0,2, 0,3 ], [ 3,1, 1,2, 0,3 ]);
is_deeply (\@allowed, [ 0,3 ], '_allowed_places');
#############################################################################
# _allow()
$A->{x} = 1; $A->{y} = 2;
$A->{cx} = 3; $A->{cy} = 2;
my $allow = $A->_allow('south','');
is_deeply ($allow, [ 1,4, 2,4, 3,4 ], 'south');
$allow = $A->_allow('south','0');
is_deeply ($allow, [ 1,4 ], 'south,0');
$allow = $A->_allow('south','1');
is_deeply ($allow, [ 2,4 ], 'south,1');
$allow = $A->_allow('south','2');
is_deeply ($allow, [ 3,4 ], 'south,2');
$allow = $A->_allow('south','3');
is_deeply ($allow, [ 3,4 ], 'south,3');
$allow = $A->_allow('south','-1');
is_deeply ($allow, [ 3,4 ], 'south,-1');
$allow = $A->_allow('south','-2');
is_deeply ($allow, [ 2,4 ], 'south,-2');
$allow = $A->_allow('south','-3');
is_deeply ($allow, [ 1,4 ], 'south,-3');
$allow = $A->_allow('south','-4');
is_deeply ($allow, [ 1,4 ], 'south,-4');
$allow = $A->_allow('north','');
is_deeply ($allow, [ 1,1, 2,1, 3,1 ], 'north');
$allow = $A->_allow('north','0');
is_deeply ($allow, [ 1,1 ], 'north,0');
$allow = $A->_allow('north','2');
is_deeply ($allow, [ 3,1 ], 'north,0');
$allow = $A->_allow('north','-1');
is_deeply ($allow, [ 3,1 ], 'north,0');
$allow = $A->_allow('west','');
is_deeply ($allow, [ 0,2, 0,3 ], 'west');
$allow = $A->_allow('west','0');
is_deeply ($allow, [ 0,2 ], 'west');
$allow = $A->_allow('west','1');
is_deeply ($allow, [ 0,3 ], 'west');
$allow = $A->_allow('east','');
is_deeply ($allow, [ 4,2, 4,3 ], 'east');
$allow = $A->_allow('east','1');
is_deeply ($allow, [ 4,3 ], 'east,1');
$allow = $A->_allow('east','2');
is_deeply ($allow, [ 4,3 ], 'east,2');
$allow = $A->_allow('east','-1');
is_deeply ($allow, [ 4,3 ], 'east,-1');
#############################################################################
# parent()
$graph = Graph::Easy->new();
($A,$B, $edge) = $graph->add_edge('A','B');
is ($A->parent(), $graph, 'parent is graph');
$group = $graph->add_group('Test');
$group->add_node($A);
is ($A->parent(), $group, 'parent is group');
#############################################################################
# angle()
my @angles = qw/south south front left -90 back -45 45 +45/;
my @expect = qw/180 180 90 0 0 270 45 45 135/;
is ($A->angle(), 0, 'default is 0 pointing up');
$A->set_attribute('rotate', 'south');
my $i = 0;
for my $e (@expect)
{
my $an = $angles[$i++];
$A->set_attribute('rotate', $an);
is ($A->angle(), $e, "expect $e for $an");
}
$A->del_attribute('flow', 'south');
is ($A->{_cached_flow}, undef, 'flow uncached by set_attribute');
$A->flow(); # cache again
$A->set_attribute('flow', 'south');
is ($A->{_cached_flow}, undef, 'flow uncached by set_attribute');
@angles = qw/south south front left -90 back -45 45 +45/;
@expect = qw/180 180 180 90 90 0 135 45 225/;
$i = 0;
for my $e (@expect)
{
my $an = $angles[$i++];
$A->set_attribute('rotate', $an);
is ($A->angle(), $e, "expect $e for $an");
}
#############################################################################
# Deleting a node should work if the node is a child node (fail untill v0.49)
$graph = Graph::Easy->new();
$A = $graph->add_node('A');
$B = $graph->add_node('B');
$B->set_attribute('origin','A');
$B->set_attribute('offset','2,2');
$graph->del_node('B');
is ($graph->as_ascii(), "+---+\n| A |\n+---+\n", 'only one node rendered');