Selkie.git | t/ | 54-scatter.rakutest


use Test;
use lib 'lib';

use Selkie::Widget::ScatterPlot;

plan 9;

subtest "construction with single series" => {
	plan 3;
	my $sp = Selkie::Widget::ScatterPlot.new(
		series => [
			{ label => 's1', points => [1 => 2, 3 => 4] },
		],
	);
	isa-ok $sp, Selkie::Widget::ScatterPlot;
	is $sp.series.elems, 1, "one series stored";
	is $sp.overlap, 'z-order', "default overlap is z-order";
};

subtest "rejects series + store-path combo" => {
	plan 1;
	dies-ok {
		Selkie::Widget::ScatterPlot.new(
			series     => [{ label => 's', points => [] }],
			store-path => <viz scatter>,
		);
	}, "mutually exclusive";
};

subtest "rejects unknown overlap mode" => {
	plan 1;
	dies-ok {
		Selkie::Widget::ScatterPlot.new(series => [], overlap => 'blend');
	}, "unknown overlap rejected";
};

subtest "explicit ranges stored" => {
	plan 4;
	my $sp = Selkie::Widget::ScatterPlot.new(
		series => [],
		x-min  => -10, x-max => 10,
		y-min  =>   0, y-max =>  5,
	);
	is $sp.x-min, -10, "x-min";
	is $sp.x-max,  10, "x-max";
	is $sp.y-min,   0, "y-min";
	is $sp.y-max,   5, "y-max";
};

subtest "set-series replaces and dirties" => {
	plan 2;
	my $sp = Selkie::Widget::ScatterPlot.new(
		series => [{ label => 'a', points => [1 => 1] }],
	);
	$sp.clear-dirty;
	$sp.set-series([
		{ label => 'b', points => [2 => 2] },
		{ label => 'c', points => [3 => 3] },
	]);
	is $sp.series.elems, 2, "two series";
	ok $sp.is-dirty, "marked dirty";
};

subtest "braille-bit returns correct sub-cell positions" => {
	plan 8;
	my $sp = Selkie::Widget::ScatterPlot.new(series => []);
	# Sub-cell layout: bit 0..7 for the 8 dot positions
	is $sp.braille-bit(0, 0), 0, "(col 0, row 0) → bit 0";
	is $sp.braille-bit(0, 1), 1, "(col 0, row 1) → bit 1";
	is $sp.braille-bit(0, 2), 2, "(col 0, row 2) → bit 2";
	is $sp.braille-bit(0, 3), 6, "(col 0, row 3) → bit 6";
	is $sp.braille-bit(1, 0), 3, "(col 1, row 0) → bit 3";
	is $sp.braille-bit(1, 1), 4, "(col 1, row 1) → bit 4";
	is $sp.braille-bit(1, 2), 5, "(col 1, row 2) → bit 5";
	is $sp.braille-bit(1, 3), 7, "(col 1, row 3) → bit 7";
};

subtest "braille-glyph maps each of 256 patterns to a unique codepoint" => {
	plan 3;
	my $sp = Selkie::Widget::ScatterPlot.new(series => []);
	# Sample boundary cases
	is $sp.braille-glyph(0),   '⠀', "0 → U+2800 (empty cell)";
	is $sp.braille-glyph(255), '⣿', "255 → U+28FF (all 8 dots)";
	# Single dot at bit 0 = U+2801 = '⠁'
	is $sp.braille-glyph(1),   '⠁', "bit 0 → U+2801";
};

subtest "braille glyphs are exhaustively unique across all 256 patterns" => {
	plan 1;
	my $sp = Selkie::Widget::ScatterPlot.new(series => []);
	my %seen;
	my $duplicate = False;
	for ^256 -> $bits {
		my $g = $sp.braille-glyph($bits);
		if %seen{$g}:exists {
			$duplicate = True;
			last;
		}
		%seen{$g} = $bits;
	}
	nok $duplicate, "all 256 bit patterns produce unique glyphs";
};

subtest "series built from a Seq of points survives repeated access" => {
	plan 2;
	# TUI apps re-render every frame; points must survive beyond the
	# first iteration or the scatter plot would go blank on frame 2.
	my $sp = Selkie::Widget::ScatterPlot.new(
		series => [{
			label  => 'dots',
			points => (1..5).map: { $_ => $_ * 2 },
		}],
	);
	is $sp.series[0]<points>[2].value, 6, "first access sees points[2] = 3 => 6";
	is $sp.series[0]<points>[2].value, 6, "second access still sees 6";
};