Notcurses-Native.git | xt/ | 10-channel-exhaustive.rakutest edit


use Test;
use lib 'lib';
use NativeCall;
use Notcurses::Native::Types;
use Notcurses::Native::Channel;

plan 20;

# === Single channel (uint32) tests ===

subtest 'ncchannel_set and ncchannel_rgb roundtrip' => {
    plan 9;

    my uint32 $ch = 0;

    # Set full RGB and read back
    is ncchannel_set($ch, 0xFF8040), 0, 'ncchannel_set returns 0 on success';
    is ncchannel_rgb($ch), 0xFF8040, 'ncchannel_rgb returns the value we set';

    # Boundary: black
    ncchannel_set($ch, 0x000000);
    is ncchannel_rgb($ch), 0x000000, 'ncchannel_rgb roundtrips black (0x000000)';

    # Boundary: white
    ncchannel_set($ch, 0xFFFFFF);
    is ncchannel_rgb($ch), 0xFFFFFF, 'ncchannel_rgb roundtrips white (0xFFFFFF)';

    # Specific colors
    ncchannel_set($ch, 0x123456);
    is ncchannel_rgb($ch), 0x123456, 'ncchannel_rgb roundtrips 0x123456';

    # Individual component extraction
    ncchannel_set($ch, 0xAABBCC);
    is ncchannel_r($ch), 0xAA, 'ncchannel_r extracts red component';
    is ncchannel_g($ch), 0xBB, 'ncchannel_g extracts green component';
    is ncchannel_b($ch), 0xCC, 'ncchannel_b extracts blue component';

    # Reject out-of-range RGB (> 0xFFFFFF)
    my uint32 $ch2 = 0;
    my int32 $ret = ncchannel_set($ch2, 0x1000000);
    is $ret, -1, 'ncchannel_set rejects RGB > 0xFFFFFF';
}

subtest 'ncchannel_rgb8 output params' => {
    plan 4;

    my uint32 $ch = 0;
    ncchannel_set($ch, 0x102030);

    my uint32 $r = 0;
    my uint32 $g = 0;
    my uint32 $b = 0;
    my uint32 $combined = ncchannel_rgb8($ch, $r, $g, $b);

    # ncchannel_rgb8 returns the full channel value (including flags), not just RGB
    is $combined +& 0xFFFFFF, 0x102030, 'ncchannel_rgb8 return value contains RGB in low 24 bits';
    is $r, 0x10, 'ncchannel_rgb8 extracts r via output param';
    is $g, 0x20, 'ncchannel_rgb8 extracts g via output param';
    is $b, 0x30, 'ncchannel_rgb8 extracts b via output param';
}

subtest 'ncchannel_set_rgb8 and component independence' => {
    plan 10;

    my uint32 $ch = 0;
    is ncchannel_set_rgb8($ch, 0x11, 0x22, 0x33), 0, 'ncchannel_set_rgb8 returns 0';
    is ncchannel_r($ch), 0x11, 'red component set correctly';
    is ncchannel_g($ch), 0x22, 'green component set correctly';
    is ncchannel_b($ch), 0x33, 'blue component set correctly';

    # Boundary: all zeros
    ncchannel_set_rgb8($ch, 0, 0, 0);
    is ncchannel_rgb($ch), 0x000000, 'set_rgb8(0,0,0) gives black';

    # Boundary: all max
    ncchannel_set_rgb8($ch, 255, 255, 255);
    is ncchannel_rgb($ch), 0xFFFFFF, 'set_rgb8(255,255,255) gives white';

    # Component independence: setting one doesn't clobber others
    ncchannel_set_rgb8($ch, 0xAA, 0xBB, 0xCC);
    is ncchannel_r($ch), 0xAA, 'red preserved after full set';
    is ncchannel_g($ch), 0xBB, 'green preserved after full set';
    is ncchannel_b($ch), 0xCC, 'blue preserved after full set';

    # Reject out-of-range component
    my uint32 $ch2 = 0;
    my int32 $ret = ncchannel_set_rgb8($ch2, 256, 0, 0);
    is $ret, -1, 'ncchannel_set_rgb8 rejects r > 255';
}

subtest 'ncchannel_set_rgb8_clipped' => {
    plan 6;

    my uint32 $ch = 0;

    # Normal values work
    ncchannel_set_rgb8_clipped($ch, 100, 150, 200);
    is ncchannel_r($ch), 100, 'clipped: normal red value passes through';
    is ncchannel_g($ch), 150, 'clipped: normal green value passes through';
    is ncchannel_b($ch), 200, 'clipped: normal blue value passes through';

    # Values above 255 are clamped to 255
    ncchannel_set_rgb8_clipped($ch, 300, 400, 500);
    is ncchannel_r($ch), 255, 'clipped: r > 255 clamped to 255';
    is ncchannel_g($ch), 255, 'clipped: g > 255 clamped to 255';
    is ncchannel_b($ch), 255, 'clipped: b > 255 clamped to 255';
}

subtest 'ncchannel_alpha set/get' => {
    plan 6;

    my uint32 $ch = 0;
    ncchannel_set($ch, 0x112233);

    is ncchannel_set_alpha($ch, NCALPHA_OPAQUE), 0, 'set_alpha OPAQUE returns 0';
    is ncchannel_alpha($ch), NCALPHA_OPAQUE, 'alpha reads back OPAQUE';

    ncchannel_set_alpha($ch, NCALPHA_BLEND);
    is ncchannel_alpha($ch), NCALPHA_BLEND, 'alpha reads back BLEND';

    ncchannel_set_alpha($ch, NCALPHA_TRANSPARENT);
    is ncchannel_alpha($ch), NCALPHA_TRANSPARENT, 'alpha reads back TRANSPARENT';

    ncchannel_set_alpha($ch, NCALPHA_HIGHCONTRAST);
    is ncchannel_alpha($ch), NCALPHA_HIGHCONTRAST, 'alpha reads back HIGHCONTRAST';

    # Setting alpha does not disturb RGB
    my uint32 $ch2 = 0;
    ncchannel_set($ch2, 0xAABBCC);
    ncchannel_set_alpha($ch2, NCALPHA_BLEND);
    is ncchannel_rgb($ch2), 0xAABBCC, 'setting alpha preserves RGB';
}

subtest 'ncchannel_default_p and ncchannel_set_default' => {
    plan 5;

    my uint32 $ch = 0;
    ok ncchannel_default_p($ch), 'freshly zeroed channel is default';

    # Setting RGB clears default flag
    ncchannel_set($ch, 0x112233);
    nok ncchannel_default_p($ch), 'channel is not default after set RGB';

    # set_default restores it
    ncchannel_set_default($ch);
    ok ncchannel_default_p($ch), 'channel is default after set_default';

    # default flag is independent of RGB bits -- RGB may remain from prior set
    ok ncchannel_default_p($ch), 'default_p is true even if RGB bits linger';

    # set_rgb8 also clears default
    ncchannel_set_default($ch);
    ncchannel_set_rgb8($ch, 1, 2, 3);
    nok ncchannel_default_p($ch), 'set_rgb8 clears default flag';
}

subtest 'ncchannel_palindex set/get and predicates' => {
    plan 8;

    my uint32 $ch = 0;

    # Initially not palette-indexed
    nok ncchannel_palindex_p($ch), 'fresh channel is not palindex';

    # Set palette index
    is ncchannel_set_palindex($ch, 42), 0, 'set_palindex returns 0';
    ok ncchannel_palindex_p($ch), 'channel is palindex after set';
    is ncchannel_palindex($ch), 42, 'palindex reads back 42';

    # Palette and RGB are mutually exclusive
    nok ncchannel_rgb_p($ch), 'palindex channel is not rgb_p';

    # Setting RGB clears palette
    ncchannel_set($ch, 0x112233);
    nok ncchannel_palindex_p($ch), 'set RGB clears palindex';
    ok ncchannel_rgb_p($ch), 'channel is rgb_p after set RGB';

    # Boundary: palette index 0
    my uint32 $ch2 = 0;
    ncchannel_set_palindex($ch2, 0);
    is ncchannel_palindex($ch2), 0, 'palette index 0 roundtrips';
}

subtest 'ncchannel_rgb_p predicate' => {
    plan 4;

    my uint32 $ch = 0;
    nok ncchannel_rgb_p($ch), 'fresh channel is not rgb_p (is default)';

    ncchannel_set($ch, 0x000000);
    ok ncchannel_rgb_p($ch), 'channel with black RGB is rgb_p';

    ncchannel_set_default($ch);
    nok ncchannel_rgb_p($ch), 'default channel is not rgb_p';

    ncchannel_set_palindex($ch, 10);
    nok ncchannel_rgb_p($ch), 'palindex channel is not rgb_p';
}

# === Dual channels (uint64) tests ===

subtest 'ncchannels_combine and ncchannels_fchannel/bchannel' => {
    plan 5;

    my uint32 $fg = 0;
    my uint32 $bg = 0;
    ncchannel_set($fg, 0xFF0000);
    ncchannel_set($bg, 0x0000FF);

    my uint64 $channels = ncchannels_combine($fg, $bg);
    is ncchannels_fchannel($channels), $fg, 'fchannel extracts the fg we combined';
    is ncchannels_bchannel($channels), $bg, 'bchannel extracts the bg we combined';

    is ncchannels_fg_rgb($channels), 0xFF0000, 'fg_rgb reads red from combined';
    is ncchannels_bg_rgb($channels), 0x0000FF, 'bg_rgb reads blue from combined';

    # ncchannels_channels should return the full 64-bit value
    is ncchannels_channels($channels), $channels, 'ncchannels_channels returns full value';
}

subtest 'ncchannels_reverse swaps fg and bg RGB' => {
    plan 6;

    my uint64 $channels = 0;
    ncchannels_set_fg_rgb($channels, 0xAA0000);
    ncchannels_set_fg_alpha($channels, NCALPHA_BLEND);
    ncchannels_set_bg_rgb($channels, 0x0000BB);
    ncchannels_set_bg_alpha($channels, NCALPHA_TRANSPARENT);

    my uint64 $reversed = ncchannels_reverse($channels);

    # reverse swaps the RGB portions of fg and bg
    is ncchannels_fg_rgb($reversed), 0x0000BB, 'after reverse, fg has old bg RGB';
    is ncchannels_bg_rgb($reversed), 0xAA0000, 'after reverse, bg has old fg RGB';

    # alpha values stay with their respective fg/bg positions (not swapped)
    is ncchannels_fg_alpha($reversed), NCALPHA_BLEND, 'fg alpha stays BLEND after reverse';
    is ncchannels_bg_alpha($reversed), NCALPHA_TRANSPARENT, 'bg alpha stays TRANSPARENT after reverse';

    # Verify with simple case: two distinct colors, no alpha
    my uint64 $simple = 0;
    ncchannels_set_fg_rgb($simple, 0x112233);
    ncchannels_set_bg_rgb($simple, 0xAABBCC);
    my uint64 $rev2 = ncchannels_reverse($simple);
    is ncchannels_fg_rgb($rev2), 0xAABBCC, 'simple reverse: fg gets old bg';
    is ncchannels_bg_rgb($rev2), 0x112233, 'simple reverse: bg gets old fg';
}

subtest 'ncchannels_set_channels, set_fchannel, set_bchannel' => {
    plan 6;

    my uint64 $channels = 0;

    # Set individual channels
    my uint32 $fg = 0;
    ncchannel_set($fg, 0x112233);
    ncchannels_set_fchannel($channels, $fg);
    is ncchannels_fg_rgb($channels), 0x112233, 'set_fchannel sets fg RGB';
    is ncchannels_bg_rgb($channels), 0x000000, 'set_fchannel leaves bg untouched';

    my uint32 $bg = 0;
    ncchannel_set($bg, 0x445566);
    ncchannels_set_bchannel($channels, $bg);
    is ncchannels_bg_rgb($channels), 0x445566, 'set_bchannel sets bg RGB';
    is ncchannels_fg_rgb($channels), 0x112233, 'set_bchannel leaves fg untouched';

    # set_channels overwrites everything
    my uint64 $new-channels = 0;
    my uint32 $nfg = 0;
    my uint32 $nbg = 0;
    ncchannel_set($nfg, 0xABCDEF);
    ncchannel_set($nbg, 0xFEDCBA);
    $new-channels = ncchannels_combine($nfg, $nbg);

    my uint64 $dst = 0;
    ncchannels_set_channels($dst, $new-channels);
    is ncchannels_fg_rgb($dst), 0xABCDEF, 'set_channels overwrites fg';
    is ncchannels_bg_rgb($dst), 0xFEDCBA, 'set_channels overwrites bg';
}

subtest 'ncchannels fg/bg RGB set/get via rgb and rgb8' => {
    plan 12;

    my uint64 $channels = 0;

    # Set fg RGB
    is ncchannels_set_fg_rgb($channels, 0xAABBCC), 0, 'set_fg_rgb returns 0';
    is ncchannels_fg_rgb($channels), 0xAABBCC, 'fg_rgb roundtrips';

    # Set bg RGB
    is ncchannels_set_bg_rgb($channels, 0x112233), 0, 'set_bg_rgb returns 0';
    is ncchannels_bg_rgb($channels), 0x112233, 'bg_rgb roundtrips';

    # fg unchanged after setting bg
    is ncchannels_fg_rgb($channels), 0xAABBCC, 'fg preserved after setting bg';

    # Set via rgb8
    is ncchannels_set_fg_rgb8($channels, 0x10, 0x20, 0x30), 0, 'set_fg_rgb8 returns 0';
    is ncchannels_fg_rgb($channels), 0x102030, 'fg_rgb8 roundtrips';

    is ncchannels_set_bg_rgb8($channels, 0x40, 0x50, 0x60), 0, 'set_bg_rgb8 returns 0';
    is ncchannels_bg_rgb($channels), 0x405060, 'bg_rgb8 roundtrips';

    # Extract via rgb8 output params
    my uint32 $r = 0; my uint32 $g = 0; my uint32 $b = 0;
    ncchannels_fg_rgb8($channels, $r, $g, $b);
    is $r, 0x10, 'fg_rgb8 output r';
    is $g, 0x20, 'fg_rgb8 output g';
    is $b, 0x30, 'fg_rgb8 output b';
}

subtest 'ncchannels fg/bg rgb8_clipped' => {
    plan 6;

    my uint64 $channels = 0;

    ncchannels_set_fg_rgb8_clipped($channels, 300, -10, 128);
    is ncchannel_r(ncchannels_fchannel($channels)), 255, 'fg clipped: r > 255 clamped';
    is ncchannel_g(ncchannels_fchannel($channels)), 0, 'fg clipped: r < 0 clamped to 0';
    is ncchannel_b(ncchannels_fchannel($channels)), 128, 'fg clipped: normal value preserved';

    ncchannels_set_bg_rgb8_clipped($channels, 50, 500, -1);
    is ncchannel_r(ncchannels_bchannel($channels)), 50, 'bg clipped: normal value preserved';
    is ncchannel_g(ncchannels_bchannel($channels)), 255, 'bg clipped: g > 255 clamped';
    is ncchannel_b(ncchannels_bchannel($channels)), 0, 'bg clipped: b < 0 clamped to 0';
}

subtest 'ncchannels fg/bg alpha' => {
    plan 8;

    my uint64 $channels = 0;
    ncchannels_set_fg_rgb($channels, 0x112233);
    ncchannels_set_bg_rgb($channels, 0x445566);

    is ncchannels_set_fg_alpha($channels, NCALPHA_BLEND), 0, 'set_fg_alpha BLEND returns 0';
    is ncchannels_fg_alpha($channels), NCALPHA_BLEND, 'fg alpha reads BLEND';

    is ncchannels_set_bg_alpha($channels, NCALPHA_TRANSPARENT), 0, 'set_bg_alpha TRANSPARENT returns 0';
    is ncchannels_bg_alpha($channels), NCALPHA_TRANSPARENT, 'bg alpha reads TRANSPARENT';

    # fg and bg alpha are independent
    is ncchannels_fg_alpha($channels), NCALPHA_BLEND, 'fg alpha unchanged after setting bg alpha';

    # Setting alpha preserves RGB
    is ncchannels_fg_rgb($channels), 0x112233, 'fg RGB preserved after setting fg alpha';
    is ncchannels_bg_rgb($channels), 0x445566, 'bg RGB preserved after setting bg alpha';

    # HIGHCONTRAST
    ncchannels_set_fg_alpha($channels, NCALPHA_HIGHCONTRAST);
    is ncchannels_fg_alpha($channels), NCALPHA_HIGHCONTRAST, 'fg alpha reads HIGHCONTRAST';
}

subtest 'ncchannels fg/bg palindex' => {
    plan 10;

    my uint64 $channels = 0;

    is ncchannels_set_fg_palindex($channels, 99), 0, 'set_fg_palindex returns 0';
    is ncchannels_fg_palindex($channels), 99, 'fg palindex reads 99';
    ok ncchannels_fg_palindex_p($channels), 'fg is palindex after set';
    nok ncchannels_fg_rgb_p($channels), 'fg is not rgb_p when palindex';

    is ncchannels_set_bg_palindex($channels, 200), 0, 'set_bg_palindex returns 0';
    is ncchannels_bg_palindex($channels), 200, 'bg palindex reads 200';
    ok ncchannels_bg_palindex_p($channels), 'bg is palindex after set';
    nok ncchannels_bg_rgb_p($channels), 'bg is not rgb_p when palindex';

    # fg palindex unchanged after setting bg
    is ncchannels_fg_palindex($channels), 99, 'fg palindex preserved after setting bg palindex';

    # Setting RGB clears palindex
    ncchannels_set_fg_rgb($channels, 0xABCDEF);
    nok ncchannels_fg_palindex_p($channels), 'set_fg_rgb clears fg palindex';
}

subtest 'ncchannels fg/bg default' => {
    plan 8;

    my uint64 $channels = 0;

    ok ncchannels_fg_default_p($channels), 'fresh channels: fg is default';
    ok ncchannels_bg_default_p($channels), 'fresh channels: bg is default';

    ncchannels_set_fg_rgb($channels, 0x112233);
    nok ncchannels_fg_default_p($channels), 'fg not default after set_fg_rgb';
    ok ncchannels_bg_default_p($channels), 'bg still default after set_fg_rgb';

    ncchannels_set_fg_default($channels);
    ok ncchannels_fg_default_p($channels), 'fg default after set_fg_default';

    ncchannels_set_bg_rgb($channels, 0x445566);
    nok ncchannels_bg_default_p($channels), 'bg not default after set_bg_rgb';

    ncchannels_set_bg_default($channels);
    ok ncchannels_bg_default_p($channels), 'bg default after set_bg_default';

    # Both can be non-default simultaneously
    ncchannels_set_fg_rgb($channels, 0xAAAAAA);
    ncchannels_set_bg_rgb($channels, 0xBBBBBB);
    nok ncchannels_fg_default_p($channels) || ncchannels_bg_default_p($channels),
        'both fg and bg non-default simultaneously';
}

subtest 'ncchannels bg_rgb8 output params' => {
    plan 3;

    my uint64 $channels = 0;
    ncchannels_set_bg_rgb8($channels, 0xDE, 0xAD, 0xBE);

    my uint32 $r = 0; my uint32 $g = 0; my uint32 $b = 0;
    ncchannels_bg_rgb8($channels, $r, $g, $b);
    is $r, 0xDE, 'bg_rgb8 output r';
    is $g, 0xAD, 'bg_rgb8 output g';
    is $b, 0xBE, 'bg_rgb8 output b';
}

# === Pixel (uint32 ABGR) tests ===

subtest 'ncpixel create and component get' => {
    plan 8;

    my uint32 $px = ncpixel(0xAA, 0xBB, 0xCC);
    is ncpixel_r($px), 0xAA, 'ncpixel_r extracts red';
    is ncpixel_g($px), 0xBB, 'ncpixel_g extracts green';
    is ncpixel_b($px), 0xCC, 'ncpixel_b extracts blue';
    is ncpixel_a($px), 0xFF, 'ncpixel_a defaults to 0xFF (fully opaque)';

    # Boundary: black pixel
    my uint32 $black = ncpixel(0, 0, 0);
    is ncpixel_r($black), 0, 'black pixel r is 0';
    is ncpixel_g($black), 0, 'black pixel g is 0';
    is ncpixel_b($black), 0, 'black pixel b is 0';
    is ncpixel_a($black), 0xFF, 'black pixel a is 0xFF';
}

subtest 'ncpixel_set_* component setters' => {
    plan 16;

    my uint32 $px = ncpixel(0, 0, 0);

    # Set red
    is ncpixel_set_r($px, 0x42), 0, 'ncpixel_set_r returns 0';
    is ncpixel_r($px), 0x42, 'red set correctly';
    is ncpixel_g($px), 0, 'green unchanged after set_r';
    is ncpixel_b($px), 0, 'blue unchanged after set_r';

    # Set green
    is ncpixel_set_g($px, 0x84), 0, 'ncpixel_set_g returns 0';
    is ncpixel_g($px), 0x84, 'green set correctly';
    is ncpixel_r($px), 0x42, 'red unchanged after set_g';

    # Set blue
    is ncpixel_set_b($px, 0xF0), 0, 'ncpixel_set_b returns 0';
    is ncpixel_b($px), 0xF0, 'blue set correctly';
    is ncpixel_r($px), 0x42, 'red unchanged after set_b';
    is ncpixel_g($px), 0x84, 'green unchanged after set_b';

    # Set alpha
    is ncpixel_set_a($px, 0x80), 0, 'ncpixel_set_a returns 0';
    is ncpixel_a($px), 0x80, 'alpha set correctly';
    is ncpixel_r($px), 0x42, 'red unchanged after set_a';

    # Boundary: max values
    ncpixel_set_r($px, 255);
    ncpixel_set_g($px, 255);
    ncpixel_set_b($px, 255);
    ncpixel_set_a($px, 255);
    is ncpixel_r($px), 255, 'max red roundtrips';
    is ncpixel_a($px), 255, 'max alpha roundtrips';
}

subtest 'ncpixel_set_rgb8' => {
    plan 5;

    my uint32 $px = ncpixel(0, 0, 0);
    ncpixel_set_a($px, 0x80);  # Set alpha first to verify it is preserved

    is ncpixel_set_rgb8($px, 0x11, 0x22, 0x33), 0, 'ncpixel_set_rgb8 returns 0';
    is ncpixel_r($px), 0x11, 'r set by set_rgb8';
    is ncpixel_g($px), 0x22, 'g set by set_rgb8';
    is ncpixel_b($px), 0x33, 'b set by set_rgb8';
    is ncpixel_a($px), 0x80, 'alpha preserved by set_rgb8';
}

done-testing;