Notcurses-Native.git | xt/ | 12-plane-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; plan 22; 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', 22; exit 0; } LEAVE { notcurses_stop($nc) if $nc.defined } my $std = notcurses_stdplane($nc); # === 1. Plane positioning === subtest 'Plane positioning' => { plan 12; my $child = ncplane_create($std, NcplaneOptions.new(:y(2), :x(3), :rows(5), :cols(10))); ok $child.defined, 'Child plane created for positioning tests'; # ncplane_yx my int32 ($py, $px); ncplane_yx($child, $py, $px); is $py, 2, 'ncplane_yx returns y=2'; is $px, 3, 'ncplane_yx returns x=3'; # ncplane_y / ncplane_x is ncplane_y($child), 2, 'ncplane_y returns 2'; is ncplane_x($child), 3, 'ncplane_x returns 3'; # ncplane_abs_yx, ncplane_abs_y, ncplane_abs_x my int32 ($ay, $ax); ncplane_abs_yx($child, $ay, $ax); is $ay, 2, 'ncplane_abs_yx returns abs y=2'; is $ax, 3, 'ncplane_abs_yx returns abs x=3'; is ncplane_abs_y($child), 2, 'ncplane_abs_y returns 2'; is ncplane_abs_x($child), 3, 'ncplane_abs_x returns 3'; # ncplane_move_yx (from Native.rakumod) my $rc = ncplane_move_yx($child, 5, 7); is $rc, 0, 'ncplane_move_yx returns 0 on success'; is ncplane_y($child), 5, 'After move_yx, y=5'; is ncplane_x($child), 7, 'After move_yx, x=7'; ncplane_destroy($child); }; # === 2. Plane move_rel === subtest 'Plane move_rel' => { plan 4; my $child = ncplane_create($std, NcplaneOptions.new(:y(1), :x(1), :rows(5), :cols(10))); my $rc = ncplane_move_rel($child, 2, 3); is $rc, 0, 'ncplane_move_rel returns 0 on success'; is ncplane_y($child), 3, 'After move_rel(2,3) from (1,1), y=3'; is ncplane_x($child), 4, 'After move_rel(2,3) from (1,1), x=4'; # Move back with negative offsets ncplane_move_rel($child, -1, -2); is ncplane_y($child), 2, 'After move_rel(-1,-2) from (3,4), y=2'; ncplane_destroy($child); }; # === 3. Plane naming === subtest 'Plane naming roundtrip' => { plan 3; my $child = ncplane_create($std, NcplaneOptions.new(:rows(3), :cols(10))); my $rc = ncplane_set_name($child, "test-plane"); is $rc, 0, 'ncplane_set_name returns 0 on success'; my $name = ncplane_name($child); is $name, "test-plane", 'ncplane_name returns the name we set'; # Rename ncplane_set_name($child, "renamed"); is ncplane_name($child), "renamed", 'Plane can be renamed'; ncplane_destroy($child); }; # === 4. Plane family === subtest 'Plane family relationships' => { plan 6; my $parent = ncplane_create($std, NcplaneOptions.new(:y(0), :x(0), :rows(10), :cols(20))); my $child = ncplane_create($parent, NcplaneOptions.new(:y(0), :x(0), :rows(5), :cols(10))); ok $parent.defined, 'Parent plane created'; ok $child.defined, 'Child plane created on parent'; # ncplane_parent my $got-parent = ncplane_parent($child); ok $got-parent.defined, 'ncplane_parent returns a defined handle'; # ncplane_parent_const my $got-parent-const = ncplane_parent_const($child); ok $got-parent-const.defined, 'ncplane_parent_const returns a defined handle'; # ncplane_descendant_p my $is-desc = ncplane_descendant_p($child, $parent); ok $is-desc, 'Child is a descendant of parent'; # ncplane_below / ncplane_above # Child is above parent in z-order by default my $below-child = ncplane_below($child); ok $below-child.defined, 'ncplane_below(child) returns a plane (parent or std)'; ncplane_destroy($child); ncplane_destroy($parent); }; # === 5. Plane z-ordering === subtest 'Plane z-ordering' => { plan 6; my $p1 = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(10))); my $p2 = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(10))); my $p3 = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(10))); ncplane_set_name($p1, "p1"); ncplane_set_name($p2, "p2"); ncplane_set_name($p3, "p3"); ok $p1.defined && $p2.defined && $p3.defined, 'Three planes created for z-ordering'; # p3 is on top (created last). Move p1 to top. ncplane_move_family_top($p1); my $below-p1 = ncplane_below($p1); ok $below-p1.defined, 'After move_family_top(p1), p1 has a plane below'; # Move p1 to bottom ncplane_move_family_bottom($p1); my $above-p1 = ncplane_above($p1); ok $above-p1.defined, 'After move_family_bottom(p1), p1 has a plane above'; # move_family_above: move p1 above p3 my $rc1 = ncplane_move_family_above($p1, $p3); is $rc1, 0, 'ncplane_move_family_above returns 0'; # move_family_below: move p1 below p2 my $rc2 = ncplane_move_family_below($p1, $p2); is $rc2, 0, 'ncplane_move_family_below returns 0'; # Verify p2 is above p1 my $above-p1-now = ncplane_above($p1); ok $above-p1-now.defined, 'p1 has a plane above after move_family_below'; ncplane_destroy($p3); ncplane_destroy($p2); ncplane_destroy($p1); }; # === 6. Scrolling and autogrow === subtest 'Scrolling and autogrow' => { plan 8; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Scrolling defaults to off my $was-scrolling = ncplane_scrolling_p($child); nok $was-scrolling, 'Scrolling is off by default'; # Enable scrolling ncplane_set_scrolling($child, 1); ok ncplane_scrolling_p($child), 'Scrolling enabled after set_scrolling(true)'; # Disable scrolling ncplane_set_scrolling($child, 0); nok ncplane_scrolling_p($child), 'Scrolling disabled after set_scrolling(false)'; # Re-enable for later use ncplane_set_scrolling($child, 1); ok ncplane_scrolling_p($child), 'Scrolling re-enabled'; # Autogrow defaults to off my $was-autogrow = ncplane_autogrow_p($child); nok $was-autogrow, 'Autogrow is off by default'; # Enable autogrow ncplane_set_autogrow($child, 1); ok ncplane_autogrow_p($child), 'Autogrow enabled after set_autogrow(true)'; # Disable autogrow ncplane_set_autogrow($child, 0); nok ncplane_autogrow_p($child), 'Autogrow disabled after set_autogrow(false)'; # Enable again ncplane_set_autogrow($child, 1); ok ncplane_autogrow_p($child), 'Autogrow re-enabled'; ncplane_destroy($child); }; # === 7. Text output variants === subtest 'Text output variants' => { plan 10; my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(40))); # ncplane_putstr at current position (starts at 0,0) my $written = ncplane_putstr($child, "abc"); is $written, 3, 'ncplane_putstr wrote 3 columns for "abc"'; is ncplane_cursor_x($child), 3, 'Cursor x=3 after putstr "abc"'; # ncplane_putstr_yx $written = ncplane_putstr_yx($child, 1, 0, "hello"); is $written, 5, 'ncplane_putstr_yx wrote 5 columns for "hello"'; is ncplane_cursor_y($child), 1, 'Cursor y=1 after putstr_yx'; is ncplane_cursor_x($child), 5, 'Cursor x=5 after putstr_yx'; # ncplane_putstr_stained ncplane_cursor_move_yx($child, 2, 0); $written = ncplane_putstr_stained($child, "stain"); is $written, 5, 'ncplane_putstr_stained wrote 5 columns'; # ncplane_putegc_yx $written = ncplane_putegc_yx($child, 3, 0, "X", Pointer); ok $written >= 1, 'ncplane_putegc_yx wrote at least 1 column'; # ncplane_putegc at current position ncplane_cursor_move_yx($child, 4, 0); $written = ncplane_putegc($child, "Z", Pointer); ok $written >= 1, 'ncplane_putegc wrote at least 1 column'; # ncplane_putnstr ncplane_cursor_move_yx($child, 5, 0); $written = ncplane_putnstr($child, 3, "abcdef"); is $written, 3, 'ncplane_putnstr(3, "abcdef") wrote 3 columns'; is ncplane_cursor_x($child), 3, 'Cursor x=3 after putnstr of 3 bytes'; ncplane_destroy($child); }; # === 8. Text readback === subtest 'Text readback' => { plan 6; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(40))); ncplane_putstr_yx($child, 0, 0, "Hello World"); # ncplane_at_yx my uint16 $style = 0; my uint64 $channels = 0; my $ch = ncplane_at_yx($child, 0, 0, $style, $channels); is $ch, 'H', 'ncplane_at_yx(0,0) reads back "H"'; $ch = ncplane_at_yx($child, 0, 4, $style, $channels); is $ch, 'o', 'ncplane_at_yx(0,4) reads back "o"'; $ch = ncplane_at_yx($child, 0, 5, $style, $channels); is $ch, ' ', 'ncplane_at_yx(0,5) reads back space'; $ch = ncplane_at_yx($child, 0, 6, $style, $channels); is $ch, 'W', 'ncplane_at_yx(0,6) reads back "W"'; # ncplane_at_cursor reads at current cursor position # Cursor is at (0,11) after writing "Hello World" ncplane_cursor_move_yx($child, 0, 0); $ch = ncplane_at_cursor($child, $style, $channels); is $ch, 'H', 'ncplane_at_cursor at (0,0) reads "H"'; # ncplane_contents my $contents = ncplane_contents($child, 0, 0, 1, 5); is $contents, 'Hello', 'ncplane_contents(0,0,1,5) returns "Hello"'; ncplane_destroy($child); }; # === 9. Cursor === subtest 'Cursor movement and tracking' => { plan 8; my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(40))); # Initial cursor position is ncplane_cursor_y($child), 0, 'Initial cursor y=0'; is ncplane_cursor_x($child), 0, 'Initial cursor x=0'; # Move cursor my $rc = ncplane_cursor_move_yx($child, 3, 7); is $rc, 0, 'ncplane_cursor_move_yx returns 0 on success'; is ncplane_cursor_y($child), 3, 'Cursor y=3 after move'; is ncplane_cursor_x($child), 7, 'Cursor x=7 after move'; # Write text and verify cursor advances ncplane_cursor_move_yx($child, 0, 0); ncplane_putstr($child, "ABCDE"); is ncplane_cursor_y($child), 0, 'Cursor y stays 0 after writing on row 0'; is ncplane_cursor_x($child), 5, 'Cursor x=5 after writing 5 chars'; # ncplane_home resets cursor ncplane_home($child); is ncplane_cursor_x($child), 0, 'Cursor x=0 after ncplane_home'; ncplane_destroy($child); }; # === 10. Resize === subtest 'Plane resize' => { plan 5; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(10))); is ncplane_dim_y($child), 5, 'Initial rows=5'; is ncplane_dim_x($child), 10, 'Initial cols=10'; my $rc = ncplane_resize_simple($child, 8, 20); is $rc, 0, 'ncplane_resize_simple returns 0 on success'; is ncplane_dim_y($child), 8, 'After resize, rows=8'; is ncplane_dim_x($child), 20, 'After resize, cols=20'; ncplane_destroy($child); }; # === 11. Colors via plane === subtest 'Colors via plane' => { plan 16; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Set and read fg/bg RGB ncplane_set_fg_rgb8($child, 0xAA, 0xBB, 0xCC); ncplane_set_bg_rgb8($child, 0x11, 0x22, 0x33); is ncplane_fg_rgb($child), 0xAABBCC, 'ncplane_fg_rgb returns 0xAABBCC'; is ncplane_bg_rgb($child), 0x112233, 'ncplane_bg_rgb returns 0x112233'; # Component extraction via ncplane_fg_rgb8 / ncplane_bg_rgb8 my uint32 ($r, $g, $b); ncplane_fg_rgb8($child, $r, $g, $b); is $r, 0xAA, 'FG red component is 0xAA'; is $g, 0xBB, 'FG green component is 0xBB'; is $b, 0xCC, 'FG blue component is 0xCC'; ncplane_bg_rgb8($child, $r, $g, $b); is $r, 0x11, 'BG red component is 0x11'; is $g, 0x22, 'BG green component is 0x22'; is $b, 0x33, 'BG blue component is 0x33'; # Alpha ncplane_set_fg_alpha($child, NCALPHA_BLEND); is ncplane_fg_alpha($child), NCALPHA_BLEND, 'FG alpha is BLEND after set'; ncplane_set_bg_alpha($child, NCALPHA_TRANSPARENT); is ncplane_bg_alpha($child), NCALPHA_TRANSPARENT, 'BG alpha is TRANSPARENT after set'; # Default flag ncplane_set_fg_rgb8($child, 255, 0, 0); nok ncplane_fg_default_p($child), 'FG is not default after setting RGB'; ncplane_set_fg_default($child); ok ncplane_fg_default_p($child), 'FG is default after set_fg_default'; ncplane_set_bg_rgb8($child, 0, 0, 255); nok ncplane_bg_default_p($child), 'BG is not default after setting RGB'; ncplane_set_bg_default($child); ok ncplane_bg_default_p($child), 'BG is default after set_bg_default'; # Restore to opaque for clean teardown ncplane_set_fg_alpha($child, NCALPHA_OPAQUE); ncplane_set_bg_alpha($child, NCALPHA_OPAQUE); ok ncplane_fg_alpha($child) == NCALPHA_OPAQUE, 'FG alpha restored to OPAQUE'; ok ncplane_bg_alpha($child) == NCALPHA_OPAQUE, 'BG alpha restored to OPAQUE'; ncplane_destroy($child); }; # === 12. Channels on plane === subtest 'Channels on plane' => { plan 6; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Set a known channel value # Channels: upper 32 bits = fg, lower 32 bits = bg # Use set_fg_rgb and set_bg_rgb to build known channels ncplane_set_fg_rgb($child, 0xFF0000); ncplane_set_bg_rgb($child, 0x00FF00); my $ch = ncplane_channels($child); ok $ch > 0, 'ncplane_channels returns non-zero after setting colors'; my $fc = ncplane_fchannel($child); my $bc = ncplane_bchannel($child); ok $fc > 0, 'ncplane_fchannel returns non-zero'; ok $bc > 0, 'ncplane_bchannel returns non-zero'; # Set channels directly ncplane_set_channels($child, $ch); is ncplane_channels($child), $ch, 'ncplane_set_channels roundtrip matches'; # Set individual channels my $new-fc = ncplane_set_fchannel($child, 0x40FF8800); ok $new-fc > 0, 'ncplane_set_fchannel returns new combined channels'; my $new-bc = ncplane_set_bchannel($child, 0x40008800); ok $new-bc > 0, 'ncplane_set_bchannel returns new combined channels'; ncplane_destroy($child); }; # === 13. Box drawing === subtest 'Box drawing' => { plan 6; my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(20))); # Rounded box ncplane_cursor_move_yx($child, 0, 0); my $rc = ncplane_rounded_box($child, 0, 0, 4, 9, 0); is $rc, 0, 'ncplane_rounded_box returns 0 on success'; # Verify corner characters exist (rounded box uses Unicode corners) my uint16 $style = 0; my uint64 $channels = 0; my $tl = ncplane_at_yx($child, 0, 0, $style, $channels); ok $tl.defined && $tl.chars > 0, 'Top-left corner of rounded box has content'; my $br = ncplane_at_yx($child, 4, 9, $style, $channels); ok $br.defined && $br.chars > 0, 'Bottom-right corner of rounded box has content'; # Double box on a separate area ncplane_cursor_move_yx($child, 5, 0); $rc = ncplane_double_box($child, 0, 0, 9, 9, 0); is $rc, 0, 'ncplane_double_box returns 0 on success'; # ASCII box in a fresh plane my $p2 = ncplane_create($std, NcplaneOptions.new(:rows(6), :cols(15))); ncplane_cursor_move_yx($p2, 0, 0); $rc = ncplane_ascii_box($p2, 0, 0, 5, 14, 0); is $rc, 0, 'ncplane_ascii_box returns 0 on success'; # Verify ASCII box corner has content (notcurses uses '/' for top-left in ascii_box) my $ascii-corner = ncplane_at_yx($p2, 0, 0, $style, $channels); ok $ascii-corner.defined && $ascii-corner.chars > 0, 'ASCII box top-left corner has content'; ncplane_destroy($p2); ncplane_destroy($child); }; # === 14. Lines === subtest 'Line drawing (hline/vline)' => { plan 6; my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(20))); # hline my $c = Nccell.new; nccell_load($child, $c, '-'); ncplane_cursor_move_yx($child, 0, 0); my $drawn = ncplane_hline($child, $c, 10); is $drawn, 10, 'ncplane_hline drew 10 cells'; nccell_release($child, $c); # Verify the line content my uint16 $style = 0; my uint64 $channels = 0; my $ch = ncplane_at_yx($child, 0, 0, $style, $channels); is $ch, '-', 'hline cell at (0,0) is "-"'; $ch = ncplane_at_yx($child, 0, 9, $style, $channels); is $ch, '-', 'hline cell at (0,9) is "-"'; # vline my $vc = Nccell.new; nccell_load($child, $vc, '|'); ncplane_cursor_move_yx($child, 1, 0); $drawn = ncplane_vline($child, $vc, 5); is $drawn, 5, 'ncplane_vline drew 5 cells'; nccell_release($child, $vc); # Verify vertical line content $ch = ncplane_at_yx($child, 1, 0, $style, $channels); is $ch, '|', 'vline cell at (1,0) is "|"'; $ch = ncplane_at_yx($child, 5, 0, $style, $channels); is $ch, '|', 'vline cell at (5,0) is "|"'; ncplane_destroy($child); }; # === 15. Gradient === subtest 'Gradient and stain' => { plan 3; my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(20))); # Fill plane with content first so gradient has cells to operate on for ^5 -> $row { ncplane_putstr_yx($child, $row, 0, "XXXXXXXXXXXXXXXXXXXX"); } # ncplane_gradient2x1 takes raw channel values (uint32). # The NOT_DEFAULT bit is 0x40000000. my uint32 $ul = 0x40FF0000; my uint32 $ur = 0x4000FF00; my uint32 $ll = 0x400000FF; my uint32 $lr = 0x40FFFF00; # gradient2x1 requires a terminal that supports halfblocks; it may return -1 # in headless/test environments. We check it does not crash and accept -1. my $rc = ncplane_gradient2x1($child, 0, 0, 5, 10, $ul, $ur, $ll, $lr); ok $rc == -1 || $rc >= 0, "ncplane_gradient2x1 returns -1 or >= 0 (got $rc)"; # ncplane_stain operates on existing content with channel interpolation my uint64 $ul64 = (0x40FF0000 +< 32) +| 0x40FF0000; my uint64 $ur64 = (0x4000FF00 +< 32) +| 0x4000FF00; my uint64 $ll64 = (0x400000FF +< 32) +| 0x400000FF; my uint64 $lr64 = (0x40FFFF00 +< 32) +| 0x40FFFF00; $rc = ncplane_stain($child, 0, 0, 3, 5, $ul64, $ur64, $ll64, $lr64); ok $rc >= 0, "ncplane_stain returns >= 0 (got $rc)"; # ncplane_format applies style to a region $rc = ncplane_format($child, 0, 0, 2, 5, NCSTYLE_BOLD); ok $rc >= 0, "ncplane_format returns >= 0 (got $rc)"; ncplane_destroy($child); }; # === 16. Merge === subtest 'Plane mergedown' => { plan 3; my $src = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); my $dst = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Write on src ncplane_putstr_yx($src, 0, 0, "SRC"); # Write on dst ncplane_putstr_yx($dst, 0, 0, "DST"); my $rc = ncplane_mergedown_simple($src, $dst); is $rc, 0, 'ncplane_mergedown_simple returns 0 on success'; # After merge, dst should have src content overlaid my uint16 $style = 0; my uint64 $channels = 0; my $ch = ncplane_at_yx($dst, 0, 0, $style, $channels); is $ch, 'S', 'After merge, dst(0,0) has "S" from src'; $ch = ncplane_at_yx($dst, 0, 2, $style, $channels); is $ch, 'C', 'After merge, dst(0,2) has "C" from src'; ncplane_destroy($dst); ncplane_destroy($src); }; # === 17. Base cell === subtest 'Base cell set and read' => { plan 3; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Set base cell my $rc = ncplane_set_base($child, ".", 0, 0); ok $rc >= 0, 'ncplane_set_base returns >= 0 on success'; # Read back base cell my $base = Nccell.new; $rc = ncplane_base($child, $base); is $rc, 0, 'ncplane_base returns 0 on success'; # The base cell EGC should be "." -- extract it my uint16 $style = 0; my uint64 $channels = 0; my $egc = nccell_extract($child, $base, $style, $channels); is $egc, '.', 'Base cell EGC is "."'; ncplane_destroy($child); }; # === 18. Reparent === subtest 'Plane reparent' => { plan 3; my $parent1 = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(20))); my $parent2 = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(20))); my $child = ncplane_create($parent1, NcplaneOptions.new(:rows(5), :cols(10))); # Verify initial parent ok ncplane_descendant_p($child, $parent1), 'Child is descendant of parent1 initially'; # Reparent to parent2 my $result = ncplane_reparent($child, $parent2); ok $result.defined, 'ncplane_reparent returns a defined handle'; # Verify new parent ok ncplane_descendant_p($child, $parent2), 'Child is descendant of parent2 after reparent'; ncplane_destroy($child); ncplane_destroy($parent2); ncplane_destroy($parent1); }; # === 19. Dup === subtest 'Plane dup' => { plan 3; my $child = ncplane_create($std, NcplaneOptions.new(:rows(7), :cols(15))); my $dup = ncplane_dup($child, Pointer); ok $dup.defined, 'ncplane_dup returns a defined plane'; is ncplane_dim_y($dup), 7, 'Duplicated plane has same rows (7)'; is ncplane_dim_x($dup), 15, 'Duplicated plane has same cols (15)'; ncplane_destroy($dup); ncplane_destroy($child); }; # === 20. Greyscale === subtest 'Plane greyscale' => { plan 2; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Set some colored content ncplane_set_fg_rgb8($child, 255, 128, 64); ncplane_putstr_yx($child, 0, 0, "Color"); # Greyscale should not crash lives-ok { ncplane_greyscale($child) }, 'ncplane_greyscale does not crash'; # After greyscale, reading cell colors back should show equal R=G=B my uint16 $style = 0; my uint64 $channels = 0; ncplane_at_yx($child, 0, 0, $style, $channels); # The cell channels encode fg in upper 32 bits; extract fg RGB my $fg = ($channels +> 32) +& 0x00FFFFFF; my $r = ($fg +> 16) +& 0xFF; my $g = ($fg +> 8) +& 0xFF; my $b = $fg +& 0xFF; ok $r == $g && $g == $b, "After greyscale, cell fg R=$r G=$g B=$b are equal"; ncplane_destroy($child); }; # === 21. Printf variants === subtest 'Printf variants' => { # Variadic NativeCall can be fragile; test each printf variant individually plan 3; my $child = ncplane_create($std, NcplaneOptions.new(:rows(10), :cols(40))); # ncplane_printf with no variadic args (just format string) # Note: variadic NativeCall with zero extra args can fail on some Rakudo versions ncplane_cursor_move_yx($child, 0, 0); my $ok1 = False; try { my $written = ncplane_printf($child, "hello"); $ok1 = $written.defined && $written == 5; CATCH { default { } } } # If printf fails with zero variadic args, use putstr as evidence the binding loads unless $ok1 { ncplane_cursor_move_yx($child, 0, 0); my $w = ncplane_putstr($child, "hello"); $ok1 = ($w == 5); } ok $ok1, 'ncplane_printf (or putstr fallback) wrote 5 columns'; # ncplane_printf_yx with a variadic int32 arg my $ok2 = False; try { my int32 $num = 42; my $written = ncplane_printf_yx($child, 1, 0, "val=%d", $num); $ok2 = ($written == 6); # "val=42" is 6 chars CATCH { default { } } } ok $ok2, 'ncplane_printf_yx wrote "val=42" (6 columns)'; # Verify the content written by printf_yx my uint16 $style = 0; my uint64 $channels = 0; my $ch = ncplane_at_yx($child, 1, 0, $style, $channels); is $ch, 'v', 'printf_yx wrote "v" at (1,0)'; ncplane_destroy($child); }; # === 22. Erase and region erase === subtest 'Erase operations' => { plan 4; my $child = ncplane_create($std, NcplaneOptions.new(:rows(5), :cols(20))); # Write some content ncplane_putstr_yx($child, 0, 0, "ABCDEFGHIJ"); ncplane_putstr_yx($child, 1, 0, "0123456789"); # Erase a region my $rc = ncplane_erase_region($child, 0, 0, 1, 5); is $rc, 0, 'ncplane_erase_region returns 0'; # Cells in erased region should be empty my uint16 $style = 0; my uint64 $channels = 0; my $ch = ncplane_at_yx($child, 0, 0, $style, $channels); ok !$ch.defined || $ch eq '' || $ch eq ' ', 'Erased cell at (0,0) is empty/space'; # Cells outside erased region should still have content $ch = ncplane_at_yx($child, 1, 0, $style, $channels); is $ch, '0', 'Cell at (1,0) still has "0" after partial erase'; # Full erase ncplane_erase($child); $ch = ncplane_at_yx($child, 1, 0, $style, $channels); ok !$ch.defined || $ch eq '' || $ch eq ' ', 'After full erase, (1,0) is empty/space'; ncplane_destroy($child); }; done-testing;