🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
//! Testy interakcji demo — bez okna i bez Waylanda. Przepuszczają prawdziwe zdarzenia
//! przez `ui::handle_events` i sprawdzają zmiany stanu.
use libluna_core::{
Accent, ButtonState, Event, KeyEvent, Modifiers, Point, PointerButton, PointerEvent,
ScrollEvent, Size, ThemePreference, ThemeSettings,
};
use libluna_render::TextEngine;
use libluna_widgets::{RadioGroup, ViewSwitcher};
use luna_demo::ui::{self, make_nav, Demo, Popover};
fn size() -> Size {
Size::new(900.0, 1028.0)
}
fn press(p: Point) -> Event {
Event::PointerButton(PointerEvent {
position: p,
button: PointerButton::Left,
state: ButtonState::Pressed,
modifiers: Modifiers::NONE,
})
}
/// Wysyła jedno kliknięcie do logiki demo.
fn click(settings: &mut ThemeSettings, demo: &mut Demo, p: Point) {
let theme = settings.resolve_themed(false);
let text = TextEngine::system();
ui::handle_events(settings, &[press(p)], size(), &theme, &text, demo);
}
fn layout(settings: &ThemeSettings, demo: &Demo) -> ui::Layout {
ui::layout(size(), &settings.resolve_themed(false), demo)
}
#[test]
fn clicking_dark_segment_sets_dark_theme() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let seg = ViewSwitcher::new(["a", "b", "c"]).segments(layout(&settings, &demo).theme_switcher)
[1]
.center();
click(&mut settings, &mut demo, seg);
assert_eq!(settings.preference(), ThemePreference::Dark);
}
#[test]
fn clicking_accent_dot_sets_accent() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let dot = layout(&settings, &demo).accents[7].center(); // Purple
click(&mut settings, &mut demo, dot);
assert_eq!(settings.accent(), Accent::Purple);
}
#[test]
fn widgets_entry_opens_gallery_and_appearance_returns() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve_themed(false);
let area = layout(&settings, &demo).sidebar;
let rows = ui::make_nav().rows(area, &theme);
let widgets = rows
.iter()
.find(|r| r.entry == ui::WIDGETS_NAV && r.selectable)
.expect("pozycja Widgety");
click(&mut settings, &mut demo, widgets.rect.center());
assert_eq!(demo.nav_selected, Some(ui::WIDGETS_NAV));
assert!(ui::is_gallery(&demo));
let appearance = rows.iter().find(|r| r.entry == 2).expect("pozycja Wygląd");
click(&mut settings, &mut demo, appearance.rect.center());
assert_eq!(demo.nav_selected, Some(2));
assert!(!ui::is_gallery(&demo));
}
#[test]
fn clicking_nav_item_selects_it() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve(false);
let item = make_nav().rows(layout(&settings, &demo).sidebar, &theme)[5]
.rect
.center(); // Sieć
click(&mut settings, &mut demo, item);
assert_eq!(demo.nav_selected, Some(5));
}
#[test]
fn clicking_view_switcher_changes_mode() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let seg =
ViewSwitcher::new(["a", "b"]).segments(layout(&settings, &demo).view_switcher)[1].center();
click(&mut settings, &mut demo, seg);
assert_eq!(demo.view_mode, 1);
}
#[test]
fn clicking_tab_selects_page() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve(false);
let text = TextEngine::system();
let seg = ui::tab_bar(&demo).segments(layout(&settings, &demo).tabs, &text, &theme)[2].center();
click(&mut settings, &mut demo, seg);
assert_eq!(demo.tabs.selected(), 2);
}
#[test]
fn about_menu_opens_bottom_sheet_and_scrim_closes_it() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Otwórz menu „☰”.
let burger = layout(&settings, &demo).burger.center();
click(&mut settings, &mut demo, burger);
assert_eq!(demo.popover, Some(Popover::Menu));
// Klik „O aplikacji” (indeks 4) otwiera dolny panel.
let theme = settings.resolve(false);
let text = TextEngine::system();
let about = ui::popover_menu(size(), &theme, &text, &layout(&settings, &demo), &demo)
.map(|(panel, menu)| menu.rows(panel, &theme)[5].rect.center())
.unwrap();
click(&mut settings, &mut demo, about);
assert!(demo.sheet.is_open());
// Klik w tło (poza panelem) zamyka panel.
click(&mut settings, &mut demo, Point::new(120.0, 300.0));
assert!(!demo.sheet.is_open());
}
#[test]
fn search_entry_focus_and_typing() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Klik w pole nadaje fokus.
let search = layout(&settings, &demo).search;
click(&mut settings, &mut demo, search.center());
assert!(demo.search.is_focused());
// Wpisanie tekstu (platforma dostarcza `text`).
let theme = settings.resolve(false);
let text = TextEngine::system();
let key = Event::Key(KeyEvent {
keysym: 0x61,
keycode: 30,
text: Some("a".to_owned()),
state: ButtonState::Pressed,
modifiers: Modifiers::NONE,
repeat: false,
});
ui::handle_events(&mut settings, &[key], size(), &theme, &text, &mut demo);
assert_eq!(demo.search.text(), "a");
}
#[test]
fn clicking_path_component_navigates_up() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
assert_eq!(demo.path.len(), 3);
let theme = settings.resolve(false);
let text = TextEngine::system();
let segs = ui::path_bar(&demo).segments(layout(&settings, &demo).path, &text, &theme);
// Klik w element bieżący nawigację „w górę” — pierwszy widoczny składnik (np. Domowy).
let first = segs
.iter()
.find(|(pick, _)| *pick == Some(0))
.map(|(_, r)| r.center())
.expect("first path component is visible");
click(&mut settings, &mut demo, first);
assert_eq!(demo.path.len(), 1);
}
#[test]
fn clicking_file_item_selects_it() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve(false);
let slot = ui::item_view(&demo)
.slot_rect(layout(&settings, &demo).items, 1, &theme)
.center();
click(&mut settings, &mut demo, slot);
assert_eq!(demo.items.selected(), Some(1));
}
#[test]
fn scrolling_files_moves_offset() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve(false);
let text = TextEngine::system();
let items = layout(&settings, &demo).items;
let scroll = Event::Scroll(ScrollEvent {
position: items.center(),
dx: 0.0,
dy: 3.0,
});
ui::handle_events(&mut settings, &[scroll], size(), &theme, &text, &mut demo);
assert!(demo.items.scroll() > 0.0);
}
#[test]
fn options_popover_selects_sort() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Otwórz popover „Opcje”.
let btn = layout(&settings, &demo).options_button.center();
click(&mut settings, &mut demo, btn);
assert_eq!(demo.popover, Some(Popover::Options));
// Klik w 3. opcję sortowania (Rozmiar).
let theme = settings.resolve(false);
let ol = ui::options_layout(size(), &theme, &layout(&settings, &demo));
let row = RadioGroup::new(["a", "b", "c"])
.row_rect(ol.radios, 2, &theme)
.center();
click(&mut settings, &mut demo, row);
assert_eq!(demo.sort, 2);
// Popover zostaje otwarty po kliknięciu wewnątrz.
assert_eq!(demo.popover, Some(Popover::Options));
}
#[test]
fn clicking_sidebar_icon_toggles_and_back() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let icon = layout(&settings, &demo).sidebar_button.center();
click(&mut settings, &mut demo, icon);
assert!(!demo.sidebar_visible);
// Klik w to samo miejsce (layout liczy się na nowo) rozwija z powrotem.
click(&mut settings, &mut demo, icon);
assert!(demo.sidebar_visible);
}
#[test]
fn dragging_sidebar_edge_resizes_within_bounds() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let edge = layout(&settings, &demo).sidebar_handle.center();
click(&mut settings, &mut demo, edge);
assert!(demo.dragging_sidebar);
// Ruch wskaźnika mocno w prawo — szerokość przycięta do maksimum.
let theme = settings.resolve(false);
let text = TextEngine::system();
ui::handle_events(
&mut settings,
&[Event::PointerMotion {
position: Point::new(5000.0, 300.0),
modifiers: Modifiers::NONE,
}],
size(),
&theme,
&text,
&mut demo,
);
assert_eq!(demo.sidebar_width, 460.0);
}
#[test]
fn burger_opens_menu_and_clicking_action_closes_it() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Klik „☰” otwiera popover.
let burger = layout(&settings, &demo).burger.center();
click(&mut settings, &mut demo, burger);
assert_eq!(demo.popover, Some(Popover::Menu));
// Klik w pierwszą pozycję menu zamyka je.
let theme = settings.resolve(false);
let text = TextEngine::system();
let (panel, menu) =
ui::popover_menu(size(), &theme, &text, &layout(&settings, &demo), &demo).unwrap();
let first = menu.rows(panel, &theme)[0].rect.center();
click(&mut settings, &mut demo, first);
assert!(demo.popover.is_none());
}
#[test]
fn clicking_outside_closes_menu() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let burger = layout(&settings, &demo).burger.center();
click(&mut settings, &mut demo, burger);
assert_eq!(demo.popover, Some(Popover::Menu));
// Klik daleko od panelu (środek treści) zamyka menu.
click(&mut settings, &mut demo, Point::new(400.0, 500.0));
assert!(demo.popover.is_none());
}
#[test]
fn window_controls_are_widget_managed() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let header = layout(&settings, &demo).header;
let close = header.close.expect("window controls on the right").center();
click(&mut settings, &mut demo, close);
assert!(demo.pending_close);
let minimize = header.minimize.unwrap().center();
click(&mut settings, &mut demo, minimize);
assert!(demo.pending_minimize);
}
#[test]
fn language_dropdown_opens_and_selects() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Otwarcie listy języków.
let dd = layout(&settings, &demo).language.center();
click(&mut settings, &mut demo, dd);
assert_eq!(demo.popover, Some(Popover::Language));
// Wybór ostatniej pozycji ustawia język i zamyka popover.
let theme = settings.resolve(false);
let text = TextEngine::system();
let (panel, menu) =
ui::popover_menu(size(), &theme, &text, &layout(&settings, &demo), &demo).unwrap();
let last = menu.rows(panel, &theme).last().unwrap().rect.center();
click(&mut settings, &mut demo, last);
assert!(demo.popover.is_none());
}
#[test]
fn accent_change_shows_toast_and_undo_reverts() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Zmiana akcentu pokazuje powiadomienie.
let dot = layout(&settings, &demo).accents[2].center();
click(&mut settings, &mut demo, dot);
assert_eq!(settings.accent(), Accent::ALL[2]);
assert!(demo.toast.is_open());
// Klik „Cofnij” przywraca poprzedni akcent i chowa powiadomienie.
let theme = settings.resolve(false);
let text = TextEngine::system();
let undo = demo
.toast
.action_rect(size(), &theme, &text)
.expect("toast action")
.center();
click(&mut settings, &mut demo, undo);
assert_eq!(settings.accent(), Accent::default());
assert!(!demo.toast.is_open());
}
#[test]
fn search_without_results_shows_empty_state_that_clears() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
demo.search.set_text("zzz");
let theme = settings.resolve(false);
let action = ui::empty_state(&demo)
.expect("status page for empty search")
.rects(layout(&settings, &demo).items, &theme)
.action
.expect("action button")
.center();
click(&mut settings, &mut demo, action);
assert!(demo.search.is_empty());
assert!(ui::empty_state(&demo).is_none());
}
#[test]
fn search_filters_items_and_selection_maps_to_full_list() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
demo.search.set_text("proj");
let visible = ui::visible_indices(&demo);
assert_eq!(
visible.len(),
1,
"filter should narrow the list to one item"
);
let view = ui::item_view(&demo);
assert_eq!(view.len(), 1);
// Klik widocznego elementu zaznacza go w pełnej liście.
let theme = settings.resolve(false);
let slot = view
.slot_rect(layout(&settings, &demo).items, 0, &theme)
.center();
click(&mut settings, &mut demo, slot);
assert_eq!(demo.items.selected(), Some(visible[0]));
}
#[test]
fn sheet_entry_focus_and_typing() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
// Otwórz dolny panel: menu ☰ → „O aplikacji”.
let burger = layout(&settings, &demo).burger.center();
click(&mut settings, &mut demo, burger);
let theme = settings.resolve(false);
let text = TextEngine::system();
let about = ui::popover_menu(size(), &theme, &text, &layout(&settings, &demo), &demo)
.map(|(panel, menu)| menu.rows(panel, &theme)[5].rect.center())
.expect("about entry");
click(&mut settings, &mut demo, about);
assert!(demo.sheet.is_open());
// Klik pola nadaje fokus; wpisany znak trafia do `Entry`.
let entry = ui::sheet_entry_rect(size(), &theme).center();
click(&mut settings, &mut demo, entry);
assert!(demo.sheet_entry.is_focused());
let key = Event::Key(KeyEvent {
keysym: 0x41,
keycode: 30,
text: Some("A".to_owned()),
state: ButtonState::Pressed,
modifiers: Modifiers::NONE,
repeat: false,
});
ui::handle_events(&mut settings, &[key], size(), &theme, &text, &mut demo);
assert_eq!(demo.sheet_entry.text(), "A");
}
#[test]
fn clicking_toast_body_dismisses_without_reverting() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let dot = layout(&settings, &demo).accents[1].center();
click(&mut settings, &mut demo, dot);
assert!(demo.toast.is_open());
let accent_after = settings.accent();
// Klik w treść powiadomienia zamyka je, ale nie cofa akcentu.
let theme = settings.resolve(false);
let text = TextEngine::system();
let r = demo.toast.rect(size(), &theme, &text);
click(
&mut settings,
&mut demo,
Point::new(r.x() + 6.0, r.center().y),
);
assert!(!demo.toast.is_open());
assert_eq!(settings.accent(), accent_after);
}
#[test]
fn choosing_style_from_menu_sets_style() {
// Wskaż katalog ze stylami z repozytorium (przykłady).
let themes =
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../examples/luna/themes");
std::env::set_var("LUNA_THEMES_DIR", themes);
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve_themed(false);
let text = TextEngine::system();
// ☰ → „Styl” otwiera listę stylów.
let burger = layout(&settings, &demo).burger.center();
click(&mut settings, &mut demo, burger);
assert_eq!(demo.popover, Some(Popover::Menu));
let style_item = ui::popover_menu(size(), &theme, &text, &layout(&settings, &demo), &demo)
.map(|(panel, menu)| menu.rows(panel, &theme)[4].rect.center())
.expect("style entry");
click(&mut settings, &mut demo, style_item);
assert_eq!(demo.popover, Some(Popover::Styles));
// Wybór pierwszego stylu (Nord) ustawia go i zamyka popover.
let nord = ui::popover_menu(size(), &theme, &text, &layout(&settings, &demo), &demo)
.map(|(panel, menu)| menu.rows(panel, &theme)[1].rect.center())
.expect("style list");
click(&mut settings, &mut demo, nord);
assert_eq!(settings.style(), Some("nord"));
assert!(demo.popover.is_none());
}
fn key(keysym: u32, text: Option<String>, modifiers: Modifiers) -> Event {
Event::Key(KeyEvent {
keysym,
keycode: 0,
text,
state: ButtonState::Pressed,
modifiers,
repeat: false,
})
}
#[test]
fn ctrl_a_selects_all_and_typing_replaces() {
let mut settings = ThemeSettings::default();
let mut demo = Demo::default();
let theme = settings.resolve(false);
let text = TextEngine::system();
// Klik w pole wyszukiwania → fokus.
let search_rect = layout(&settings, &demo).search;
ui::handle_events(
&mut settings,
&[press(search_rect.center())],
size(),
&theme,
&text,
&mut demo,
);
assert!(demo.search.is_focused());
// Wpisanie „abc”.
for c in ['a', 'b', 'c'] {
let e = key(c as u32, Some(c.to_string()), Modifiers::NONE);
ui::handle_events(&mut settings, &[e], size(), &theme, &text, &mut demo);
}
assert_eq!(demo.search.text(), "abc");
// Ctrl+A zaznacza wszystko…
let ctrl_a = key(
0x61,
None,
Modifiers {
ctrl: true,
..Modifiers::NONE
},
);
ui::handle_events(&mut settings, &[ctrl_a], size(), &theme, &text, &mut demo);
assert_eq!(demo.search.selected_text(), Some("abc"));
// …a wpisanie znaku zastępuje zaznaczenie.
let x = key(0x58, Some("X".to_owned()), Modifiers::NONE);
ui::handle_events(&mut settings, &[x], size(), &theme, &text, &mut demo);
assert_eq!(demo.search.text(), "X");
}