Selkie.git | t/ | 84-border-title.rakutest
use Test;
use lib 'lib';
use Selkie::BorderStyle;
use Selkie::Widget::Border;
# Title placement is pure arithmetic exposed as a class method, so all
# of it is testable without a notcurses plane. The invariant that
# matters: a title never lands on a corner glyph.
plan 11;
my constant B = Selkie::Widget::Border;
subtest "title defaults" => {
plan 5;
my $b = Selkie::Widget::Border.new;
is $b.title-align, TitleLeft, "top title is left-aligned by default";
is $b.bottom-title, '', "no bottom title by default";
is $b.bottom-title-align, TitleLeft,
"bottom title is left-aligned by default too (symmetry)";
is $b.title-prefix, ' ', "single-space prefix";
is $b.title-suffix, ' ', "single-space suffix";
};
subtest "left alignment is byte-identical to the historical column" => {
plan 5;
# Border has always drawn its title at column 2. Every existing
# golden in this distro and downstream depends on it.
for 13, 24, 40, 120 -> $cols {
is B.title-column(TitleLeft, $cols, ' Settings '), 2,
"cols=$cols → column 2";
}
# The one case where left-alignment moved: a title truncated to the
# full interior width used to be painted at column 2, which ran the
# last character over the top-right corner glyph. It now starts at
# column 1 and the corner survives. No golden in this distro or
# downstream exercised that (it needs title.chars >= cols-4).
is B.title-column(TitleLeft, 12, ' ' ~ ('x' x 8) ~ ' '), 1,
"a title filling the interior is pulled back off the corner";
};
subtest "centre alignment" => {
plan 3;
# cols=24, decorated=10 → (24-10)/2 = 7
is B.title-column(TitleCenter, 24, ' Settings '), 7, "even leftover";
# cols=20, decorated=5 → 15/2 = 7.5 → floor 7
is B.title-column(TitleCenter, 20, ' abc '), 7, "odd leftover floors";
is B.title-column(TitleCenter, 21, ' abc '), 8, "odd width";
};
subtest "right alignment" => {
plan 3;
# Ends two columns short of the right corner: last cell is cols-3.
is B.title-column(TitleRight, 24, ' Settings '), 12, "cols=24";
is B.title-column(TitleRight, 24, ' Settings ') + ' Settings '.chars,
22, "last painted column is cols-3";
is B.title-column(TitleRight, 40, ' a '), 35, "narrow title";
};
subtest "corners are never overwritten" => {
plan 3;
# Property sweep: for every width and every title that fits, the
# placement stays strictly inside the corners.
my @bad-lo;
my @bad-hi;
for 5 .. 60 -> $cols {
for 1 .. ($cols - 4) -> $chars {
my $decorated = ' ' ~ ('x' x $chars) ~ ' ';
for TitleLeft, TitleCenter, TitleRight -> $align {
my $x = B.title-column($align, $cols, $decorated);
@bad-lo.push("$align/$cols/$chars") if $x < 1;
@bad-hi.push("$align/$cols/$chars")
if $x + $decorated.chars > $cols - 1;
}
}
}
is @bad-lo.elems, 0, "never starts on the left corner";
is @bad-hi.elems, 0, "never runs into the right corner";
ok @bad-lo.elems + @bad-hi.elems == 0,
"sweep covered widths 5..60 with clean results";
};
subtest "clamping when the title fills the whole interior" => {
plan 3;
# decorated.chars == cols-2 exactly fills columns 1..cols-2. The
# only legal start is 1, whatever the alignment asked for.
my $decorated = ' ' ~ ('x' x 18) ~ ' '; # 20 chars
is B.title-column(TitleLeft, 22, $decorated), 1, "left clamped down";
is B.title-column(TitleCenter, 22, $decorated), 1, "centre already 1";
is B.title-column(TitleRight, 22, $decorated), 1, "right clamped up";
};
subtest "cols == 5 degenerate width" => {
plan 4;
# The narrowest frame that still shows a title: one character of
# text between prefix and suffix, filling columns 1..3.
my $decorated = ' X ';
is B.title-column(TitleLeft, 5, $decorated), 1, "left";
is B.title-column(TitleCenter, 5, $decorated), 1, "centre";
is B.title-column(TitleRight, 5, $decorated), 1, "right";
ok B.title-column(TitleLeft, 5, $decorated) + 3 == 4,
"ends on the last interior column, leaving the corner alone";
};
subtest "over-long decorated strings bottom out at column 1" => {
plan 2;
# Border truncates before it gets here, so this is defence in depth
# for direct callers: the clamp can't produce a negative column.
is B.title-column(TitleRight, 10, ' ' x 40), 1, "right";
is B.title-column(TitleCenter, 10, ' ' x 40), 1, "centre";
};
subtest "title setters mark dirty" => {
plan 4;
my $b = Selkie::Widget::Border.new;
$b.clear-dirty;
$b.set-title-align(TitleCenter);
ok $b.is-dirty, "set-title-align marks dirty";
is $b.title-align, TitleCenter, "and takes effect";
$b.clear-dirty;
$b.set-bottom-title('q quit');
ok $b.is-dirty, "set-bottom-title marks dirty";
$b.clear-dirty;
$b.set-bottom-title-align(TitleRight);
ok $b.is-dirty, "set-bottom-title-align marks dirty";
};
subtest "affix setter" => {
plan 3;
my $b = Selkie::Widget::Border.new;
$b.clear-dirty;
$b.set-title-affixes('┤ ', ' ├');
ok $b.is-dirty, "set-title-affixes marks dirty";
is $b.title-prefix, '┤ ', "prefix stored";
is $b.title-suffix, ' ├', "suffix stored";
};
subtest "constructor accepts every title knob" => {
plan 6;
my $b = Selkie::Widget::Border.new(
title => 'Inbox',
title-align => TitleCenter,
bottom-title => 'q quit',
bottom-title-align => TitleRight,
title-prefix => '[',
title-suffix => ']',
);
is $b.title, 'Inbox', "title";
is $b.title-align, TitleCenter, "title-align";
is $b.bottom-title, 'q quit', "bottom-title";
is $b.bottom-title-align, TitleRight, "bottom-title-align";
is $b.title-prefix, '[', "title-prefix";
is $b.title-suffix, ']', "title-suffix";
};