Selkie.git | t/ | 52-histogram.rakutest


use Test;
use lib 'lib';

use Selkie::Widget::Histogram;

plan 9;

subtest "construction with equal-width bins" => {
	plan 3;
	my $h = Selkie::Widget::Histogram.new(
		values => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
		bins   => 5,
	);
	isa-ok $h, Selkie::Widget::Histogram;
	is $h.values.elems, 10, "values stored";
	is $h.bins,         5,  "bins stored";
};

subtest "construction with explicit edges" => {
	plan 1;
	my $h = Selkie::Widget::Histogram.new(
		values    => [1, 5, 10, 15, 20],
		bin-edges => [0, 10, 20],
	);
	is $h.bin-edges.list, (0, 10, 20), "edges stored";
};

subtest "rejects neither bins nor bin-edges" => {
	plan 1;
	dies-ok { Selkie::Widget::Histogram.new(values => [1, 2, 3]) },
		"requires bins or bin-edges";
};

subtest "rejects both bins and bin-edges" => {
	plan 1;
	dies-ok {
		Selkie::Widget::Histogram.new(
			values    => [1, 2, 3],
			bins      => 3,
			bin-edges => [0, 1, 2, 3],
		);
	}, "mutually exclusive";
};

subtest "rejects non-ascending bin-edges" => {
	plan 1;
	dies-ok {
		Selkie::Widget::Histogram.new(
			values    => [1, 2],
			bin-edges => [10, 5, 0],
		);
	}, "descending edges rejected";
};

subtest "equal-width bins count correctly" => {
	plan 5;
	# 100 values evenly distributed in [0, 100). 10 bins of width 10
	# each get exactly 10 samples.
	my @vals = (0..99).map: *.Real;
	my $h = Selkie::Widget::Histogram.new(values => @vals, bins => 10);
	is $h.data.elems, 10,       "ten bins";
	is $h.data[0]<value>, 10,    "bin 0 has 10 samples";
	is $h.data[9]<value>, 10,    "bin 9 has 10 samples";
	is $h.data[5]<value>, 10,    "middle bin has 10 samples";
	# Total counts should sum to input size
	my $total = $h.data.map(*.<value>).sum;
	is $total, 100, "total counts match input size";
};

subtest "explicit edges with left-closed/right-open intervals" => {
	plan 4;
	# Edges [0, 10, 20, 30] → bins [0,10), [10,20), [20,30]
	# Values: 0 → bin 0; 10 → bin 1; 20 → bin 2; 29.9 → bin 2; 30 → bin 2
	my $h = Selkie::Widget::Histogram.new(
		values    => [0, 5, 10, 15, 20, 25, 29.9, 30],
		bin-edges => [0, 10, 20, 30],
	);
	is $h.data.elems,    3,  "three bins";
	is $h.data[0]<value>, 2, "bin 0: 2 samples (0, 5)";
	is $h.data[1]<value>, 2, "bin 1: 2 samples (10, 15) — boundary 10 goes to bin 1, not bin 0";
	is $h.data[2]<value>, 4, "bin 2: 4 samples (20, 25, 29.9, 30) — last bin includes max";
};

subtest "empty values produce zero bars" => {
	plan 1;
	my $h = Selkie::Widget::Histogram.new(values => [], bins => 5);
	is $h.data.elems, 0, "no bars when input is empty";
};

subtest "single-value input produces a single bar" => {
	plan 2;
	my $h = Selkie::Widget::Histogram.new(values => [42, 42, 42], bins => 5);
	# All identical — degenerate case, returns one bar with all counts
	is $h.data.elems, 1, "single bar for degenerate input";
	is $h.data[0]<value>, 3, "all three samples counted";
};