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


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;