Notcurses-Native.git | xt/ | 13-widget-exhaustive.rakutest edit


use Test;
use lib 'lib';
use lib 't/lib';
use TestHelper;
use NativeCall;
use Notcurses::Native;
use Notcurses::Native::Types;
use Notcurses::Native::Plane;
use Notcurses::Native::Cell;
use Notcurses::Native::Widgets;

plan 8;


my $opts = NotcursesOptions.new(
	:loglevel(NCLOGLEVEL_SILENT),
	:flags(NCOPTION_SUPPRESS_BANNERS +| NCOPTION_NO_ALTERNATE_SCREEN +| NCOPTION_INHIBIT_SETLOCALE),
);
my ($nc, $) = test-init-nc($opts);
unless $nc.defined {
    skip 'No terminal available', 8;
    exit 0;
}
LEAVE { notcurses_stop($nc) if $nc.defined }
my $std = notcurses_stdplane($nc);

# === 1. Progress bar lifecycle ===

subtest 'Progress bar lifecycle' => {
    plan 16;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(1), :cols(40)));
    ok $plane.defined, 'Created plane for progress bar';

    my $popts = NcprogbarOptions.new(:ulchannel(0), :urchannel(0), :blchannel(0), :brchannel(0));
    my $bar = ncprogbar_create($plane, $popts);
    ok $bar.defined, 'Progress bar created';

    # Set and verify progress at multiple points
    my int32 $rc;

    $rc = ncprogbar_set_progress($bar, 0e0);
    is $rc, 0, 'Set progress to 0.0';
    my num64 $p = ncprogbar_progress($bar);
    ok $p >= -0.01e0 && $p <= 0.01e0, "Read back progress 0.0 (got $p)";

    $rc = ncprogbar_set_progress($bar, 0.25e0);
    is $rc, 0, 'Set progress to 0.25';
    $p = ncprogbar_progress($bar);
    ok $p >= 0.24e0 && $p <= 0.26e0, "Read back progress 0.25 (got $p)";

    $rc = ncprogbar_set_progress($bar, 0.5e0);
    is $rc, 0, 'Set progress to 0.5';
    $p = ncprogbar_progress($bar);
    ok $p >= 0.49e0 && $p <= 0.51e0, "Read back progress 0.5 (got $p)";

    $rc = ncprogbar_set_progress($bar, 0.75e0);
    is $rc, 0, 'Set progress to 0.75';
    $p = ncprogbar_progress($bar);
    ok $p >= 0.74e0 && $p <= 0.76e0, "Read back progress 0.75 (got $p)";

    $rc = ncprogbar_set_progress($bar, 1e0);
    is $rc, 0, 'Set progress to 1.0';
    $p = ncprogbar_progress($bar);
    ok $p >= 0.99e0 && $p <= 1.01e0, "Read back progress 1.0 (got $p)";

    # Verify plane accessor
    my $bplane = ncprogbar_plane($bar);
    ok $bplane.defined, 'ncprogbar_plane returns a valid plane';

    # Test retrograde flag
    my $plane2 = ncplane_create($std, NcplaneOptions.new(:rows(1), :cols(40)));
    my $retro-opts = NcprogbarOptions.new(:flags(NCPROGBAR_OPTION_RETROGRADE));
    my $retro-bar = ncprogbar_create($plane2, $retro-opts);
    ok $retro-bar.defined, 'Retrograde progress bar created';

    $rc = ncprogbar_set_progress($retro-bar, 0.5e0);
    is $rc, 0, 'Set retrograde bar progress to 0.5';
    $p = ncprogbar_progress($retro-bar);
    ok $p >= 0.49e0 && $p <= 0.51e0, "Retrograde bar reads back 0.5 (got $p)";

    ncprogbar_destroy($retro-bar);
    ncprogbar_destroy($bar);
};

# === 2. Reel lifecycle ===

subtest 'Reel lifecycle' => {
    plan 5;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(30)));
    ok $plane.defined, 'Created plane for reel';

    my $ropts = NcreelOptions.new;
    my $reel = ncreel_create($plane, $ropts);
    ok $reel.defined, 'Reel created';

    is ncreel_tabletcount($reel), 0, 'Empty reel has 0 tablets';

    my $rplane = ncreel_plane($reel);
    ok $rplane.defined, 'ncreel_plane returns a valid plane';

    my int32 $rc = ncreel_redraw($reel);
    is $rc, 0, 'ncreel_redraw succeeds on empty reel';

    ncreel_destroy($reel);
};

# === 3. Tabbed full lifecycle ===

subtest 'Tabbed full lifecycle' => {
    plan 24;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(50)));
    ok $plane.defined, 'Created plane for tabbed widget';

    my $topts = NctabbedOptions.new(:separator(' | '));
    my $tabbed = nctabbed_create($plane, $topts);
    ok $tabbed.defined, 'Tabbed widget created';
    is nctabbed_tabcount($tabbed), 0, 'No tabs initially';

    # Add 3 tabs (pass type objects for NULL after/before, Pointer for NULL callback/opaque)
    my $tab1 = nctabbed_add($tabbed, NctabHandle, NctabHandle, Pointer, 'Tab1', Pointer);
    ok $tab1.defined, 'Added Tab1';

    my $tab2 = nctabbed_add($tabbed, NctabHandle, NctabHandle, Pointer, 'Tab2', Pointer);
    ok $tab2.defined, 'Added Tab2';

    my $tab3 = nctabbed_add($tabbed, NctabHandle, NctabHandle, Pointer, 'Tab3', Pointer);
    ok $tab3.defined, 'Added Tab3';

    is nctabbed_tabcount($tabbed), 3, 'Tab count is 3 after adding 3 tabs';

    # Get selected tab and verify its name
    my $selected = nctabbed_selected($tabbed);
    ok $selected.defined, 'Got selected tab handle';
    my $name = nctab_name($selected);
    ok $name.defined, "Selected tab has a name: $name";

    # Navigate forward
    my $next = nctabbed_next($tabbed);
    ok $next.defined, 'nctabbed_next returns a tab';
    my $next-name = nctab_name($next);
    ok $next-name.defined, "Next tab name: $next-name";

    # Navigate backward
    my $prev = nctabbed_prev($tabbed);
    ok $prev.defined, 'nctabbed_prev returns a tab';

    # Verify separator
    my $sep = nctabbed_separator($tabbed);
    is $sep, ' | ', 'Separator is " | "';

    my int32 $sep-width = nctabbed_separator_width($tabbed);
    is $sep-width, 3, 'Separator width is 3';

    # Get/set channels
    my uint64 $hdrchan = nctabbed_hdrchan($tabbed);
    ok $hdrchan.defined, "Got hdrchan: $hdrchan";

    my uint64 $selchan = nctabbed_selchan($tabbed);
    ok $selchan.defined, "Got selchan: $selchan";

    my uint64 $sepchan = nctabbed_sepchan($tabbed);
    ok $sepchan.defined, "Got sepchan: $sepchan";

    nctabbed_set_hdrchan($tabbed, 0x0000FF00_0000FF00);
    my uint64 $new-hdr = nctabbed_hdrchan($tabbed);
    is $new-hdr, 0x0000FF00_0000FF00, 'Set and read back hdrchan';

    # Set tab name
    my int32 $rc = nctab_set_name($tab1, 'Renamed');
    is $rc, 0, 'nctab_set_name succeeded';
    my $renamed = nctab_name($tab1);
    is $renamed, 'Renamed', 'Tab name updated to Renamed';

    # Move tabs
    nctab_move_right($tabbed, $tab1);
    nctab_move_left($tabbed, $tab1);
    ok True, 'Move right/left did not crash';

    # Get content plane
    my $cplane = nctabbed_content_plane($tabbed);
    ok $cplane.defined, 'Got content plane';

    # Delete a tab
    $rc = nctabbed_del($tabbed, $tab3);
    is $rc, 0, 'Deleted Tab3';
    is nctabbed_tabcount($tabbed), 2, 'Tab count is 2 after deletion';

    # Redraw and destroy
    nctabbed_redraw($tabbed);
    nctabbed_destroy($tabbed);
};

# === 4. uint64 Plot lifecycle ===

subtest 'uint64 Plot lifecycle' => {
    plan 8;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(40)));
    ok $plane.defined, 'Created plane for uint64 plot';

    my $popts = NcplotOptions.new(:rangex(10));
    my $plot = ncuplot_create($plane, $popts, 0, 100);
    ok $plot.defined, 'ncuplot created with range 0..100';

    my $pplane = ncuplot_plane($plot);
    ok $pplane.defined, 'ncuplot_plane returns valid plane';

    my int32 $rc;
    $rc = ncuplot_add_sample($plot, 0, 50);
    is $rc, 0, 'ncuplot_add_sample(0, 50) succeeded';

    $rc = ncuplot_add_sample($plot, 1, 75);
    is $rc, 0, 'ncuplot_add_sample(1, 75) succeeded';

    $rc = ncuplot_set_sample($plot, 2, 25);
    is $rc, 0, 'ncuplot_set_sample(2, 25) succeeded';

    # Read back sample at slot 2
    my uint64 $y = 0;
    $rc = ncuplot_sample($plot, 2, $y);
    is $rc, 0, 'ncuplot_sample read succeeded';
    is $y, 25, 'ncuplot_sample reads back 25 at slot 2';

    ncuplot_destroy($plot);
};

# === 5. double Plot lifecycle ===

subtest 'double Plot lifecycle' => {
    plan 8;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(40)));
    ok $plane.defined, 'Created plane for double plot';

    my $popts = NcplotOptions.new(:rangex(10));
    my $plot = ncdplot_create($plane, $popts, 0e0, 100e0);
    ok $plot.defined, 'ncdplot created with range 0.0..100.0';

    my $pplane = ncdplot_plane($plot);
    ok $pplane.defined, 'ncdplot_plane returns valid plane';

    my int32 $rc;
    $rc = ncdplot_add_sample($plot, 0, 50.5e0);
    is $rc, 0, 'ncdplot_add_sample(0, 50.5) succeeded';

    $rc = ncdplot_set_sample($plot, 1, 75.25e0);
    is $rc, 0, 'ncdplot_set_sample(1, 75.25) succeeded';

    # Read back sample at slot 1
    my num64 $y = 0e0;
    $rc = ncdplot_sample($plot, 1, $y);
    is $rc, 0, 'ncdplot_sample read succeeded';
    ok $y >= 75.24e0 && $y <= 75.26e0, "ncdplot_sample reads back ~75.25 at slot 1 (got $y)";

    $rc = ncdplot_set_sample($plot, 2, 0e0);
    is $rc, 0, 'ncdplot_set_sample(2, 0.0) succeeded for boundary value';

    ncdplot_destroy($plot);
};

# === 6. Reader lifecycle ===

subtest 'Reader lifecycle' => {
    plan 10;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(3), :cols(30)));
    ok $plane.defined, 'Created plane for reader';

    my $ropts = NcreaderOptions.new(:flags(NCREADER_OPTION_CURSOR));
    my $reader = ncreader_create($plane, $ropts);
    ok $reader.defined, 'Reader created with cursor option';

    my $rplane = ncreader_plane($reader);
    ok $rplane.defined, 'ncreader_plane returns valid plane';

    # Write text character by character
    my int32 $rc;
    $rc = ncreader_write_egc($reader, 'H');
    is $rc, 0, 'Wrote "H" to reader';

    $rc = ncreader_write_egc($reader, 'i');
    is $rc, 0, 'Wrote "i" to reader';

    # Read contents back
    my $contents = ncreader_contents($reader);
    ok $contents.defined && $contents.contains('Hi'), "Contents contain 'Hi' (got: {$contents.raku})";

    # Move cursor left and right
    $rc = ncreader_move_left($reader);
    ok $rc == 0 || $rc == -1, "ncreader_move_left returned $rc (0 or -1 acceptable)";

    $rc = ncreader_move_right($reader);
    ok $rc == 0 || $rc == -1, "ncreader_move_right returned $rc (0 or -1 acceptable)";

    # Clear the reader
    $rc = ncreader_clear($reader);
    is $rc, 0, 'ncreader_clear succeeded';

    # Contents after clear should be empty or whitespace
    my $after-clear = ncreader_contents($reader);
    ok !$after-clear.defined || $after-clear.trim eq '', "Contents after clear are empty (got: {($after-clear // '').raku})";

    ncreader_destroy($reader, Pointer);
};

# === 7. Multiselector lifecycle ===

subtest 'Multiselector lifecycle' => {
    plan 3;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(40)));
    ok $plane.defined, 'Created plane for multiselector';

    # Create with no items (empty options)
    my $msopts = NcmultiselectorOptions.new(:title('Select items'), :footer('Space to toggle'));
    my $msel = ncmultiselector_create($plane, $msopts);
    ok $msel.defined, 'Multiselector created';

    my $msplane = ncmultiselector_plane($msel);
    ok $msplane.defined, 'ncmultiselector_plane returns valid plane';

    ncmultiselector_destroy($msel);
};

# === 8. Selector lifecycle ===

subtest 'Selector lifecycle' => {
    plan 10;

    my $plane = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(40)));
    ok $plane.defined, 'Created plane for selector';

    my $sopts = NcselectorOptions.new(:title('Pick one'), :footer('Use arrows'));
    my $sel = ncselector_create($plane, $sopts);
    ok $sel.defined, 'Selector created';

    my $splane = ncselector_plane($sel);
    ok $splane.defined, 'ncselector_plane returns valid plane';

    # Add items
    my $item-a = NcselectorItem.new(:option('alpha'), :desc('First option'));
    my int32 $rc = ncselector_additem($sel, $item-a);
    is $rc, 0, 'Added item alpha';

    my $item-b = NcselectorItem.new(:option('beta'), :desc('Second option'));
    $rc = ncselector_additem($sel, $item-b);
    is $rc, 0, 'Added item beta';

    my $item-c = NcselectorItem.new(:option('gamma'), :desc('Third option'));
    $rc = ncselector_additem($sel, $item-c);
    is $rc, 0, 'Added item gamma';

    # Get selected — should be the first item
    my $selected = ncselector_selected($sel);
    ok $selected.defined, "Selected item: $selected";

    # Navigate forward
    my $next = ncselector_nextitem($sel);
    ok $next.defined, "After nextitem, selected: $next";

    # Navigate backward
    my $prev = ncselector_previtem($sel);
    ok $prev.defined, "After previtem, selected: $prev";

    # Delete an item
    $rc = ncselector_delitem($sel, 'gamma');
    is $rc, 0, 'Deleted item gamma';

    ncselector_destroy($sel, Pointer);
};

done-testing;