#![ cfg( feature = "test-support" ) ] // 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 { column::() .padding( 16.0 ) .spacing( 8.0 ) .push( text_edit( "Username", "alice" ) .on_change( |s| Msg::Username( s ) ) .on_submit( Msg::UsernameSubmit ), ) .push( text_edit::( "Password", "" ) .secure( true ) .on_change( |s| Msg::Password( s ) ) .on_submit( Msg::PasswordSubmit ), ) .into() } fn render_login() -> UiSurface { let mut surface = UiSurface::::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::::new( 320, 240 ); let mut username = String::from( "alice" ); let view = |val: &str| -> Element { column::() .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::::new( 240, 200 ); let view: Element = column() .push( text_edit::( "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::::new( 240, 100 ); let view: Element = 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::::new( 240, 100 ); let view: Element = column() .push( text_edit::( "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" ), } }