package tests::TestCursesUIDriverTest;

use strict;

use base qw/Test::Unit::TestSuite/;

sub name {
    return "Lire::Test::CursesUIDriver Tests";
}

sub include_tests {
    return qw/tests::TestCursesUIDriverTest::setup
              tests::TestCursesUIDriverTest::events
            /;
}

package tests::TestCursesUIDriverTest::setup;

use base qw/Lire::Test::TestCase/;

use Lire::Test::CursesUIDriver;

use Curses;
use Curses::UI;

sub set_up {
    my $self = $_[0];
    $self->SUPER::set_up();

   $self->{'driver'} = new Lire::Test::CursesUIDriver();

    return;
}

sub tear_down {
    my $self = $_[0];
    $self->SUPER::tear_down();

    endwin()
      unless isendwin();

    return;
}

sub test_new {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    $self->assert( UNIVERSAL::isa( $driver, 'Lire::Test::CursesUIDriver' ),
                   "new() didn't return a Lire::Test::CursesUIDriver instance: $driver" );
    $self->assert_deep_equals( [], $driver->{'_event_loop_handlers'} );

}

sub _default_mouse_support {
    return Curses->can('NCURSES_MOUSE_VERSION') &&
      NCURSES_MOUSE_VERSION() >= 1;
}

sub _check_curses_ui_globals {
    my $self = $_[0];

    $self->assert( ! isendwin(), 'isendwin was set' );
    $self->assert_null( $Curses::UI::rootobject,
                        '$Curses::UI::rootobject != undef' );
    foreach my $var ( qw/ debug screen_too_small initialized color_support color_object/ )
    {
        no strict 'refs';
        my $var = 'Curses::UI::' . $var;
        $self->assert_num_equals( 0, $$var, "$var != 0: $$var" );
    }
    $self->assert_num_equals( _default_mouse_support(),
                              $Curses::UI::ncurses_mouse );


    return;
}

sub test_setup_curses_ui {
    my $self = $_[0];

    my $or_event_handler = \&Curses::UI::do_one_event;
    my $or_fatalerror = \&Curses::UI::fatalerror;

    my $driver = $self->{'driver'};
    $driver->setup_curses_ui();
    $self->assert( UNIVERSAL::isa( $driver->{'term'}, 'Curses::Screen' ),
                   "'term' attribute isn't a Curses::Screen instance: $driver->{'term'}" );
    $self->_check_curses_ui_globals();
    $self->assert_str_equals( $or_event_handler,
                              $driver->{'_do_one_event_ref'} );
    $self->assert_not_equals( $or_event_handler, \&Curses::UI::do_one_event );
    $self->assert_str_equals( $or_fatalerror,
                              $driver->{'_fatalerror_ref'} );
    $self->assert_str_equals( \&Lire::Test::CursesUIDriver::fatalerror,
                              \&Curses::UI::fatalerror );

    my $ui = new Curses::UI();
    $self->assert_str_equals( $Curses::UI::rootobject, $ui );

    $driver->{'_ui'} = $ui;
    $driver->teardown_curses_ui();

    $self->assert( isendwin(), 'isendwin was not set!' );
    $self->assert_null( $driver->{'_ui'}, "'_ui' attribute wasn't removed" );
    $self->assert_str_equals( $or_event_handler, \&Curses::UI::do_one_event );
    $self->assert_str_equals( $or_fatalerror, \&Curses::UI::fatalerror );

    $driver->setup_curses_ui();
    $self->_check_curses_ui_globals();

    $ui = undef;
    $ui = new Curses::UI();

    $self->assert_str_equals( $Curses::UI::rootobject, $ui );
    $driver->teardown_curses_ui();
    $self->assert( isendwin(), 'isendwin was not set!' );

    return;
}

package tests::TestCursesUIDriverTest::events;

use base qw/Lire::Test::TestCase/;

use Lire::Test::CursesUIDriver;

use Curses;
use Curses::UI;

sub set_up {
    my $self = $_[0];
    $self->SUPER::set_up();

    $self->{'driver'} = new Lire::Test::CursesUIDriver();
    $self->{'driver'}->setup_curses_ui();

    return;
}

sub tear_down {
    my $self = $_[0];
    $self->SUPER::tear_down();

    $self->{'driver'}->teardown_curses_ui();

    return;
}

sub test_set_curses_ui {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    $self->assert_died( sub { $driver->set_curses_ui( undef ) },
                        qr/missing \'ui\' parameter/ );
    $self->assert_died( sub { $driver->set_curses_ui( {} ) },
                        qr/\'ui\' parameter should be a \'Curses::UI\' instance, not \'HASH/ );

    my $ui = new Curses::UI();
    $driver->set_curses_ui( $ui );
    $self->assert_str_equals( $ui, $driver->{'_ui'} );
    $self->assert_died( sub { $driver->set_curses_ui( $ui ) },
                        qr/you need to call teardown_curses_ui\(\) before calling set_curses_ui\(\) again/ );
}

sub test_find_menu_def {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();

    my $menu = [ { -label => 'File',
                   -submenu => [ { -label => 'Reports',
                                   -submenu => [ { -label => 'Report 1' }, ],
                                 },
                                 { -label => 'Scalar Menu',
                                   -value => 'Hi!',
                                  },
                                 { -label => 'Exit',
                                 }, ],
                 } ];
    my $menubar = $ui->add( 'menu', 'Menubar', -menu => $menu );

    $self->assert_died( sub { $driver->activate_menu( 'menu/File/Exit' ) },
                        qr/set_curses_ui\(\) wasn't called/ );
    $driver->set_curses_ui( $ui );

    $self->assert_died( sub { $driver->activate_menu() },
                        qr/missing 'menu_path' parameter/ );
    $self->assert_died( sub { $driver->activate_menu( 'menu' ) },
                        qr/'menu_path' should have at least 2 components/ );
    $self->assert_died( sub { $driver->activate_menu( 'menu1/Reports' ) },
                        qr/no 'menu1' menubar/ ); #'/); # cperl-mode...

    $self->assert_died( sub { $driver->activate_menu( 'menu/Reports' ) },
                        qr/no 'Reports' menu in 'menu' menubar/ );
    $self->assert_died( sub { $driver->activate_menu('menu/File/Reports/Report 2') },
                        qr{no 'Report 2' submenu in 'menu/File/Reports} );

    $self->assert_deep_equals( $menu->[0],
                               $driver->find_menu_def( 'menu/File' ) );
    $self->assert_deep_equals( $menu->[0]{'-submenu'}[0],
                               $driver->find_menu_def( 'menu/File/Reports' ) );
    $self->assert_deep_equals( $menu->[0]{'-submenu'}[2],
                               $driver->find_menu_def( 'menu/File/Exit' ) );
}

sub test_activate_menu {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();
    my $report_called =0;
    my $exit_called = undef;
    my $menu = [ { -label => 'File',
                   -submenu => [ { -label => 'Reports',
                                   -submenu => [ { -label => 'Report 1',
                                                   -value => sub { $report_called = 1 },
                                                 }, ],
                                 },
                                 { -label => 'Scalar Menu',
                                   -value => 'Hi!',
                                  },
                                 { -label => 'Exit',
                                   -value => sub { $exit_called = shift },
                                 }, ],
                 } ];
    my $menubar = $ui->add( 'menu', 'Menubar', -menu => $menu );

    $self->assert_died( sub { $driver->activate_menu( 'menu/File/Exit' ) },
                        qr/set_curses_ui\(\) wasn't called/ );
    $driver->set_curses_ui( $ui );

    $self->assert_died( sub { $driver->activate_menu() },
                        qr/missing 'menu_path' parameter/ );
    $self->assert_died( sub { $driver->activate_menu( 'menu' ) },
                        qr/'menu_path' should have at least 2 components/ );
    $self->assert_died( sub { $driver->activate_menu( 'menu1/Reports' ) },
                        qr/no 'menu1' menubar/ ); #'/); # cperl-mode...

    $self->assert_died( sub { $driver->activate_menu( 'menu/Reports' ) },
                        qr/no 'Reports' menu in 'menu' menubar/ );
    $self->assert_died( sub { $driver->activate_menu('menu/File/Reports/Report 2') },
                        qr{no 'Report 2' submenu in 'menu/File/Reports} );


    $self->assert_died( sub { $driver->activate_menu( 'menu/File/Reports' ) },
                        qr{no callback defined for 'File/Reports' in 'menu' menubar} );
    $self->assert_died( sub { $driver->activate_menu( 'menu/File/Scalar Menu' ) },
                        qr{no callback defined for 'File/Scalar Menu' in 'menu' menubar} );

    $self->assert_equals( 0, $report_called );
    $driver->activate_menu( 'menu/File/Reports/Report 1' );
    $self->assert_equals( 1, $report_called );

    $self->assert_null( $exit_called );
    $driver->activate_menu( 'menu/File/Exit' );
    $self->assert_str_equals( $menubar, $exit_called );

    return;
}

sub test_find_menu {
    my $self = $_[0];

    my $menu = [ { -label => 'File',
                   -submenu => [ { -label => 'Reports',
                                   -submenu => [ { -label => 'Report 1' }, ],
                                 },
                                 { -label => 'Scalar Menu' },
                                 { -label => 'Exit' }, ],
                 } ];
    my $driver = $self->{'driver'};
    $self->assert_str_equals( $menu->[0], $driver->_find_menu( $menu, 'File') );
    $self->assert_str_equals( $menu->[0]{'-submenu'}[1], $driver->_find_menu( $menu->[0]{'-submenu'}, 'Scalar Menu') );
    $self->assert_null( $driver->_find_menu( $menu, 'wawa'),
                       '_find_menu should have returned undef' );
}

sub test_find_widget {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    $self->setup_three_pane_ui();

    $self->{'widgets'}{'field_1'}->focus();

    $self->assert_died( sub { $driver->find_widget() },
                        qr/missing 'path' parameter/ );

    $self->assert_died( sub { $driver->find_widget( '/win3' )},
                        qr{no widget 'win3' in root container} );
    $self->assert_str_equals( $self->{'widgets'}{'field_1'},
                              $driver->find_widget( '/win1/field_1' ) );

    $self->assert_str_equals( $self->{'widgets'}{'field_1'},
                              $driver->find_widget( 'field_1' ) );
    $self->assert_died( sub { $driver->find_widget( 'field_2' )},
                        qr{no widget 'field_2' in '/win1' container} );

    $self->assert_str_equals( $self->{'widgets'}{'field_2'},
                              $driver->find_widget( 'win3/field_2' ) );
    $self->assert_died( sub { $driver->find_widget( 'win3/field_3' )},
                        qr{no widget 'field_3' in '/win1/win3' container} );
    $self->assert_str_equals( $self->{'widgets'}{'buttons'},
                              $driver->find_widget( '/win2/buttons' ) );
    $self->assert_died( sub { $driver->find_widget( 'buttons' )},
                        qr{no widget 'buttons' in '/win1' container} );

    $self->{'widgets'}{'win2'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'buttons'},
                              $driver->find_widget( 'buttons' ) );

    $self->assert_str_equals( $self->{'widgets'}{'ui'},
                              $driver->find_widget( '/' ) );

    $self->{'widgets'}{'field_3'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'field_3'},
                              $driver->find_widget( '.' ) );
}

sub setup_three_pane_ui {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();
    $driver->set_curses_ui( $ui );

    my %w = ( 'ui' => $ui );
    $w{'win1'} = $ui->add( 'win1', 'Window', -height => 30 );
    $w{'field_1'} = $w{'win1'}->add( 'field_1', 'TextEntry' );
    $w{'win3'} = $w{'win1'}->add( 'win3', 'Window' );
    $w{'field_2'} = $w{'win3'}->add( 'field_2', 'TextEntry' );
    $w{'win2'} = $ui->add( 'win2', 'Window', -height => 30 );
    $w{'field_3'} = $w{'win2'}->add( 'field_3', 'TextEntry' );
    $w{'buttons'} = $w{'win2'}->add( 'buttons', 'Buttonbox', 
                                     -buttons => [ { -label => '[ OK ]',
                                                     -value => 1 },
                                                   { -label => '[ Cancel ]',
                                                     -value => 2 },
                                                 ] );
    $self->{'widgets'} = \%w;
    return;
}

sub test_top_window {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    $self->setup_three_pane_ui();

    $self->{'widgets'}{'field_2'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'win3'},
                              $driver->top_window() );
    $self->{'widgets'}{'field_3'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'win2'},
                              $driver->top_window() );
    $self->{'widgets'}{'field_1'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'win1'},
                              $driver->top_window() );
    $self->{'widgets'}{'win3'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'win3'},
                              $driver->top_window() );

    # Check that when focus is given to a container, it
    # focuses the last focused widgets. And in this case,
    # this also gives focus to a contained window.
    $self->{'widgets'}{'field_2'}->focus();
    $self->{'widgets'}{'win1'}->focus();
    $self->assert_str_equals( $self->{'widgets'}{'win3'},
                              $driver->top_window() );

    my $mbar = $self->{'widgets'}{'ui'}->add( 'menubar', 'Menubar',
                                              '-menu' => [ { '-label' => 'A menu' } ],
                                            );
    $mbar->focus();
    $self->assert_str_equals( $self->{'widgets'}{'win3'},
                              $driver->top_window() );
    $self->assert_str_equals( $mbar,
                              $self->{'widgets'}{'ui'}->focus_path(-1) );
}

sub test_widget_path {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    $self->setup_three_pane_ui();

    $self->assert_str_equals( '/',
                              $driver->_widget_path( $self->{'widgets'}{'ui'} ) 
                              );
    $self->assert_str_equals( '/win1',
                              $driver->_widget_path( $self->{'widgets'}{'win1'} ) );
    $self->assert_str_equals( '/win1/win3/field_2',
                              $driver->_widget_path( $self->{'widgets'}{'field_2'} ) );
}

sub test_click_button {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();
    $driver->set_curses_ui( $ui );
    my $win = $ui->add( 'win', 'Window' );
    my $clicked = 0;
    my $box = $win->add( 'buttons', 'Buttonbox',
                         -buttons => [ { '-label' => '[ OK ]',
                                         '-value' => 1 },
                                       { '-label' => '[ Cancel ]',
                                         '-value'  => 0 },
                                       { '-label' => '[ Weird ]',
                                         '-onpress' => sub {$clicked = shift }}
                                     ] );
    $box->focus();

    $self->assert_died( sub { $driver->click_button() },
                        qr/missing 'path' parameter/ );
    $self->assert_died( sub { $driver->click_button( 'buttons' ) },
                        qr/missing 'button' parameter/ );
    $self->assert_died( sub { $driver->click_button( '/win', 'OK' ) },
                        qr{'/win' should be a Curses::UI::Buttonbox widget, not 'Curses::UI::Window} );
    $self->assert_died( sub { $driver->click_button( 'buttons', 'OK' ) },
                        qr{no 'OK' button in '/win/buttons' Buttonbox} );

    $driver->click_button( 'buttons', '[ Cancel ]' );
    $self->assert_num_equals( 1, $box->{'-selected'} );
    $driver->click_button( 'buttons', '[ OK ]' );
    $self->assert_num_equals( 0, $box->{'-selected'} );

    $self->assert_num_equals( 0, $clicked );
    $driver->click_button( 'buttons', '[ Weird ]' );
    $self->assert_num_equals( 2, $box->{'-selected'} );
    $self->assert_num_equals( $box, $clicked );

    return;
}

sub test_enter_text {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();

    $driver->set_curses_ui( $ui );
    my $win = $ui->add( 'win', 'Window' );
    my $editor = $win->add( 'editor', 'TextEditor', -height => 20 );
    my $viewer = $win->add( 'viewer', 'TextViewer', -height => 20 );
    my $entry = $win->add( 'entry', 'TextEntry', -regexp => '/^\w+$/' );

    $self->assert_died( sub { $driver->enter_text() },
                        qr/missing 'path' parameter/ );

    $self->assert_died( sub { $driver->enter_text( 'text' ) },
                        qr/missing 'text' parameter/ );

    my $two_lines = "These are two lines\n of text.";
    $driver->enter_text( 'editor',  $two_lines );
    $self->assert_num_equals( 1, $editor->{'-focus'} );
    $self->assert_str_equals(  $two_lines, $editor->text() );
    $self->assert_num_equals(  length $two_lines, $editor->pos() );
    $self->assert_str_equals( $editor, $driver->find_widget( '.' ) );
    $driver->enter_text( 'editor',  " More text.\cA\cP" );
    $self->assert_str_equals(  $two_lines . " More text.", $editor->text() );
    $self->assert_equals( 0, $editor->pos() );

    $driver->enter_text( 'viewer',  "Text" );
    $self->assert_equals( 0, $editor->{'-focus'} );
    $self->assert_equals( 1, $viewer->{'-focus'} );
    $self->assert_str_equals( '', $viewer->text() );

    $driver->enter_text( 'entry', "This is some text." );
    $self->assert_equals( 1, $entry->{'-focus'} );
    $self->assert_str_equals( 'Thisissometext', $entry->get() );
}

sub test_enter_key {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();
    my $widget_cb;

    $driver->set_curses_ui( $ui );
    my $win = $ui->add( 'win', 'Window' );
    my $entry = $win->add( 'entry', 'TextEntry',
                           -onchange => sub { $widget_cb = shift }
                         );

    $self->assert_died( sub { $driver->enter_key() },
                        qr/missing 'path' parameter/ );
    $self->assert_died( sub { $driver->enter_key( 'text' ) },
                        qr/missing 'char' parameter/ );
    $self->assert_died( sub { $driver->enter_key( 'text', 'wawa' ) },
                        qr/'char' should be either an integer or a single character: 'wawa'/ );

    $driver->enter_key( 'entry', 'T' );
    $self->assert_str_equals( $entry, $widget_cb );
    $self->assert_equals( 1, $entry->{'-focus'} );
    $self->assert_str_equals( 'T', $entry->get() );
    $self->assert_num_equals( 1, $entry->pos() );
    $driver->enter_key( 'entry', KEY_HOME() );
    $self->assert_str_equals( 'T', $entry->get() );
    $self->assert_num_equals( 0, $entry->pos() );
}

sub test_select_items {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();
    $driver->set_curses_ui( $ui );
    my $win = $ui->add( 'win', 'Window' );
    my ( $radio_cb, $list_cb, $pop_cb);
    my $radio = $win->add( 'radio', 'Radiobuttonbox',
                           -values => [ 'radio1', 'radio2', 'radio3' ],
                           -labels => { 'radio3' => 'Radio Button3' },
                           -onchange => sub { $radio_cb = shift } );
    my $list = $win->add( 'list', 'Listbox',
                           -multi => 1,
                           -values => [ 'option1', 'option2', 'option3' ],
                           -onchange => sub { $list_cb = shift } );
    my $pop = $win->add( 'pop', 'Popupmenu',
                         -values => [ 'menu1', 'menu2', 'menu3' ],
                         -onchange => sub { $pop_cb = shift } );

    $self->assert_died( sub { $driver->select_items() },
                        qr/missing 'path' parameter/ );
    $self->assert_died( sub { $driver->select_items( 'radio' ) },
                        qr/at least one 'item' parameter is required/ );
    $self->assert_died( sub { $driver->select_items( '/win', 'option1' ) },
                        qr{'/win' should be a Curses::UI::Listbox or Curses::UI::Popupmenu widget, not 'Curses::UI::Window} );

    $self->assert_died( sub { $driver->select_items( 'radio', 'radio1', 'radio2' ) },
                        qr{'/win/radio' doesn't support multi-selection} );
    $self->assert_died( sub { $driver->select_items( 'pop', 'menu1', 'menu2' ) },
                        qr{'/win/pop' doesn't support multi-selection} );
    $self->assert_died( sub { $driver->select_items( 'list', 'option1', 'option4' ) },
                        qr{'/win/list' doesn't have any 'option4' item} );

    $self->assert_died( sub { $driver->select_items( 'radio', 'radio3' ) },
                        qr{'/win/radio' doesn't have any 'radio3' item} );

    $driver->select_items( 'radio', 'Radio Button3' );
    $self->assert_str_equals( 'radio3', $radio->get() );
    $self->assert_str_equals( $radio, $radio_cb );

    $driver->select_items( 'pop', 'menu3' );
    $self->assert_str_equals( 'menu3', $pop->get() );
    $self->assert_str_equals( $pop, $pop_cb );
    $pop_cb = undef;
    $driver->select_items( 'pop', 'menu3' );
    $self->assert_null( $pop_cb, 'callback was called' );

    $driver->select_items( 'list', 'option1', 'option2', 'option3' );
    $self->assert_deep_equals( [ 'option1', 'option2', 'option3' ] ,
                               [ sort $list->get() ] );
    $self->assert_str_equals( $list, $list_cb );
}

sub test_widget_displayed_labels {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $empty_widget = { '-values' => [] };
    my $widget_no_labels = { '-values' => [ 1, 2, 3 ] };
    my $widget = { '-values' => [ 1, 2, 'c' ],
                   '-labels' => { '1' => 'a', '2' => 'b' } };

    $self->assert_deep_equals( [], $driver->_widget_displayed_labels( $empty_widget ) );
    $self->assert_deep_equals( [ 1, 2, 3 ], $driver->_widget_displayed_labels( $widget_no_labels ) );
    $self->assert_deep_equals( [ 'a', 'b', 'c' ], $driver->_widget_displayed_labels( $widget ) );

}

sub test_event_loop_dispatcher {
    my $self = $_[0];

    my $driver = $self->{'driver'};
    my $ui = new Curses::UI();
    $driver->set_curses_ui( $ui );
    $self->assert_died( sub { $driver->event_loop_dispatcher() },
                        qr/no event loop handlers available in current CursesUIDriver/ );

    my $called = 0;
    my $widget = { '-has_modal_focus' => 1 };
    $driver->{'_event_loop_handlers'} = [ sub { $called = [ @_ ] } ];
    $driver->event_loop_dispatcher( $driver->{'_ui'}, $widget );
    $self->assert_deep_equals( [ $driver->{'_ui'}, $widget], $called  );
    $self->assert_equals( 0, $widget->{'-has_modal_focus'} );
    $self->assert_deep_equals( [], $driver->{'_event_loop_handlers'} );

    # Test that the closure dispatching is working
    push @{$driver->{'_event_loop_handlers'}}, sub { $called = shift };
    $driver->{'_ui'}->do_one_event();
    $self->assert_str_equals( $driver->{'_ui'}, $called );
}

sub test_add_event_loop_handler {
    my $self = $_[0];

    my $driver = $self->{'driver'};

    $self->assert_deep_equals( [], $driver->{'_event_loop_handlers'} );
    $self->assert_died( sub { $driver->add_event_loop_handler() },
                        qr/missing 'handler' parameter/ );
    $self->assert_died( sub { $driver->add_event_loop_handler( {} ) },
                        qr/'handler' should be a CODE ref, not 'HASH/ );

    my $handler1 = sub {};
    my $handler2 = sub {};
    $driver->add_event_loop_handler( $handler1 );
    $self->assert_deep_equals( [ $handler1 ],
                               $driver->{'_event_loop_handlers'} );
    $driver->add_event_loop_handler( $handler2 );
    $self->assert_deep_equals( [ $handler1, $handler2 ],
                               $driver->{'_event_loop_handlers'} );
}

1;


syntax highlighted by Code2HTML, v. 0.9.1