Selkie.git | t/ | 83-border-style.rakutest
use Test;
use lib 'lib';
use Selkie::BorderStyle;
use Selkie::Widget::Border;
# Glyph-set plumbing: the tables themselves, the .for() cache, and the
# override precedence Border applies between an explicit glyph table
# and a BorderKind. Nothing here needs a plane — BorderGlyphs is a
# plain value class and effective-glyphs is pure.
plan 10;
# Kind => (top-left, top-right, bottom-left, bottom-right, horizontal,
# vertical). Pinning the literal glyphs is the point of the test: these
# are load-bearing for every Border snapshot in the distro and for
# downstream apps' goldens.
my @tables =
BorderSingle, <┌ ┐ └ ┘ ─ │>,
BorderRounded, <╭ ╮ ╰ ╯ ─ │>,
BorderDouble, <╔ ╗ ╚ ╝ ═ ║>,
BorderHeavy, <┏ ┓ ┗ ┛ ━ ┃>,
BorderAscii, ('+', '+', '+', '+', '-', '|'),
;
subtest "stock glyph tables" => {
plan @tables.elems div 2;
for @tables -> $kind, @g {
subtest "$kind" => {
plan 6;
my $t = BorderGlyphs.for($kind);
is $t.top-left, @g[0], "top-left";
is $t.top-right, @g[1], "top-right";
is $t.bottom-left, @g[2], "bottom-left";
is $t.bottom-right, @g[3], "bottom-right";
is $t.horizontal, @g[4], "horizontal";
is $t.vertical, @g[5], "vertical";
};
}
};
subtest "every glyph is exactly one character" => {
plan @tables.elems div 2;
# A multi-column glyph would shear the frame: the horizontal run is
# emitted as one putstr of cols-2 copies.
for @tables -> $kind, @ {
my $t = BorderGlyphs.for($kind);
my @lens = ($t.top-left, $t.top-right, $t.bottom-left,
$t.bottom-right, $t.horizontal, $t.vertical).map(*.chars);
is-deeply @lens, [1, 1, 1, 1, 1, 1], "$kind glyphs are single characters";
}
};
subtest "BorderAscii is 7-bit clean" => {
plan 1;
my $t = BorderGlyphs.for(BorderAscii);
my $all = $t.top-left ~ $t.top-right ~ $t.bottom-left
~ $t.bottom-right ~ $t.horizontal ~ $t.vertical;
ok $all.ords.all < 128, "no codepoint above 127 — the escape hatch works";
};
subtest ".for is cached" => {
plan 2;
ok BorderGlyphs.for(BorderRounded) === BorderGlyphs.for(BorderRounded),
"same kind returns the identical object";
nok BorderGlyphs.for(BorderRounded) === BorderGlyphs.for(BorderDouble),
"different kinds are different objects";
};
subtest "factory methods match .for" => {
plan 5;
ok BorderGlyphs.single.horizontal eq BorderGlyphs.for(BorderSingle).horizontal, "single";
ok BorderGlyphs.rounded.top-left eq BorderGlyphs.for(BorderRounded).top-left, "rounded";
ok BorderGlyphs.double.vertical eq BorderGlyphs.for(BorderDouble).vertical, "double";
ok BorderGlyphs.heavy.bottom-right eq BorderGlyphs.for(BorderHeavy).bottom-right, "heavy";
ok BorderGlyphs.ascii.top-right eq BorderGlyphs.for(BorderAscii).top-right, "ascii";
};
subtest "custom glyph tables" => {
plan 3;
my $g = BorderGlyphs.new(
top-left => '.', top-right => '.',
bottom-left => "'", bottom-right => "'",
horizontal => '.', vertical => ':',
);
is $g.top-left, '.', "custom glyph stored";
is $g.vertical, ':', "custom vertical stored";
my $derived = BorderGlyphs.for(BorderSingle).clone(top-left => '◤');
is-deeply
($derived.top-left, $derived.top-right, $derived.horizontal),
('◤', '┐', '─'),
"clone varies one glyph and keeps the rest";
};
subtest "all six glyphs are required" => {
plan 1;
dies-ok { BorderGlyphs.new(top-left => '+', top-right => '+') },
"a partial table is a construction error, not a half-drawn frame";
};
subtest "Border defaults to BorderSingle" => {
plan 3;
my $b = Selkie::Widget::Border.new;
is $b.border-style, BorderSingle, "default kind";
nok $b.border-glyphs.defined, "no explicit glyph table by default";
ok $b.effective-glyphs === BorderGlyphs.for(BorderSingle),
"effective-glyphs resolves to the single-line table";
};
subtest "effective-glyphs precedence" => {
plan 4;
my $custom = BorderGlyphs.new(
top-left => 'a', top-right => 'b',
bottom-left => 'c', bottom-right => 'd',
horizontal => 'e', vertical => 'f',
);
my $b = Selkie::Widget::Border.new(border-style => BorderDouble);
ok $b.effective-glyphs === BorderGlyphs.for(BorderDouble),
"kind wins when no explicit table is set";
$b.set-border-glyphs($custom);
ok $b.effective-glyphs === $custom,
"explicit table beats the kind";
$b.set-border-style(BorderHeavy);
ok $b.effective-glyphs === $custom,
"changing the kind under an explicit table changes nothing visible";
$b.clear-border-glyphs;
ok $b.effective-glyphs === BorderGlyphs.for(BorderHeavy),
"clearing the table falls back to the current kind";
};
subtest "glyph setters mark dirty" => {
plan 4;
my $b = Selkie::Widget::Border.new;
my $custom = BorderGlyphs.for(BorderAscii);
$b.clear-dirty;
$b.set-border-style(BorderRounded);
ok $b.is-dirty, "set-border-style marks dirty";
$b.clear-dirty;
$b.set-border-glyphs($custom);
ok $b.is-dirty, "set-border-glyphs marks dirty";
$b.clear-dirty;
$b.clear-border-glyphs;
ok $b.is-dirty, "clear-border-glyphs marks dirty";
# Constructor-set kind must be honoured too — Mindmoor/Cantina set
# it at build time and never call the setter.
my $c = Selkie::Widget::Border.new(border-style => BorderAscii);
ok $c.effective-glyphs === BorderGlyphs.for(BorderAscii),
"constructor-set kind resolves";
};