use std::rc::Rc;
use slate_framework::app_state::AppState;
use slate_framework::app_state::window_state::WindowState;
use slate_framework::element::AnyElement;
use slate_framework::elements::Div;
use slate_framework::elements::text_area::build_key_down_handler_for_test;
use slate_framework::event::KeyHandlers;
use slate_framework::executor::{Executor, RedrawRequester};
use slate_framework::focus::FocusableEntry;
use slate_framework::ime::ImeState;
use slate_framework::text_system::TextSystem;
use slate_framework::types::ElementId;
use slate_framework::view::{IntoAny, View};
use slate_framework::{Key, KeyCode, Modifiers, NamedKey};
use slate_platform::{DefaultPlatform, Platform, Window, WindowId, WindowOptions, wake_run_loop};
use slate_reactive::{Runtime, Signal};
type Case = (&'static str, fn() -> Result<(), String>);
macro_rules! ensure_eq {
($left:expr, $right:expr, $($arg:tt)*) => {{
let left = $left;
let right = $right;
if left != right {
return Err(format!(
"{} (left: {:?}, right: {:?})",
format!($($arg)*),
left,
right
));
}
}};
}
#[allow(dead_code)]
struct NoopView;
impl View for NoopView {
fn render(&mut self, _cx: &mut slate_framework::RenderCx) -> AnyElement {
Div::new().into_any()
}
}
fn make_state() -> (Rc<AppState>, WindowId) {
let platform = DefaultPlatform::new();
let window = platform.create_window(WindowOptions {
title: "slate-textarea-nav-test".into(),
size: (1, 1),
min_size: None,
resizable: false,
visible: false,
position: Some((-32000, -32000)),
});
let redraw_requester = RedrawRequester::new(wake_run_loop);
let executor = Executor::new(redraw_requester.clone());
let runtime = slate_reactive::Runtime::new();
let _ = platform;
let state = Rc::new(AppState::new(
executor,
redraw_requester.clone(),
runtime.clone(),
));
let window_id = window.id();
{
let win_state = WindowState::new(window, runtime);
state.windows.borrow_mut().insert(window_id, win_state);
}
state.register_redraw_requester_for_test(window_id, redraw_requester);
(state, window_id)
}
fn three_line_layout() -> slate_text::MultilineLayout {
let mut text_system = TextSystem::new().expect("create TextSystem");
let font = text_system
.load_font_from_bytes(slate_text::TEST_FONT, 14.0, 1.0)
.expect("load bundled font");
let doc = text_system
.shape_document(&font, "alpha\nbeta\ngamma")
.expect("shape document");
slate_text::wrap_document(&doc, 1000.0)
}
fn setup(caret: usize) -> (Rc<AppState>, WindowId, Rc<std::cell::RefCell<ImeState>>) {
let (state, win) = make_state();
let elem = ElementId::from_raw(20);
state.register_focusable_for_test(
win,
FocusableEntry {
id: elem,
tab_index: 0,
focus_ring: true,
},
);
state.set_focus_for_test(win, elem);
let ime_rc = state.register_ime_state_for_test(win, elem);
{
let mut s = ime_rc.borrow_mut();
s.text = "alpha\nbeta\ngamma".to_string();
s.caret = caret;
s.last_layout = Some(Rc::new(three_line_layout()));
}
state.republish_ime_cache_for_test(win);
let rt = Runtime::new();
let signal = Signal::new(rt, String::new());
state.install_element_key_handlers_for_test(
win,
elem,
KeyHandlers {
on_key_down: Some(build_key_down_handler_for_test(signal)),
on_key_up: None,
on_text_input: None,
},
);
(state, win, ime_rc)
}
fn press(state: &Rc<AppState>, win: WindowId, code: KeyCode, named: NamedKey) {
state.dispatch_key_down_for_test(win, code, Key::Named(named), Modifiers::default(), false);
}
fn check_platform_and_window_construct() -> Result<(), String> {
let _state = make_state();
Ok(())
}
fn check_arrow_down_through_real_handler_moves_to_next_line_no_panic() -> Result<(), String> {
let (state, win, ime_rc) = setup(0); press(&state, win, KeyCode::ArrowDown, NamedKey::ArrowDown);
let s = ime_rc.borrow();
ensure_eq!(s.caret, 6, "↓ from line0 col0 lands at start of line1");
ensure_eq!(s.desired_x, Some(0.0), "↓ seeds the sticky column");
Ok(())
}
fn check_arrow_up_at_first_line_clamps_no_panic() -> Result<(), String> {
let (state, win, ime_rc) = setup(3); press(&state, win, KeyCode::ArrowUp, NamedKey::ArrowUp);
let s = ime_rc.borrow();
ensure_eq!(s.caret, 0, "↑ on the first line clamps to line start");
Ok(())
}
fn check_end_through_real_handler_is_visual_line_relative_no_panic() -> Result<(), String> {
let (state, win, ime_rc) = setup(6); press(&state, win, KeyCode::End, NamedKey::End);
let s = ime_rc.borrow();
ensure_eq!(s.caret, 10, "End is visual-line relative");
Ok(())
}
fn check_home_through_real_handler_is_visual_line_relative_no_panic() -> Result<(), String> {
let (state, win, ime_rc) = setup(9); press(&state, win, KeyCode::Home, NamedKey::Home);
let s = ime_rc.borrow();
ensure_eq!(s.caret, 6, "Home lands at line1 start, not document start");
Ok(())
}
fn main() {
let cases: &[Case] = &[
(
"platform_and_window_construct",
check_platform_and_window_construct,
),
(
"arrow_down_through_real_handler_moves_to_next_line_no_panic",
check_arrow_down_through_real_handler_moves_to_next_line_no_panic,
),
(
"arrow_up_at_first_line_clamps_no_panic",
check_arrow_up_at_first_line_clamps_no_panic,
),
(
"end_through_real_handler_is_visual_line_relative_no_panic",
check_end_through_real_handler_is_visual_line_relative_no_panic,
),
(
"home_through_real_handler_is_visual_line_relative_no_panic",
check_home_through_real_handler_is_visual_line_relative_no_panic,
),
];
let mut failed = 0;
for (name, f) in cases {
match f() {
Ok(()) => println!("ok - {name}"),
Err(e) => {
eprintln!("FAIL - {name}: {e}");
failed += 1;
}
}
}
if failed > 0 {
eprintln!("\n{failed} case(s) failed");
std::process::exit(1);
}
println!("\nall {} case(s) passed", cases.len());
}