The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use strict;
use warnings;

BEGIN {
   # We need to force TERM=xterm so that we can guarantee the right byte
   # sequences for testing
   $ENV{TERM} = "xterm";
}

use Test::More;
use Test::Identity;

use Tickit::Term qw( BIND_FIRST );

use Time::HiRes qw( sleep );

my $term = Tickit::Term->new( UTF8 => 1 );
$term->set_size( 25, 80 );

is( $term->get_input_handle, undef, '$term->get_input_handle undef' );

# key events
{
   my ( $type, $str );
   my $id = $term->bind_event( key => sub {
      my ( $term, $ev, $args ) = @_;
      identical( $_[0], $term, '$_[0] is term for resize event' );
      is( $ev, "key", '$ev is key' );
      $type = $args->{type};
      $str  = $args->{str};

      return 1;
   } );

   $term->emit_key( type => "text", str => " ", mod => 0 );

   is( $type, "text", '$type after emit_key Space' );
   is( $str,  " ",    '$str after emit_key Space' );

   $term->input_push_bytes( "A" );

   is( $type, "text", '$type after push_bytes A' );
   is( $str,  "A",    '$str after push_bytes A' );

   is( $term->check_timeout, undef, '$term has no timeout after A' );

   # We'll test with a Unicode character outside of Latin-1, to ensure it
   # roundtrips correctly
   #
   # 'ĉ' [U+0109] - LATIN SMALL LETTER C WITH CIRCUMFLEX
   #  UTF-8: 0xc4 0x89

   undef $type; undef $str;
   $term->input_push_bytes( "\xc4\x89" );

   is( $type, "text",    '$type after push_bytes for UTF-8' );
   is( $str,  "\x{109}", '$str after push_bytes for UTF-8' );

   $term->input_push_bytes( "\e[A" );

   is( $type, "key", '$type after push_bytes Up' );
   is( $str,  "Up",  '$str after push_bytes Up' );

   is( $term->check_timeout, undef, '$term has no timeout after Up' );

   undef $type; undef $str;
   $term->input_push_bytes( "\e[" );

   is( $type, undef, '$type undef after partial Down' );
   ok( defined $term->check_timeout, '$term has timeout after partial Down' );

   $term->input_push_bytes( "B" );

   is( $type, "key",  '$type after push_bytes after completed Down' );
   is( $str,  "Down", '$str after push_bytes after completed Down' );

   is( $term->check_timeout, undef, '$term has no timeout after completed Down' );

   undef $type; undef $str;
   $term->input_push_bytes( "\e" );

   is( $type, undef, '$type undef after partial Escape' );

   my $timeout = $term->check_timeout;
   ok( $timeout, '$term has timeout after partial Escape' );

   sleep $timeout + 0.01; # account for timing overlaps

   is( $term->check_timeout, undef, '$term has no timeout after timedout' );

   is( $type, "key",    '$type after push_bytes after timedout' );
   is( $str,  "Escape", '$str after push_bytes after timedout' );

   $term->unbind_event_id( $id );
}

# event handler return values
{
   my $first_ret = 0;
   my @called;
   my @ids = (
      $term->bind_event( key => sub { push @called, "A"; return $first_ret } ),
      $term->bind_event( key => sub { push @called, "B"; return 0 } ),
   );

   $term->emit_key( type => "key", str => "X" );

   is_deeply( \@called, [qw( A B )], 'both event handlers called when first returns 0' );

   $first_ret = 1;
   @called = ();

   $term->emit_key( type => "key", str => "X" );

   is_deeply( \@called, [qw( A )], 'second event handlers not called when first returns 1' );

   $term->unbind_event_id( $_ ) for @ids;
}

# BIND_FIRST
{
   my @called;
   my @ids = map {
      my $str = $_;
      $term->bind_event( key => BIND_FIRST, sub { push @called, $str; return 0 } );
   } qw( A B );

   $term->emit_key( type => "key", str => "X" );

   is_deeply( \@called, [qw( B A )], 'event handlers called in reverse order with BIND_FIRST' );

   $term->unbind_event_id( $_ ) for @ids;
}

# mouse events
{
   my ( $type, $button, $line, $col );
   my $id = $term->bind_event( mouse => sub {
      my ( $term, $ev, $args ) = @_;
      is( $ev, "mouse", '$ev is mouse' );
      $type   = $args->{type};
      $button = $args->{button};
      $line   = $args->{line};
      $col    = $args->{col};

      return 1;
   } );

   $term->emit_mouse( type => "press", button => 1, line => 2, col => 3 );

   is( $type,   "press", '$type after emit_mouse' );
   is( $button, 1,       '$button after emit_mouse' );
   is( $line,   2,       '$line after emit_mouse' );
   is( $col,    3,       '$col after emit_mouse' );

   $term->emit_mouse( type => "wheel", button => "down", line => 2, col => 3 );

   is( $type,   "wheel", '$type after emit_mouse wheel' );
   is( $button, "down",  '$button after emit_mouse wheel' );
   is( $line,   2,       '$line after emit_mouse wheel' );
   is( $col,    3,       '$col after emit_mouse wheel' );

   $term->unbind_event_id( $id );
}

# Legacy event handling
{
   my $warned;
   local $SIG{__WARN__} = sub {
      $warned++, return if $_[0] =~ m/->set_on_key is deprecated/;
      local $SIG{__WARN__}; warn @_;
   };

   my ( $type, $str );
   $term->set_on_key( sub { ( undef, $type, $str ) = @_; } );

   ok( $warned, '->set_on_key gave deprecation warning' );

   $term->input_push_bytes( "A" );
}

{
   pipe( my $rd, my $wr ) or die "pipe() - $!";

   my $term = Tickit::Term->new( input_handle => $rd );

   isa_ok( $term, "Tickit::Term", '$term isa Tickit::Term' );
   is( $term->get_input_handle->fileno, $rd->fileno,
      '$term->get_input_handle->fileno is $rd' );
}

done_testing;