1pub mod affix;
39pub mod alert;
40pub mod anchor;
41pub mod area_chart;
42pub mod autocomplete;
43pub mod avatar;
44pub mod backtop;
45pub mod badge;
46pub mod bar_chart;
47pub mod breadcrumb;
48pub mod button;
49pub mod button_group;
50pub mod calendar;
51pub mod card;
52pub mod carousel;
53pub mod cascader;
54pub mod chart;
55mod chart_frame;
56pub mod chart_scale;
57pub mod chart_shape;
58pub mod checkbox;
59pub mod checkbox_group;
60pub mod code_block;
61pub mod code_editor;
62pub mod col;
63pub mod collapse;
64pub mod color_picker;
65pub mod container;
66pub mod date_picker;
67pub mod date_time_picker;
68pub mod descriptions;
69pub mod dialog;
70pub mod divider;
71pub mod draggable;
72pub mod drawer;
73pub mod dropdown;
74pub mod empty;
75pub mod flex;
76pub mod form;
77pub(crate) mod gpui_compat;
78pub mod heat_bar;
79pub mod horizontal_list;
80pub mod image;
81pub mod input;
82pub mod input_number;
83pub mod input_tag;
84pub mod label;
85pub mod layout_helpers;
86pub mod line_chart;
87pub mod link;
88pub mod loading;
89pub mod mention;
90pub mod menu;
91pub mod message;
92pub mod message_box;
93pub mod motion;
94pub mod notification;
95pub mod operation;
96pub mod page_header;
97pub mod pagination;
98pub mod paragraph;
99pub mod pie_chart;
100pub mod popconfirm;
101pub mod popover;
102pub mod preview;
103pub mod progress;
104pub mod qr_code;
105pub mod radio;
106pub mod radio_group;
107pub mod rate;
108pub mod result;
109pub mod row;
110pub mod scrollbar;
111pub mod segment_ratio_bar;
112pub mod segmented;
113pub mod select;
114pub mod selectable_text;
115pub mod signal_meter;
116pub mod skeleton;
117pub mod slider;
118pub mod space;
119pub mod sparkline;
120pub mod splitter;
121pub mod statistic;
122pub mod steps;
123pub mod switch;
124pub mod table;
125pub mod tabs;
126pub mod tag;
127pub mod text;
128pub mod textarea;
129pub mod time_picker;
130pub mod timeline;
131pub mod timer;
132pub mod title;
133pub mod tooltip;
134pub mod tour;
135pub mod transfer;
136pub mod tree;
137pub mod tree_select;
138pub mod upload;
139pub mod virtualized_list;
140pub mod virtualized_table;
141pub mod virtualized_tree;
142pub mod watermark;
143pub mod window_frame;
144
145pub use affix::*;
146pub use alert::*;
147pub use anchor::*;
148pub use area_chart::*;
149pub use autocomplete::*;
150pub use avatar::*;
151pub use backtop::*;
152pub use badge::*;
153pub use bar_chart::*;
154pub use breadcrumb::*;
155pub use button::*;
156pub use button_group::*;
157pub use calendar::*;
158pub use card::*;
159pub use carousel::*;
160pub use cascader::*;
161pub use chart::*;
162pub use chart_scale::*;
163pub use chart_shape::*;
164pub use checkbox::*;
165pub use checkbox_group::*;
166pub use code_block::*;
167pub use code_editor::*;
168pub use col::*;
169pub use collapse::*;
170pub use color_picker::*;
171pub use container::*;
172pub use date_picker::*;
173pub use date_time_picker::*;
174pub use descriptions::*;
175pub use dialog::*;
176pub use divider::*;
177pub use draggable::*;
178pub use drawer::*;
179pub use dropdown::*;
180pub use empty::*;
181pub use flex::*;
182pub use form::*;
183pub use heat_bar::*;
184pub use horizontal_list::*;
185pub use image::*;
186pub use input::*;
187pub use input_number::*;
188pub use input_tag::*;
189pub use label::*;
190pub use line_chart::*;
191pub use link::*;
192pub use liora_core::ThemeMode;
193pub use liora_theme::{ButtonSize, ButtonVariant};
194pub use loading::*;
195pub use mention::*;
196pub use menu::*;
197pub use message::*;
198pub use message_box::*;
199pub use motion::*;
200pub use notification::*;
201pub use operation::*;
202pub use page_header::*;
203pub use pagination::*;
204pub use paragraph::*;
205pub use pie_chart::*;
206pub use popconfirm::*;
207pub use popover::*;
208pub use preview::*;
209pub use progress::*;
210pub use qr_code::*;
211pub use radio::*;
212pub use radio_group::*;
213pub use rate::*;
214pub use result::*;
215pub use row::*;
216pub use scrollbar::*;
217pub use segment_ratio_bar::*;
218pub use segmented::*;
219pub use select::*;
220pub use selectable_text::*;
221pub use signal_meter::*;
222pub use skeleton::*;
223pub use slider::*;
224pub use space::*;
225pub use sparkline::*;
226pub use splitter::*;
227pub use statistic::*;
228pub use steps::*;
229pub use switch::*;
230pub use table::*;
231pub use tabs::*;
232pub use tag::*;
233pub use text::*;
234pub use textarea::*;
235pub use time_picker::*;
236pub use timeline::*;
237pub use timer::*;
238pub use title::*;
239pub use tooltip::*;
240pub use tour::*;
241pub use transfer::*;
242pub use tree::*;
243pub use tree_select::*;
244pub use upload::*;
245pub use virtualized_list::*;
246pub use virtualized_table::*;
247pub use virtualized_tree::*;
248pub use watermark::*;
249pub use window_frame::*;
250
251pub fn init_liora(cx: &mut gpui::App) {
263 init_liora_with_mode(cx, ThemeMode::System);
264}
265
266pub fn init_liora_with_mode(cx: &mut gpui::App, mode: ThemeMode) {
268 liora_core::init_liora_with_mode(cx, mode);
269 MessageManager::init(cx);
270 register_liora_key_bindings(cx);
271}
272
273fn register_liora_key_bindings(cx: &mut gpui::App) {
274 Input::register_key_bindings(cx);
275 CodeBlock::register_key_bindings(cx);
276 CodeEditor::register_key_bindings(cx);
277 Checkbox::register_key_bindings(cx);
278 CheckboxGroup::register_key_bindings(cx);
279 Radio::register_key_bindings(cx);
280 RadioGroup::register_key_bindings(cx);
281 Switch::register_key_bindings(cx);
282 Dialog::register_key_bindings(cx);
283 Drawer::register_key_bindings(cx);
284 Preview::register_key_bindings(cx);
285 Autocomplete::register_key_bindings(cx);
286 Cascader::register_key_bindings(cx);
287 ColorPicker::register_key_bindings(cx);
288 DatePicker::register_key_bindings(cx);
289 DateTimePicker::register_key_bindings(cx);
290 Popover::register_key_bindings(cx);
291 Select::register_key_bindings(cx);
292 TimePicker::register_key_bindings(cx);
293 Text::register_key_bindings(cx);
294 Paragraph::register_key_bindings(cx);
295 Title::register_key_bindings(cx);
296 Tour::register_key_bindings(cx);
297}
298
299#[cfg(test)]
300mod application_init_api_tests {
301 #[test]
302 fn components_crate_exposes_one_line_application_init() {
303 let source = include_str!("lib.rs");
304 assert!(source.contains("pub fn init_liora(cx: &mut gpui::App)"));
305 assert!(
306 source.contains("pub fn init_liora_with_mode(cx: &mut gpui::App, mode: ThemeMode)")
307 );
308 assert!(source.contains("pub use liora_core::ThemeMode"));
309 assert!(source.contains("fn register_liora_key_bindings(cx: &mut gpui::App)"));
310 assert!(source.contains("MessageManager::init(cx)"));
311
312 for component in [
313 "Input",
314 "CodeBlock",
315 "CodeEditor",
316 "Checkbox",
317 "Radio",
318 "RadioGroup",
319 "Switch",
320 "Dialog",
321 "Drawer",
322 "Preview",
323 "Autocomplete",
324 "Cascader",
325 "ColorPicker",
326 "DatePicker",
327 "DateTimePicker",
328 "Popover",
329 "Select",
330 "TimePicker",
331 "Text",
332 "Paragraph",
333 "Title",
334 "Tour",
335 ] {
336 let registration = format!("{component}::register_key_bindings(cx)");
337 assert!(
338 source.contains(®istration),
339 "unified app init should include {registration}"
340 );
341 }
342 }
343}
344
345#[cfg(test)]
346mod motion_coverage_tests {
347 #[test]
348 fn interactive_surfaces_use_liora_motion() {
349 let popup_sources = [
350 include_str!("select.rs"),
351 include_str!("cascader.rs"),
352 include_str!("date_picker.rs"),
353 include_str!("time_picker.rs"),
354 include_str!("date_time_picker.rs"),
355 ];
356
357 for source in popup_sources {
358 assert!(source.contains("panel-motion"));
359 assert!(source.contains("pop_in("));
360 }
361 }
362
363 #[test]
364 fn interactive_state_indicators_use_liora_motion() {
365 let state_sources = [
366 include_str!("backtop.rs"),
367 include_str!("checkbox.rs"),
368 include_str!("radio.rs"),
369 include_str!("collapse.rs"),
370 include_str!("tree.rs"),
371 include_str!("menu.rs"),
372 include_str!("segmented.rs"),
373 include_str!("tabs.rs"),
374 include_str!("rate.rs"),
375 ];
376
377 for source in state_sources {
378 assert!(source.contains("pop_in("));
379 }
380 }
381}
382
383#[cfg(test)]
384mod overlay_escape_coverage_tests {
385 #[test]
386 fn overlay_like_components_expose_configurable_escape_close() {
387 let components = [
388 ("dialog", include_str!("dialog.rs")),
389 ("drawer", include_str!("drawer.rs")),
390 ("message_box", include_str!("message_box.rs")),
391 ("preview", include_str!("preview.rs")),
392 ("popover", include_str!("popover.rs")),
393 ("dropdown", include_str!("dropdown.rs")),
394 ("popconfirm", include_str!("popconfirm.rs")),
395 ("menu", include_str!("menu.rs")),
396 ("select", include_str!("select.rs")),
397 ("cascader", include_str!("cascader.rs")),
398 ("date_picker", include_str!("date_picker.rs")),
399 ("date_time_picker", include_str!("date_time_picker.rs")),
400 ("time_picker", include_str!("time_picker.rs")),
401 ("color_picker", include_str!("color_picker.rs")),
402 ("autocomplete", include_str!("autocomplete.rs")),
403 ("tour", include_str!("tour.rs")),
404 ];
405
406 for (name, source) in components {
407 assert!(
408 source.contains("close_on_escape"),
409 "{name} should expose/forward close_on_escape"
410 );
411 assert!(
412 source.contains("close_on_escape: true")
413 || source.contains("close_on_escape = true")
414 || source.contains(".close_on_escape(")
415 || source.contains("close_on_escape: true,")
416 || name == "message_box",
417 "{name} should default or forward Escape close behavior"
418 );
419 }
420 }
421
422 #[test]
423 fn popover_wrappers_forward_click_outside_close_policy() {
424 for (name, source) in [
425 ("dropdown", include_str!("dropdown.rs")),
426 ("popconfirm", include_str!("popconfirm.rs")),
427 ] {
428 assert!(
429 source.contains("close_on_click_outside: true"),
430 "{name} should default to click-outside close"
431 );
432 assert!(
433 source.contains("pub fn close_on_click_outside("),
434 "{name} should expose close_on_click_outside(...)"
435 );
436 assert!(
437 source.contains(".close_on_click_outside(close_on_click_outside)"),
438 "{name} should forward click-outside policy to Popover"
439 );
440 }
441 }
442
443 #[test]
444 fn preview_exposes_click_outside_close_policy() {
445 let source = include_str!("preview.rs");
446 assert!(source.contains("close_on_click_outside: true"));
447 assert!(source.contains("pub fn close_on_click_outside("));
448 assert!(source.contains(".when(close_on_click_outside"));
449 assert!(source.contains("preview.close_on_click_outside = self.close_on_click_outside"));
450 }
451
452 #[test]
453 fn input_popups_expose_click_outside_close_policy() {
454 for (name, source) in [
455 ("select", include_str!("select.rs")),
456 ("autocomplete", include_str!("autocomplete.rs")),
457 ("cascader", include_str!("cascader.rs")),
458 ("date_picker", include_str!("date_picker.rs")),
459 ("date_time_picker", include_str!("date_time_picker.rs")),
460 ("time_picker", include_str!("time_picker.rs")),
461 ("color_picker", include_str!("color_picker.rs")),
462 ] {
463 assert!(
464 source.contains("close_on_click_outside: true"),
465 "{name} should default to click-outside close"
466 );
467 assert!(
468 source.contains("pub fn close_on_click_outside("),
469 "{name} should expose close_on_click_outside(...)"
470 );
471 assert!(
472 source.contains(".when(close_on_click_outside"),
473 "{name} should bind outside-click close conditionally"
474 );
475 }
476 }
477
478 #[test]
479 fn popup_key_bindings_are_registered_by_unified_component_init() {
480 let source = include_str!("lib.rs");
481 let docs_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
482 .join("../../apps/liora-docs/src/main.rs");
483 let gallery_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
484 .join("../../apps/liora-gallery/src/main.rs");
485
486 if docs_path.exists() {
487 let docs = std::fs::read_to_string(&docs_path).expect("read docs main.rs");
488 assert!(docs.contains("init_liora(cx)"));
489 }
490 if gallery_path.exists() {
491 let gallery = std::fs::read_to_string(&gallery_path).expect("read gallery main.rs");
492 assert!(gallery.contains("init_liora(cx)"));
493 }
494
495 for component in [
496 "Autocomplete",
497 "Cascader",
498 "ColorPicker",
499 "DatePicker",
500 "DateTimePicker",
501 "Dialog",
502 "Drawer",
503 "Popover",
504 "Preview",
505 "Select",
506 "TimePicker",
507 "Tour",
508 ] {
509 let registration = format!("{component}::register_key_bindings(cx)");
510 assert!(
511 source.contains(®istration),
512 "unified component init missing {registration}"
513 );
514 }
515 }
516}
517
518#[cfg(test)]
519mod api_consistency_audit_tests {
520 #[test]
521 fn public_callbacks_keep_value_window_app_shape_except_entity_local_controls() {
522 let value_callbacks = [
523 (
524 "affix",
525 include_str!("affix.rs"),
526 "Fn(bool, &mut Window, &mut App)",
527 ),
528 (
529 "autocomplete",
530 include_str!("autocomplete.rs"),
531 "Fn(AutocompleteItem, &mut Window, &mut App)",
532 ),
533 (
534 "calendar",
535 include_str!("calendar.rs"),
536 "Fn(CalendarDate, &mut Window, &mut App)",
537 ),
538 (
539 "checkbox",
540 include_str!("checkbox.rs"),
541 "Fn(bool, &mut Window, &mut App)",
542 ),
543 (
544 "checkbox_group",
545 include_str!("checkbox_group.rs"),
546 "Fn(Vec<usize>, &mut Window, &mut App)",
547 ),
548 (
549 "color_picker",
550 include_str!("color_picker.rs"),
551 "Fn(SharedString, &mut Window, &mut App)",
552 ),
553 (
554 "input_number",
555 include_str!("input_number.rs"),
556 "Fn(f64, &mut Window, &mut App)",
557 ),
558 (
559 "input_tag",
560 include_str!("input_tag.rs"),
561 "Fn(Vec<SharedString>, &mut Window, &mut App)",
562 ),
563 (
564 "pagination",
565 include_str!("pagination.rs"),
566 "Fn(usize, &mut Window, &mut App)",
567 ),
568 (
569 "radio_group",
570 include_str!("radio_group.rs"),
571 "Fn(usize, &mut Window, &mut App)",
572 ),
573 (
574 "switch",
575 include_str!("switch.rs"),
576 "Fn(bool, &mut Window, &mut App)",
577 ),
578 (
579 "tour",
580 include_str!("tour.rs"),
581 "Fn(usize, &mut Window, &mut App)",
582 ),
583 ];
584
585 for (name, source, signature) in value_callbacks {
586 assert!(
587 source.contains(signature),
588 "{name} should keep callback signature `{signature}`"
589 );
590 }
591
592 let entity_local_callbacks = [
593 (
594 "input",
595 include_str!("input.rs"),
596 "Fn(&str, &mut Context<Self>)",
597 ),
598 (
599 "code_editor",
600 include_str!("code_editor.rs"),
601 "Fn(&str, &mut Context<CodeEditor>)",
602 ),
603 (
604 "horizontal_list",
605 include_str!("horizontal_list.rs"),
606 "Fn(usize, usize, &mut Window, &mut Context<HorizontalList>)",
607 ),
608 ];
609
610 for (name, source, signature) in entity_local_callbacks {
611 assert!(
612 source.contains(signature),
613 "{name} should document its entity-local callback context with `{signature}`"
614 );
615 }
616 }
617
618 #[test]
619 fn state_builders_keep_consistent_boolean_builder_names() {
620 let disabled_sources = [
621 ("button", include_str!("button.rs")),
622 ("checkbox", include_str!("checkbox.rs")),
623 ("radio", include_str!("radio.rs")),
624 ("switch", include_str!("switch.rs")),
625 ("segmented", include_str!("segmented.rs")),
626 ("upload", include_str!("upload.rs")),
627 ("transfer", include_str!("transfer.rs")),
628 ("horizontal_list", include_str!("horizontal_list.rs")),
629 ];
630 for (name, source) in disabled_sources {
631 assert!(
632 source.contains("pub fn disabled("),
633 "{name} should expose disabled(...) as its public boolean state builder"
634 );
635 }
636
637 for (name, source) in [
638 ("dialog", include_str!("dialog.rs")),
639 ("drawer", include_str!("drawer.rs")),
640 ("popover", include_str!("popover.rs")),
641 ("dropdown", include_str!("dropdown.rs")),
642 ("popconfirm", include_str!("popconfirm.rs")),
643 ("tour", include_str!("tour.rs")),
644 ("select", include_str!("select.rs")),
645 ("autocomplete", include_str!("autocomplete.rs")),
646 ("date_picker", include_str!("date_picker.rs")),
647 ("time_picker", include_str!("time_picker.rs")),
648 ] {
649 assert!(
650 source.contains("pub fn close_on_escape("),
651 "{name} should expose close_on_escape(...) for overlay keyboard behavior"
652 );
653 }
654 }
655
656 #[test]
657 fn avoidable_runtime_panics_stay_out_of_hardened_paths() {
658 let hardened_sources = [
659 ("button", include_str!("button.rs")),
660 ("chart", include_str!("chart.rs")),
661 ("date_time_picker", include_str!("date_time_picker.rs")),
662 ("input", include_str!("input.rs")),
663 ("input_number", include_str!("input_number.rs")),
664 ("sparkline", include_str!("sparkline.rs")),
665 ];
666
667 for (name, source) in hardened_sources {
668 let production = source.split("#[cfg(test)]").next().unwrap_or(source);
669 assert!(
670 !production.contains(".unwrap()"),
671 "{name} production path should not use avoidable bare unwrap()"
672 );
673 assert!(
674 !production.contains("expect(\"valid default"),
675 "{name} production path should not panic on constant default values"
676 );
677 }
678
679 let code_block_production = include_str!("code_block.rs")
680 .split("#[cfg(test)]")
681 .next()
682 .unwrap_or(include_str!("code_block.rs"));
683 assert!(
684 !code_block_production.contains(".paint(\n")
685 || !code_block_production.contains(".unwrap();"),
686 "CodeBlock paint paths should not panic on shaped text paint results"
687 );
688 assert!(
689 !code_block_production.contains("lock poisoned")
690 && code_block_production.contains("lock_highlight_cache")
691 && code_block_production.contains("lock_selectable_state_map"),
692 "CodeBlock synchronized caches should recover poisoned locks instead of panicking"
693 );
694
695 for (name, source, helper) in [
696 (
697 "selectable_text",
698 include_str!("selectable_text.rs"),
699 "lock_selection_state_map",
700 ),
701 ("timer", include_str!("timer.rs"), "lock_timer_windows"),
702 ] {
703 let production = source.split("#[cfg(test)]").next().unwrap_or(source);
704 assert!(
705 !production.contains("lock poisoned") && production.contains(helper),
706 "{name} synchronized runtime state should recover poisoned locks instead of panicking"
707 );
708 }
709 }
710}
711
712#[cfg(test)]
713mod visual_theme_consistency_tests {
714 #[test]
715 fn hardened_colored_surfaces_use_theme_inverted_text_token() {
716 for (name, source) in [
717 ("tag", include_str!("tag.rs")),
718 ("progress", include_str!("progress.rs")),
719 ("badge", include_str!("badge.rs")),
720 ("pagination", include_str!("pagination.rs")),
721 ("bar_chart", include_str!("bar_chart.rs")),
722 ("pie_chart", include_str!("pie_chart.rs")),
723 ] {
724 let production = source.split("#[cfg(test)]").next().unwrap_or(source);
725 assert!(
726 production.contains("theme.neutral.inverted"),
727 "{name} should use theme.neutral.inverted for text on colored/dark surfaces"
728 );
729 assert!(
730 !production.contains("gpui::white()"),
731 "{name} production rendering should not hard-code white text"
732 );
733 }
734 }
735
736 #[test]
737 fn gradient_buttons_use_theme_inverted_text_token() {
738 let production = include_str!("button.rs")
739 .split("#[cfg(test)]")
740 .next()
741 .unwrap_or_default();
742
743 assert!(
744 production.contains("let text = theme.neutral.inverted"),
745 "gradient button text should use the semantic inverted text token"
746 );
747 }
748
749 #[test]
750 fn virtualized_components_use_theme_surface_border_and_radius_tokens() {
751 for (name, source) in [
752 ("virtualized_table", include_str!("virtualized_table.rs")),
753 ("virtualized_tree", include_str!("virtualized_tree.rs")),
754 ] {
755 assert!(
756 source.contains("theme.neutral.card"),
757 "{name} should use the themed card surface"
758 );
759 assert!(
760 source.contains("theme.neutral.border"),
761 "{name} should use themed borders"
762 );
763 assert!(
764 source.contains("theme.radius.md"),
765 "{name} should use themed radius tokens"
766 );
767 }
768 }
769
770 #[test]
771 fn modal_masks_and_loading_masks_use_theme_tokens() {
772 for (name, source, token) in [
773 ("dialog", include_str!("dialog.rs"), "theme.neutral.overlay"),
774 ("drawer", include_str!("drawer.rs"), "theme.neutral.overlay"),
775 ("tour", include_str!("tour.rs"), "theme.neutral.overlay"),
776 ("loading", include_str!("loading.rs"), "theme.neutral.mask"),
777 ] {
778 let production = source.split("#[cfg(test)]").next().unwrap_or(source);
779 assert!(production.contains(token), "{name} should use {token}");
780 assert!(
781 !production.contains("0x00000066") && !production.contains("0xFFFFFF99"),
782 "{name} should not hard-code light/dark mask colors"
783 );
784 }
785 }
786
787 #[test]
788 fn code_editor_and_window_frame_use_theme_interaction_tokens() {
789 let code_editor = include_str!("code_editor.rs")
790 .split("#[cfg(test)]")
791 .next()
792 .unwrap_or_default();
793 assert!(code_editor.contains("theme.neutral.border"));
794 assert!(!code_editor.contains("rgb(0xe2e8f0)"));
795
796 let window_frame = include_str!("window_frame.rs")
797 .split("#[cfg(test)]")
798 .next()
799 .unwrap_or_default();
800 assert!(window_frame.contains("theme.danger.base"));
801 assert!(window_frame.contains("theme.neutral.inverted"));
802 assert!(!window_frame.contains("gpui::red()"));
803 assert!(!window_frame.contains("gpui::white()"));
804 }
805}