First commit. Version 0.1.0
This commit is contained in:
207
tests/text_edit_dispatch.rs
Normal file
207
tests/text_edit_dispatch.rs
Normal file
@@ -0,0 +1,207 @@
|
||||
// End-to-end coverage for the `text_edit` ↔ event-loop contract: the layout
|
||||
// pass snapshots the widget's `on_change` / `on_submit` / `value` into a
|
||||
// `WidgetHandlers::TextEdit` entry, and the runtime drives keystrokes through
|
||||
// `text_change_msg` / `submit_msg` / `current_value`. These tests skip the
|
||||
// Wayland loop and exercise that contract directly through `UiSurface`.
|
||||
|
||||
use ltk::core::{ RenderOptions, UiSurface };
|
||||
use ltk::test_support::WidgetHandlers;
|
||||
use ltk::{ column, text_edit, Color, Element };
|
||||
|
||||
#[ derive( Clone, Debug, PartialEq, Eq ) ]
|
||||
enum Msg
|
||||
{
|
||||
Username( String ),
|
||||
Password( String ),
|
||||
UsernameSubmit,
|
||||
PasswordSubmit,
|
||||
}
|
||||
|
||||
fn login_view() -> Element<Msg>
|
||||
{
|
||||
column::<Msg>()
|
||||
.padding( 16.0 )
|
||||
.spacing( 8.0 )
|
||||
.push(
|
||||
text_edit( "Username", "alice" )
|
||||
.on_change( |s| Msg::Username( s ) )
|
||||
.on_submit( Msg::UsernameSubmit ),
|
||||
)
|
||||
.push(
|
||||
text_edit::<Msg>( "Password", "" )
|
||||
.secure( true )
|
||||
.on_change( |s| Msg::Password( s ) )
|
||||
.on_submit( Msg::PasswordSubmit ),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_login() -> UiSurface<Msg>
|
||||
{
|
||||
let mut surface = UiSurface::<Msg>::new( 320, 240 );
|
||||
let _ = surface.render(
|
||||
&login_view(),
|
||||
RenderOptions::full_canvas( 320, 240 ).background( Color::rgb( 0.1, 0.1, 0.1 ) ),
|
||||
);
|
||||
surface
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn layout_records_two_text_edit_widgets()
|
||||
{
|
||||
let surface = render_login();
|
||||
assert_eq!( surface.widget_rects().len(), 2 );
|
||||
for w in surface.widget_rects()
|
||||
{
|
||||
assert!( w.handlers.is_text_input() );
|
||||
}
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn text_change_msg_carries_new_value_through_user_callback()
|
||||
{
|
||||
let surface = render_login();
|
||||
let username_idx = surface.widget_rects()[ 0 ].flat_idx;
|
||||
|
||||
let h = surface.handlers( username_idx ).expect( "text edit handler should exist" );
|
||||
let msg = h.text_change_msg( "alic" ); // user pressed backspace once
|
||||
assert_eq!( msg, Some( Msg::Username( "alic".into() ) ) );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn text_change_msg_passes_unicode_unchanged()
|
||||
{
|
||||
let surface = render_login();
|
||||
let username_idx = surface.widget_rects()[ 0 ].flat_idx;
|
||||
let h = surface.handlers( username_idx ).unwrap();
|
||||
|
||||
let msg = h.text_change_msg( "café 🦀" );
|
||||
assert_eq!( msg, Some( Msg::Username( "café 🦀".into() ) ) );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn submit_msg_returns_configured_message()
|
||||
{
|
||||
let surface = render_login();
|
||||
let password_idx = surface.widget_rects()[ 1 ].flat_idx;
|
||||
let h = surface.handlers( password_idx ).unwrap();
|
||||
assert_eq!( h.submit_msg(), Some( Msg::PasswordSubmit ) );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn each_text_edit_dispatches_to_its_own_callback()
|
||||
{
|
||||
// The two fields share the same `Msg` enum but differ in which variant
|
||||
// they wrap the new value in — confirms the handler snapshot captured
|
||||
// the right closure for the right widget.
|
||||
let surface = render_login();
|
||||
let username_h = surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap();
|
||||
let password_h = surface.handlers( surface.widget_rects()[ 1 ].flat_idx ).unwrap();
|
||||
|
||||
assert_eq!( username_h.text_change_msg( "x" ), Some( Msg::Username( "x".into() ) ) );
|
||||
assert_eq!( password_h.text_change_msg( "x" ), Some( Msg::Password( "x".into() ) ) );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn current_value_reflects_initial_state()
|
||||
{
|
||||
let surface = render_login();
|
||||
let username_h = surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap();
|
||||
let password_h = surface.handlers( surface.widget_rects()[ 1 ].flat_idx ).unwrap();
|
||||
|
||||
assert_eq!( username_h.current_value(), Some( "alice" ) );
|
||||
assert_eq!( password_h.current_value(), Some( "" ) );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn current_value_reflects_state_after_app_update()
|
||||
{
|
||||
// User types into the username field. The app updates state and the next
|
||||
// render must snapshot the new value into the handler.
|
||||
let mut surface = UiSurface::<Msg>::new( 320, 240 );
|
||||
let mut username = String::from( "alice" );
|
||||
|
||||
let view = |val: &str| -> Element<Msg>
|
||||
{
|
||||
column::<Msg>()
|
||||
.padding( 16.0 )
|
||||
.push(
|
||||
text_edit( "Username", val.to_string() )
|
||||
.on_change( |s| Msg::Username( s ) ),
|
||||
)
|
||||
.into()
|
||||
};
|
||||
|
||||
let opts = RenderOptions::full_canvas( 320, 240 );
|
||||
let _ = surface.render( &view( &username ), opts );
|
||||
assert_eq!(
|
||||
surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap().current_value(),
|
||||
Some( "alice" ),
|
||||
);
|
||||
|
||||
username.push_str( "_2" );
|
||||
let _ = surface.render( &view( &username ), opts );
|
||||
assert_eq!(
|
||||
surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap().current_value(),
|
||||
Some( "alice_2" ),
|
||||
);
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn is_text_input_distinguishes_text_edit_from_other_handlers()
|
||||
{
|
||||
use ltk::button;
|
||||
let mut surface = UiSurface::<Msg>::new( 240, 200 );
|
||||
let view: Element<Msg> = column()
|
||||
.push( text_edit::<Msg>( "p", "" ) )
|
||||
.push( button( "go" ).on_press( Msg::UsernameSubmit ) )
|
||||
.into();
|
||||
let _ = surface.render( &view, RenderOptions::full_canvas( 240, 200 ) );
|
||||
|
||||
let h0 = surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap();
|
||||
let h1 = surface.handlers( surface.widget_rects()[ 1 ].flat_idx ).unwrap();
|
||||
assert!( h0.is_text_input() );
|
||||
assert!( !h1.is_text_input() );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn submit_msg_returns_none_for_non_text_edit_handlers()
|
||||
{
|
||||
use ltk::button;
|
||||
let mut surface = UiSurface::<Msg>::new( 240, 100 );
|
||||
let view: Element<Msg> = column()
|
||||
.push( button( "go" ).on_press( Msg::UsernameSubmit ) )
|
||||
.into();
|
||||
let _ = surface.render( &view, RenderOptions::full_canvas( 240, 100 ) );
|
||||
|
||||
let h = surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap();
|
||||
assert!( h.submit_msg().is_none() );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn text_change_msg_returns_none_when_no_callback_configured()
|
||||
{
|
||||
let mut surface = UiSurface::<Msg>::new( 240, 100 );
|
||||
let view: Element<Msg> = column()
|
||||
.push( text_edit::<Msg>( "no callback", "x" ) )
|
||||
.into();
|
||||
let _ = surface.render( &view, RenderOptions::full_canvas( 240, 100 ) );
|
||||
|
||||
let h = surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap();
|
||||
assert!( h.text_change_msg( "y" ).is_none() );
|
||||
}
|
||||
|
||||
#[ test ]
|
||||
fn handler_variant_for_text_edit_carries_value_field()
|
||||
{
|
||||
// Confirm the layout pass populates the `value` field of the
|
||||
// `WidgetHandlers::TextEdit` snapshot — input dispatch reads it for cursor
|
||||
// placement / backspace rebuild.
|
||||
let surface = render_login();
|
||||
let h = surface.handlers( surface.widget_rects()[ 0 ].flat_idx ).unwrap();
|
||||
match h
|
||||
{
|
||||
WidgetHandlers::TextEdit { value, .. } => assert_eq!( value, "alice" ),
|
||||
_ => panic!( "expected TextEdit handler variant" ),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user