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;