HTML-D3

 view release on metacpan or  search on metacpan

t/unit.t  view on Meta::CPAN

	'tooltips: tooltip div present'                    => 1,
	'tooltips: mouseover handler present'              => 1,
	'tooltips: no raw </b>'                            => 1,
	'tooltips: escaped <\/b> present'                  => 1,

	# render_line_chart_snippet
	'snippet: die non-array'                           => 1,
	'snippet: returns hashref'                         => 1,
	'snippet: svg_id is chart'                         => 1,
	'snippet: html is string'                          => 1,
	'snippet: no DOCTYPE'                              => 1,
	'snippet: no html wrapper'                         => 1,
	'snippet: no body wrapper'                         => 1,
	'snippet: no head closing'                         => 1,
	'snippet: SVG element present'                     => 1,
	'snippet: tooltip div present'                     => 1,
	'snippet: mouseover present'                       => 1,
	'snippet: no raw </b>'                             => 1,
	'snippet: escaped <\/b> present'                   => 1,
	'snippet: extra hashref serialised as d.extra'     => 1,
	'snippet: extra key appears in JSON'               => 1,
	'snippet: Object.entries(d.extra) in mouseover'    => 1,
	'snippet: non-hashref third element ignored'       => 1,
	'snippet: exactly one extra in data'               => 1,

	# render_zoomable_line_chart_snippet
	'zoom: die non-array'                              => 1,
	'zoom: returns hashref'                            => 1,
	'zoom: svg_id is chart'                            => 1,
	'zoom: html is string'                             => 1,
	'zoom: no DOCTYPE'                                 => 1,
	'zoom: no html wrapper'                            => 1,
	'zoom: d3.brushX present'                          => 1,
	'zoom: Reset zoom button present'                  => 1,
	'zoom: allData declared'                           => 1,
	'zoom: currentData declared'                       => 1,
	'zoom: zoomed.length < 2 guard'                    => 1,
	'zoom: reset restores allData.slice'               => 1,
	'zoom: no raw </b>'                                => 1,
	'zoom: escaped <\/b> present'                      => 1,
	'zoom: extra data serialised'                      => 1,
	'zoom: Object.entries(d.extra) present'            => 1,

	# render_multi_series_line_chart_with_tooltips
	'ms-tt: die non-array'                             => 1,
	'ms-tt: returns string'                            => 1,
	'ms-tt: DOCTYPE present'                           => 1,
	'ms-tt: tooltip div present'                       => 1,
	'ms-tt: series name in output'                     => 1,
	'ms-tt: no raw </b>'                               => 1,
	'ms-tt: escaped <\/b> present'                     => 1,

	# render_multi_series_line_chart_with_animated_tooltips
	'ms-anim: die non-array'                           => 1,
	'ms-anim: returns string'                          => 1,
	'ms-anim: DOCTYPE present'                         => 1,
	'ms-anim: translateY animation present'            => 1,
	'ms-anim: no raw </b>'                             => 1,
	'ms-anim: escaped <\/b> present'                   => 1,

	# render_multi_series_line_chart_with_legends
	'ms-leg: die non-array'                            => 1,
	'ms-leg: returns string'                           => 1,
	'ms-leg: DOCTYPE present'                          => 1,
	'ms-leg: legend CSS class in stylesheet'           => 1,
	'ms-leg: series name in output'                    => 1,
	'ms-leg: no raw </b>'                              => 1,
	'ms-leg: escaped <\/b> present'                    => 1,

	# render_multi_series_line_chart_with_interactive_legends
	'ms-int: die non-array'                            => 1,
	'ms-int: returns string'                           => 1,
	'ms-int: DOCTYPE present'                          => 1,
	'ms-int: isVisible variable present'               => 1,
	'ms-int: isVisible ? 0 : 1 toggle present'         => 1,
	'ms-int: legend CSS class in stylesheet'           => 1,
	'ms-int: no raw </b>'                              => 1,
	'ms-int: escaped <\/b> present'                    => 1,
);

# Marks a ledger entry as verified.  Calling with an unknown key catches typos.
sub mark {
	my ($key) = @_;
	exists $LEDGER{$key}
		? delete $LEDGER{$key}
		: fail("BUG: unknown ledger key '$key'");
}

# ─────────────────────────────────────────────────────────────────────────────
# new()
# ─────────────────────────────────────────────────────────────────────────────

subtest 'new() -- default values' => sub {
	# Mock Object::Configure so no config file on the test runner's machine
	# can interfere with the default-value assertions.
	mock('Object::Configure::configure' => sub { $_[1] });

	my $chart = new_ok('HTML::D3');

	isa_ok($chart, 'HTML::D3', 'constructor returns a blessed HTML::D3 object');
	mark('new: returns blessed HTML::D3 object');

	is($chart->{width},  $DEFAULT_WIDTH,  'default width is 800');
	mark('new: default width 800');

	is($chart->{height}, $DEFAULT_HEIGHT, 'default height is 600');
	mark('new: default height 600');

	is($chart->{title},  $DEFAULT_TITLE,  'default title is "Chart"');
	mark('new: default title Chart');

	restore('Object::Configure::configure');

	diag('new() object: ' . join(', ', map { "$_=$chart->{$_}" } sort keys %$chart))
		if $ENV{TEST_VERBOSE};
};

subtest 'new() -- custom flat-hash arguments' => sub {
	my $chart = HTML::D3->new(width => 1_024, height => 768, title => 'Sales');

	is($chart->{width},  1_024,   'custom width stored');
	mark('new: custom width applied');

	is($chart->{height}, 768,     'custom height stored');
	mark('new: custom height applied');

	is($chart->{title},  'Sales', 'custom title stored');
	mark('new: custom title applied');
};

subtest 'new() -- hashref arguments' => sub {
	# The POD documents that new() accepts both flat-hash and hashref calling styles.
	my $chart = HTML::D3->new({ width => 640, height => 480, title => 'Ref' });

	is($chart->{width},  640,   'hashref width stored');
	is($chart->{height}, 480,   'hashref height stored');

t/unit.t  view on Meta::CPAN

};

subtest 'render_multi_series_line_chart_with_tooltips() -- output' => sub {
	my $html = HTML::D3->new(title => 'Multi TT')
	                   ->render_multi_series_line_chart_with_tooltips(\@MULTI_DATA);

	returns_ok($html, { type => 'string' }, 'return value is a plain string');
	mark('ms-tt: returns string');

	like($html, qr/<!DOCTYPE html>/i, 'HTML5 DOCTYPE present');
	mark('ms-tt: DOCTYPE present');

	like($html, qr/<div class="tooltip" id="tooltip">/, 'tooltip div present');
	mark('ms-tt: tooltip div present');

	like($html, qr/Series A/, 'first series name present in output');
	mark('ms-tt: series name in output');

	unlike($html, qr{</b>},   'raw </b> absent');
	mark('ms-tt: no raw </b>');

	like($html, qr{<\\/b>}, 'escaped <\/b> present');
	mark('ms-tt: escaped <\/b> present');
};

# ─────────────────────────────────────────────────────────────────────────────
# render_multi_series_line_chart_with_animated_tooltips()
# ─────────────────────────────────────────────────────────────────────────────

subtest 'render_multi_series_line_chart_with_animated_tooltips() -- validation' => sub {
	throws_ok(
		sub { HTML::D3->new()->render_multi_series_line_chart_with_animated_tooltips('bad') },
		qr/\Q$ERR_ARRAY_OF_HASH\E/,
		'non-array data dies',
	);
	mark('ms-anim: die non-array');
};

subtest 'render_multi_series_line_chart_with_animated_tooltips() -- output' => sub {
	my $html = HTML::D3->new(title => 'Animated')
	                   ->render_multi_series_line_chart_with_animated_tooltips(\@MULTI_DATA);

	returns_ok($html, { type => 'string' }, 'return value is a plain string');
	mark('ms-anim: returns string');

	like($html, qr/<!DOCTYPE html>/i, 'HTML5 DOCTYPE present');
	mark('ms-anim: DOCTYPE present');

	# POD specifies a CSS translateY slide-in animation on the tooltip.
	like($html, qr/translateY/, 'CSS translateY animation present');
	mark('ms-anim: translateY animation present');

	unlike($html, qr{</b>},   'raw </b> absent');
	mark('ms-anim: no raw </b>');

	like($html, qr{<\\/b>}, 'escaped <\/b> present');
	mark('ms-anim: escaped <\/b> present');
};

# ─────────────────────────────────────────────────────────────────────────────
# render_multi_series_line_chart_with_legends()
# ─────────────────────────────────────────────────────────────────────────────

subtest 'render_multi_series_line_chart_with_legends() -- validation' => sub {
	throws_ok(
		sub { HTML::D3->new()->render_multi_series_line_chart_with_legends('bad') },
		qr/\Q$ERR_ARRAY_OF_HASH\E/,
		'non-array data dies',
	);
	mark('ms-leg: die non-array');
};

subtest 'render_multi_series_line_chart_with_legends() -- output' => sub {
	my $html = HTML::D3->new(title => 'Legends')
	                   ->render_multi_series_line_chart_with_legends(\@MULTI_DATA);

	returns_ok($html, { type => 'string' }, 'return value is a plain string');
	mark('ms-leg: returns string');

	like($html, qr/<!DOCTYPE html>/i, 'HTML5 DOCTYPE present');
	mark('ms-leg: DOCTYPE present');

	# POD specifies a .legend CSS class defined in the stylesheet.
	like($html, qr/\.legend\s*\{/, '.legend CSS class defined in <style> block');
	mark('ms-leg: legend CSS class in stylesheet');

	like($html, qr/Series A/, 'series name present in output');
	mark('ms-leg: series name in output');

	unlike($html, qr{</b>},   'raw </b> absent');
	mark('ms-leg: no raw </b>');

	like($html, qr{<\\/b>}, 'escaped <\/b> present');
	mark('ms-leg: escaped <\/b> present');
};

# ─────────────────────────────────────────────────────────────────────────────
# render_multi_series_line_chart_with_interactive_legends()
# ─────────────────────────────────────────────────────────────────────────────

subtest 'render_multi_series_line_chart_with_interactive_legends() -- validation' => sub {
	throws_ok(
		sub { HTML::D3->new()->render_multi_series_line_chart_with_interactive_legends('bad') },
		qr/\Q$ERR_ARRAY_OF_HASH\E/,
		'non-array data dies',
	);
	mark('ms-int: die non-array');
};

subtest 'render_multi_series_line_chart_with_interactive_legends() -- output' => sub {
	my $html = HTML::D3->new(title => 'Interactive')
	                   ->render_multi_series_line_chart_with_interactive_legends(\@MULTI_DATA);

	returns_ok($html, { type => 'string' }, 'return value is a plain string');
	mark('ms-int: returns string');

	like($html, qr/<!DOCTYPE html>/i, 'HTML5 DOCTYPE present');
	mark('ms-int: DOCTYPE present');

	# POD: "isVisible variable tracks current visibility state"
	like($html, qr/isVisible/, 'isVisible variable declared in legend click handler');
	mark('ms-int: isVisible variable present');

	# POD: "opacity toggled by isVisible ? 0 : 1"
	like($html, qr/isVisible\s*\?\s*0\s*:\s*1/, 'isVisible ? 0 : 1 opacity toggle present');
	mark('ms-int: isVisible ? 0 : 1 toggle present');

	like($html, qr/\.legend\s*\{/, '.legend CSS class defined in <style> block');
	mark('ms-int: legend CSS class in stylesheet');

	unlike($html, qr{</b>},   'raw </b> absent');
	mark('ms-int: no raw </b>');

	like($html, qr{<\\/b>}, 'escaped <\/b> present');
	mark('ms-int: escaped <\/b> present');
};

# ─────────────────────────────────────────────────────────────────────────────
# Ledger check -- every documented condition must have been exercised.
# ─────────────────────────────────────────────────────────────────────────────

subtest 'API contract ledger -- all documented conditions exercised' => sub {
	if(keys %LEDGER) {
		for my $untested (sort keys %LEDGER) {
			fail("UNTESTED documented condition: $untested");
		}
	} else {
		pass('All documented conditions were covered by the test suite');
	}
};

done_testing();



( run in 1.073 second using v1.01-cache-2.11-cpan-364913b4093 )