Skip to main content

liora_components/
lib.rs

1//! Liora's public GPUI component prelude.
2//!
3//! `liora-components` exports the visual and interactive controls used by the
4//! native Gallery and Docs applications: form controls, overlays, navigation,
5//! data display, charts, code blocks/editors, virtualized data views, and small
6//! utility widgets.
7//!
8//! ## Application setup
9//!
10//! A GPUI app should initialize Liora once during application startup:
11//!
12//! ```no_run
13//! use gpui::App;
14//! use liora_components::init_liora;
15//!
16//! fn setup(cx: &mut App) {
17//!     init_liora(cx);
18//! }
19//! ```
20//!
21//! The high-level `liora_components::init_liora(cx)` entry point initializes
22//! Liora core/theme state, global component services, and the app-level key
23//! bindings needed by inputs, text/code selection, overlays, Preview, Tour, and
24//! picker popups. Use `liora_components::init_liora_with_mode(...)` for an
25//! explicit Light or Dark startup mode.
26//!
27//! ## Stateful controls
28//!
29//! Controls with focus, selection, open state, or text value should normally be
30//! stored as `gpui::Entity<T>` fields in a parent view. This preserves state
31//! across GPUI renders. Gallery and Docs are the maintained examples for this pattern.
32//!
33//! ## Architecture boundary
34//!
35//! Liora components render native GPUI element trees. This crate does not require
36//! Tauri, WebView, HTML/CSS, DOM, or a browser runtime.
37
38pub 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 kbd;
85pub mod label;
86pub mod layout_helpers;
87pub mod line_chart;
88pub mod link;
89pub mod loading;
90pub mod mention;
91pub mod menu;
92pub mod message;
93pub mod message_box;
94pub mod motion;
95pub mod notification;
96pub mod operation;
97pub mod otp_input;
98pub mod page_header;
99pub mod pagination;
100pub mod paragraph;
101pub mod pie_chart;
102pub mod popconfirm;
103pub mod popover;
104pub mod preview;
105pub mod progress;
106pub mod qr_code;
107pub mod radio;
108pub mod radio_group;
109pub mod rate;
110pub mod result;
111pub mod row;
112pub mod scrollbar;
113pub mod segment_ratio_bar;
114pub mod segmented;
115pub mod select;
116pub mod selectable_text;
117pub mod signal_meter;
118pub mod skeleton;
119pub mod slider;
120pub mod space;
121pub mod sparkline;
122pub mod spinner;
123pub mod splitter;
124pub mod statistic;
125pub mod steps;
126pub mod switch;
127pub mod table;
128pub mod tabs;
129pub mod tag;
130pub mod text;
131pub mod textarea;
132pub mod time_picker;
133pub mod timeline;
134pub mod timer;
135pub mod title;
136pub mod tooltip;
137pub mod tour;
138pub mod transfer;
139pub mod tree;
140pub mod tree_select;
141pub mod upload;
142pub mod virtualized_list;
143pub mod virtualized_table;
144pub mod virtualized_tree;
145pub mod watermark;
146pub mod window_frame;
147
148pub use affix::*;
149pub use alert::*;
150pub use anchor::*;
151pub use area_chart::*;
152pub use autocomplete::*;
153pub use avatar::*;
154pub use backtop::*;
155pub use badge::*;
156pub use bar_chart::*;
157pub use breadcrumb::*;
158pub use button::*;
159pub use button_group::*;
160pub use calendar::*;
161pub use card::*;
162pub use carousel::*;
163pub use cascader::*;
164pub use chart::*;
165pub use chart_scale::*;
166pub use chart_shape::*;
167pub use checkbox::*;
168pub use checkbox_group::*;
169pub use code_block::*;
170pub use code_editor::*;
171pub use col::*;
172pub use collapse::*;
173pub use color_picker::*;
174pub use container::*;
175pub use date_picker::*;
176pub use date_time_picker::*;
177pub use descriptions::*;
178pub use dialog::*;
179pub use divider::*;
180pub use draggable::*;
181pub use drawer::*;
182pub use dropdown::*;
183pub use empty::*;
184pub use flex::*;
185pub use form::*;
186pub use heat_bar::*;
187pub use horizontal_list::*;
188pub use image::*;
189pub use input::*;
190pub use input_number::*;
191pub use input_tag::*;
192pub use kbd::*;
193pub use label::*;
194pub use line_chart::*;
195pub use link::*;
196pub use liora_core::ThemeMode;
197pub use liora_theme::{ButtonSize, ButtonVariant};
198pub use loading::*;
199pub use mention::*;
200pub use menu::*;
201pub use message::*;
202pub use message_box::*;
203pub use motion::*;
204pub use notification::*;
205pub use operation::*;
206pub use otp_input::*;
207pub use page_header::*;
208pub use pagination::*;
209pub use paragraph::*;
210pub use pie_chart::*;
211pub use popconfirm::*;
212pub use popover::*;
213pub use preview::*;
214pub use progress::*;
215pub use qr_code::*;
216pub use radio::*;
217pub use radio_group::*;
218pub use rate::*;
219pub use result::*;
220pub use row::*;
221pub use scrollbar::*;
222pub use segment_ratio_bar::*;
223pub use segmented::*;
224pub use select::*;
225pub use selectable_text::*;
226pub use signal_meter::*;
227pub use skeleton::*;
228pub use slider::*;
229pub use space::*;
230pub use sparkline::*;
231pub use spinner::*;
232pub use splitter::*;
233pub use statistic::*;
234pub use steps::*;
235pub use switch::*;
236pub use table::*;
237pub use tabs::*;
238pub use tag::*;
239pub use text::*;
240pub use textarea::*;
241pub use time_picker::*;
242pub use timeline::*;
243pub use timer::*;
244pub use title::*;
245pub use tooltip::*;
246pub use tour::*;
247pub use transfer::*;
248pub use tree::*;
249pub use tree_select::*;
250pub use upload::*;
251pub use virtualized_list::*;
252pub use virtualized_table::*;
253pub use virtualized_tree::*;
254pub use watermark::*;
255pub use window_frame::*;
256
257/// Initialize Liora's recommended application runtime in one call.
258///
259/// This is the high-level setup entry point application authors should use for
260/// normal Liora + GPUI apps. It initializes the core theme/portal state with
261/// [`liora_core::ThemeMode::System`], installs global component services such as
262/// [`MessageManager`], and registers key bindings for interactive components.
263///
264/// Use [`init_liora_with_mode`] when a product needs an explicit Light or Dark
265/// startup mode. The lower-level `liora_core::init_liora(...)` functions remain
266/// available for advanced crate-local setup, but they intentionally initialize
267/// only core/theme state and not component services.
268pub fn init_liora(cx: &mut gpui::App) {
269    init_liora_with_mode(cx, ThemeMode::System);
270}
271
272/// Initialize Liora's recommended application runtime with an explicit theme mode.
273pub fn init_liora_with_mode(cx: &mut gpui::App, mode: ThemeMode) {
274    liora_core::init_liora_with_mode(cx, mode);
275    MessageManager::init(cx);
276    register_liora_key_bindings(cx);
277}
278
279fn register_liora_key_bindings(cx: &mut gpui::App) {
280    Input::register_key_bindings(cx);
281    CodeBlock::register_key_bindings(cx);
282    CodeEditor::register_key_bindings(cx);
283    Checkbox::register_key_bindings(cx);
284    CheckboxGroup::register_key_bindings(cx);
285    Radio::register_key_bindings(cx);
286    RadioGroup::register_key_bindings(cx);
287    Switch::register_key_bindings(cx);
288    Dialog::register_key_bindings(cx);
289    Drawer::register_key_bindings(cx);
290    Preview::register_key_bindings(cx);
291    Autocomplete::register_key_bindings(cx);
292    Cascader::register_key_bindings(cx);
293    ColorPicker::register_key_bindings(cx);
294    DatePicker::register_key_bindings(cx);
295    DateTimePicker::register_key_bindings(cx);
296    Popover::register_key_bindings(cx);
297    Select::register_key_bindings(cx);
298    TimePicker::register_key_bindings(cx);
299    Text::register_key_bindings(cx);
300    Paragraph::register_key_bindings(cx);
301    Title::register_key_bindings(cx);
302    Tour::register_key_bindings(cx);
303}
304
305#[cfg(test)]
306mod application_init_api_tests {
307    #[test]
308    fn component_modules_have_english_module_documentation() {
309        let modules = [
310            ("affix.rs", include_str!("affix.rs")),
311            ("alert.rs", include_str!("alert.rs")),
312            ("anchor.rs", include_str!("anchor.rs")),
313            ("area_chart.rs", include_str!("area_chart.rs")),
314            ("autocomplete.rs", include_str!("autocomplete.rs")),
315            ("avatar.rs", include_str!("avatar.rs")),
316            ("backtop.rs", include_str!("backtop.rs")),
317            ("badge.rs", include_str!("badge.rs")),
318            ("bar_chart.rs", include_str!("bar_chart.rs")),
319            ("breadcrumb.rs", include_str!("breadcrumb.rs")),
320            ("button.rs", include_str!("button.rs")),
321            ("button_group.rs", include_str!("button_group.rs")),
322            ("calendar.rs", include_str!("calendar.rs")),
323            ("card.rs", include_str!("card.rs")),
324            ("carousel.rs", include_str!("carousel.rs")),
325            ("cascader.rs", include_str!("cascader.rs")),
326            ("chart.rs", include_str!("chart.rs")),
327            ("chart_frame.rs", include_str!("chart_frame.rs")),
328            ("chart_scale.rs", include_str!("chart_scale.rs")),
329            ("chart_shape.rs", include_str!("chart_shape.rs")),
330            ("checkbox.rs", include_str!("checkbox.rs")),
331            ("checkbox_group.rs", include_str!("checkbox_group.rs")),
332            ("code_block.rs", include_str!("code_block.rs")),
333            ("code_editor.rs", include_str!("code_editor.rs")),
334            ("col.rs", include_str!("col.rs")),
335            ("collapse.rs", include_str!("collapse.rs")),
336            ("color_picker.rs", include_str!("color_picker.rs")),
337            ("container.rs", include_str!("container.rs")),
338            ("date_picker.rs", include_str!("date_picker.rs")),
339            ("date_time_picker.rs", include_str!("date_time_picker.rs")),
340            ("descriptions.rs", include_str!("descriptions.rs")),
341            ("dialog.rs", include_str!("dialog.rs")),
342            ("divider.rs", include_str!("divider.rs")),
343            ("draggable.rs", include_str!("draggable.rs")),
344            ("drawer.rs", include_str!("drawer.rs")),
345            ("dropdown.rs", include_str!("dropdown.rs")),
346            ("empty.rs", include_str!("empty.rs")),
347            ("flex.rs", include_str!("flex.rs")),
348            ("form.rs", include_str!("form.rs")),
349            ("gpui_compat.rs", include_str!("gpui_compat.rs")),
350            ("heat_bar.rs", include_str!("heat_bar.rs")),
351            ("horizontal_list.rs", include_str!("horizontal_list.rs")),
352            ("image.rs", include_str!("image.rs")),
353            ("input.rs", include_str!("input.rs")),
354            ("input_number.rs", include_str!("input_number.rs")),
355            ("input_tag.rs", include_str!("input_tag.rs")),
356            ("label.rs", include_str!("label.rs")),
357            ("kbd.rs", include_str!("kbd.rs")),
358            ("layout_helpers.rs", include_str!("layout_helpers.rs")),
359            ("line_chart.rs", include_str!("line_chart.rs")),
360            ("link.rs", include_str!("link.rs")),
361            ("loading.rs", include_str!("loading.rs")),
362            ("mention.rs", include_str!("mention.rs")),
363            ("menu.rs", include_str!("menu.rs")),
364            ("message.rs", include_str!("message.rs")),
365            ("message_box.rs", include_str!("message_box.rs")),
366            ("motion.rs", include_str!("motion.rs")),
367            ("notification.rs", include_str!("notification.rs")),
368            ("operation.rs", include_str!("operation.rs")),
369            ("otp_input.rs", include_str!("otp_input.rs")),
370            ("page_header.rs", include_str!("page_header.rs")),
371            ("pagination.rs", include_str!("pagination.rs")),
372            ("paragraph.rs", include_str!("paragraph.rs")),
373            ("pie_chart.rs", include_str!("pie_chart.rs")),
374            ("popconfirm.rs", include_str!("popconfirm.rs")),
375            ("popover.rs", include_str!("popover.rs")),
376            ("preview.rs", include_str!("preview.rs")),
377            ("progress.rs", include_str!("progress.rs")),
378            ("qr_code.rs", include_str!("qr_code.rs")),
379            ("radio.rs", include_str!("radio.rs")),
380            ("radio_group.rs", include_str!("radio_group.rs")),
381            ("rate.rs", include_str!("rate.rs")),
382            ("result.rs", include_str!("result.rs")),
383            ("row.rs", include_str!("row.rs")),
384            ("scrollbar.rs", include_str!("scrollbar.rs")),
385            ("segment_ratio_bar.rs", include_str!("segment_ratio_bar.rs")),
386            ("segmented.rs", include_str!("segmented.rs")),
387            ("select.rs", include_str!("select.rs")),
388            ("selectable_text.rs", include_str!("selectable_text.rs")),
389            ("signal_meter.rs", include_str!("signal_meter.rs")),
390            ("skeleton.rs", include_str!("skeleton.rs")),
391            ("slider.rs", include_str!("slider.rs")),
392            ("space.rs", include_str!("space.rs")),
393            ("spinner.rs", include_str!("spinner.rs")),
394            ("sparkline.rs", include_str!("sparkline.rs")),
395            ("splitter.rs", include_str!("splitter.rs")),
396            ("statistic.rs", include_str!("statistic.rs")),
397            ("steps.rs", include_str!("steps.rs")),
398            ("switch.rs", include_str!("switch.rs")),
399            ("table.rs", include_str!("table.rs")),
400            ("tabs.rs", include_str!("tabs.rs")),
401            ("tag.rs", include_str!("tag.rs")),
402            ("text.rs", include_str!("text.rs")),
403            ("textarea.rs", include_str!("textarea.rs")),
404            ("time_picker.rs", include_str!("time_picker.rs")),
405            ("timeline.rs", include_str!("timeline.rs")),
406            ("timer.rs", include_str!("timer.rs")),
407            ("title.rs", include_str!("title.rs")),
408            ("tooltip.rs", include_str!("tooltip.rs")),
409            ("tour.rs", include_str!("tour.rs")),
410            ("transfer.rs", include_str!("transfer.rs")),
411            ("tree.rs", include_str!("tree.rs")),
412            ("tree_select.rs", include_str!("tree_select.rs")),
413            ("upload.rs", include_str!("upload.rs")),
414            ("virtualized_list.rs", include_str!("virtualized_list.rs")),
415            ("virtualized_table.rs", include_str!("virtualized_table.rs")),
416            ("virtualized_tree.rs", include_str!("virtualized_tree.rs")),
417            ("watermark.rs", include_str!("watermark.rs")),
418            ("window_frame.rs", include_str!("window_frame.rs")),
419        ];
420
421        for (path, source) in modules {
422            assert!(
423                source.starts_with("//!"),
424                "{path} must start with module docs"
425            );
426            assert!(
427                source.contains("## Usage model"),
428                "{path} must document usage model"
429            );
430            assert!(
431                source.contains("## Design contract"),
432                "{path} must document design contract"
433            );
434            assert!(
435                !source.contains("代目"),
436                "{path} docs must be English, not draft text"
437            );
438            for forbidden in [
439                "Configuration and state type for",
440                "value used by this public",
441                "Sets or returns",
442                "Selects the value variant for this API",
443                "variant for this API",
444                "mode for this API",
445                "Represents the ",
446                "option for the ",
447                "Configures or computes",
448                "associated with this public",
449                "Creates a new value with the required baseline configuration",
450                "Creates a default",
451                "initial public state",
452                "documented default layout",
453                "for this data model",
454                "for fluent component construction",
455                "Returns a copy configured with the supplied",
456                "Applies the value setting",
457                "setting and returns the updated builder",
458                "Current value represented by this option or component state",
459                "Human-readable label shown in the component UI",
460                "Configured width used during layout",
461                "Configured height used during layout",
462                "Stable identifier used to connect rendered UI",
463            ] {
464                assert!(
465                    !source.contains(forbidden),
466                    "{path} rustdoc should be specific, not template phrase: {forbidden}"
467                );
468            }
469        }
470    }
471
472    #[test]
473    fn components_crate_exposes_one_line_application_init() {
474        let source = include_str!("lib.rs");
475        assert!(source.contains("pub fn init_liora(cx: &mut gpui::App)"));
476        assert!(
477            source.contains("pub fn init_liora_with_mode(cx: &mut gpui::App, mode: ThemeMode)")
478        );
479        assert!(source.contains("pub use liora_core::ThemeMode"));
480        assert!(source.contains("fn register_liora_key_bindings(cx: &mut gpui::App)"));
481        assert!(source.contains("MessageManager::init(cx)"));
482
483        for component in [
484            "Input",
485            "CodeBlock",
486            "CodeEditor",
487            "Checkbox",
488            "Radio",
489            "RadioGroup",
490            "Switch",
491            "Dialog",
492            "Drawer",
493            "Preview",
494            "Autocomplete",
495            "Cascader",
496            "ColorPicker",
497            "DatePicker",
498            "DateTimePicker",
499            "Popover",
500            "Select",
501            "TimePicker",
502            "Text",
503            "Paragraph",
504            "Title",
505            "Tour",
506        ] {
507            let registration = format!("{component}::register_key_bindings(cx)");
508            assert!(
509                source.contains(&registration),
510                "unified app init should include {registration}"
511            );
512        }
513    }
514}
515
516#[cfg(test)]
517mod motion_coverage_tests {
518    #[test]
519    fn interactive_surfaces_use_liora_motion() {
520        let popup_sources = [
521            include_str!("select.rs"),
522            include_str!("cascader.rs"),
523            include_str!("date_picker.rs"),
524            include_str!("time_picker.rs"),
525            include_str!("date_time_picker.rs"),
526        ];
527
528        for source in popup_sources {
529            assert!(source.contains("panel-motion"));
530            assert!(source.contains("pop_in("));
531        }
532    }
533
534    #[test]
535    fn interactive_state_indicators_use_liora_motion() {
536        let state_sources = [
537            include_str!("backtop.rs"),
538            include_str!("checkbox.rs"),
539            include_str!("radio.rs"),
540            include_str!("collapse.rs"),
541            include_str!("tree.rs"),
542            include_str!("menu.rs"),
543            include_str!("segmented.rs"),
544            include_str!("tabs.rs"),
545            include_str!("rate.rs"),
546        ];
547
548        for source in state_sources {
549            assert!(source.contains("pop_in("));
550        }
551    }
552}
553
554#[cfg(test)]
555mod overlay_escape_coverage_tests {
556    #[test]
557    fn overlay_like_components_expose_configurable_escape_close() {
558        let components = [
559            ("dialog", include_str!("dialog.rs")),
560            ("drawer", include_str!("drawer.rs")),
561            ("message_box", include_str!("message_box.rs")),
562            ("preview", include_str!("preview.rs")),
563            ("popover", include_str!("popover.rs")),
564            ("dropdown", include_str!("dropdown.rs")),
565            ("popconfirm", include_str!("popconfirm.rs")),
566            ("menu", include_str!("menu.rs")),
567            ("select", include_str!("select.rs")),
568            ("cascader", include_str!("cascader.rs")),
569            ("date_picker", include_str!("date_picker.rs")),
570            ("date_time_picker", include_str!("date_time_picker.rs")),
571            ("time_picker", include_str!("time_picker.rs")),
572            ("color_picker", include_str!("color_picker.rs")),
573            ("autocomplete", include_str!("autocomplete.rs")),
574            ("tour", include_str!("tour.rs")),
575        ];
576
577        for (name, source) in components {
578            assert!(
579                source.contains("close_on_escape"),
580                "{name} should expose/forward close_on_escape"
581            );
582            assert!(
583                source.contains("close_on_escape: true")
584                    || source.contains("close_on_escape = true")
585                    || source.contains(".close_on_escape(")
586                    || source.contains("close_on_escape: true,")
587                    || name == "message_box",
588                "{name} should default or forward Escape close behavior"
589            );
590        }
591    }
592
593    #[test]
594    fn popover_wrappers_forward_click_outside_close_policy() {
595        for (name, source) in [
596            ("dropdown", include_str!("dropdown.rs")),
597            ("popconfirm", include_str!("popconfirm.rs")),
598        ] {
599            assert!(
600                source.contains("close_on_click_outside: true"),
601                "{name} should default to click-outside close"
602            );
603            assert!(
604                source.contains("pub fn close_on_click_outside("),
605                "{name} should expose close_on_click_outside(...)"
606            );
607            assert!(
608                source.contains(".close_on_click_outside(close_on_click_outside)"),
609                "{name} should forward click-outside policy to Popover"
610            );
611        }
612    }
613
614    #[test]
615    fn preview_exposes_click_outside_close_policy() {
616        let source = include_str!("preview.rs");
617        assert!(source.contains("close_on_click_outside: true"));
618        assert!(source.contains("pub fn close_on_click_outside("));
619        assert!(source.contains(".when(close_on_click_outside"));
620        assert!(source.contains("preview.close_on_click_outside = self.close_on_click_outside"));
621    }
622
623    #[test]
624    fn input_popups_expose_click_outside_close_policy() {
625        for (name, source) in [
626            ("select", include_str!("select.rs")),
627            ("autocomplete", include_str!("autocomplete.rs")),
628            ("cascader", include_str!("cascader.rs")),
629            ("date_picker", include_str!("date_picker.rs")),
630            ("date_time_picker", include_str!("date_time_picker.rs")),
631            ("time_picker", include_str!("time_picker.rs")),
632            ("color_picker", include_str!("color_picker.rs")),
633        ] {
634            assert!(
635                source.contains("close_on_click_outside: true"),
636                "{name} should default to click-outside close"
637            );
638            assert!(
639                source.contains("pub fn close_on_click_outside("),
640                "{name} should expose close_on_click_outside(...)"
641            );
642            assert!(
643                source.contains(".when(close_on_click_outside"),
644                "{name} should bind outside-click close conditionally"
645            );
646        }
647    }
648
649    #[test]
650    fn popup_key_bindings_are_registered_by_unified_component_init() {
651        let source = include_str!("lib.rs");
652        let docs_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
653            .join("../../apps/liora-docs/src/main.rs");
654        let gallery_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
655            .join("../../apps/liora-gallery/src/main.rs");
656
657        if docs_path.exists() {
658            let docs = std::fs::read_to_string(&docs_path).expect("read docs main.rs");
659            assert!(docs.contains("init_liora(cx)"));
660        }
661        if gallery_path.exists() {
662            let gallery = std::fs::read_to_string(&gallery_path).expect("read gallery main.rs");
663            assert!(gallery.contains("init_liora(cx)"));
664        }
665
666        for component in [
667            "Autocomplete",
668            "Cascader",
669            "ColorPicker",
670            "DatePicker",
671            "DateTimePicker",
672            "Dialog",
673            "Drawer",
674            "Popover",
675            "Preview",
676            "Select",
677            "TimePicker",
678            "Tour",
679        ] {
680            let registration = format!("{component}::register_key_bindings(cx)");
681            assert!(
682                source.contains(&registration),
683                "unified component init missing {registration}"
684            );
685        }
686    }
687}
688
689#[cfg(test)]
690mod api_consistency_audit_tests {
691    #[test]
692    fn public_callbacks_keep_value_window_app_shape_except_entity_local_controls() {
693        let value_callbacks = [
694            (
695                "affix",
696                include_str!("affix.rs"),
697                "Fn(bool, &mut Window, &mut App)",
698            ),
699            (
700                "autocomplete",
701                include_str!("autocomplete.rs"),
702                "Fn(AutocompleteItem, &mut Window, &mut App)",
703            ),
704            (
705                "calendar",
706                include_str!("calendar.rs"),
707                "Fn(CalendarDate, &mut Window, &mut App)",
708            ),
709            (
710                "checkbox",
711                include_str!("checkbox.rs"),
712                "Fn(bool, &mut Window, &mut App)",
713            ),
714            (
715                "checkbox_group",
716                include_str!("checkbox_group.rs"),
717                "Fn(Vec<usize>, &mut Window, &mut App)",
718            ),
719            (
720                "color_picker",
721                include_str!("color_picker.rs"),
722                "Fn(SharedString, &mut Window, &mut App)",
723            ),
724            (
725                "input_number",
726                include_str!("input_number.rs"),
727                "Fn(f64, &mut Window, &mut App)",
728            ),
729            (
730                "input_tag",
731                include_str!("input_tag.rs"),
732                "Fn(Vec<SharedString>, &mut Window, &mut App)",
733            ),
734            (
735                "pagination",
736                include_str!("pagination.rs"),
737                "Fn(usize, &mut Window, &mut App)",
738            ),
739            (
740                "radio_group",
741                include_str!("radio_group.rs"),
742                "Fn(usize, &mut Window, &mut App)",
743            ),
744            (
745                "switch",
746                include_str!("switch.rs"),
747                "Fn(bool, &mut Window, &mut App)",
748            ),
749            (
750                "tour",
751                include_str!("tour.rs"),
752                "Fn(usize, &mut Window, &mut App)",
753            ),
754        ];
755
756        for (name, source, signature) in value_callbacks {
757            assert!(
758                source.contains(signature),
759                "{name} should keep callback signature `{signature}`"
760            );
761        }
762
763        let entity_local_callbacks = [
764            (
765                "input",
766                include_str!("input.rs"),
767                "Fn(&str, &mut Context<Self>)",
768            ),
769            (
770                "code_editor",
771                include_str!("code_editor.rs"),
772                "Fn(&str, &mut Context<CodeEditor>)",
773            ),
774            (
775                "horizontal_list",
776                include_str!("horizontal_list.rs"),
777                "Fn(usize, usize, &mut Window, &mut Context<HorizontalList>)",
778            ),
779        ];
780
781        for (name, source, signature) in entity_local_callbacks {
782            assert!(
783                source.contains(signature),
784                "{name} should document its entity-local callback context with `{signature}`"
785            );
786        }
787    }
788
789    #[test]
790    fn state_builders_keep_consistent_boolean_builder_names() {
791        let disabled_sources = [
792            ("button", include_str!("button.rs")),
793            ("checkbox", include_str!("checkbox.rs")),
794            ("radio", include_str!("radio.rs")),
795            ("switch", include_str!("switch.rs")),
796            ("segmented", include_str!("segmented.rs")),
797            ("upload", include_str!("upload.rs")),
798            ("transfer", include_str!("transfer.rs")),
799            ("horizontal_list", include_str!("horizontal_list.rs")),
800        ];
801        for (name, source) in disabled_sources {
802            assert!(
803                source.contains("pub fn disabled("),
804                "{name} should expose disabled(...) as its public boolean state builder"
805            );
806        }
807
808        for (name, source) in [
809            ("dialog", include_str!("dialog.rs")),
810            ("drawer", include_str!("drawer.rs")),
811            ("popover", include_str!("popover.rs")),
812            ("dropdown", include_str!("dropdown.rs")),
813            ("popconfirm", include_str!("popconfirm.rs")),
814            ("tour", include_str!("tour.rs")),
815            ("select", include_str!("select.rs")),
816            ("autocomplete", include_str!("autocomplete.rs")),
817            ("date_picker", include_str!("date_picker.rs")),
818            ("time_picker", include_str!("time_picker.rs")),
819        ] {
820            assert!(
821                source.contains("pub fn close_on_escape("),
822                "{name} should expose close_on_escape(...) for overlay keyboard behavior"
823            );
824        }
825    }
826
827    #[test]
828    fn avoidable_runtime_panics_stay_out_of_hardened_paths() {
829        let hardened_sources = [
830            ("button", include_str!("button.rs")),
831            ("chart", include_str!("chart.rs")),
832            ("date_time_picker", include_str!("date_time_picker.rs")),
833            ("input", include_str!("input.rs")),
834            ("input_number", include_str!("input_number.rs")),
835            ("sparkline", include_str!("sparkline.rs")),
836        ];
837
838        for (name, source) in hardened_sources {
839            let production = source.split("#[cfg(test)]").next().unwrap_or(source);
840            assert!(
841                !production.contains(".unwrap()"),
842                "{name} production path should not use avoidable bare unwrap()"
843            );
844            assert!(
845                !production.contains("expect(\"valid default"),
846                "{name} production path should not panic on constant default values"
847            );
848        }
849
850        let code_block_production = include_str!("code_block.rs")
851            .split("#[cfg(test)]")
852            .next()
853            .unwrap_or(include_str!("code_block.rs"));
854        assert!(
855            !code_block_production.contains(".paint(\n")
856                || !code_block_production.contains(".unwrap();"),
857            "CodeBlock paint paths should not panic on shaped text paint results"
858        );
859        assert!(
860            !code_block_production.contains("lock poisoned")
861                && code_block_production.contains("lock_highlight_cache")
862                && code_block_production.contains("lock_selectable_state_map"),
863            "CodeBlock synchronized caches should recover poisoned locks instead of panicking"
864        );
865
866        for (name, source, helper) in [
867            (
868                "selectable_text",
869                include_str!("selectable_text.rs"),
870                "lock_selection_state_map",
871            ),
872            ("timer", include_str!("timer.rs"), "lock_timer_windows"),
873        ] {
874            let production = source.split("#[cfg(test)]").next().unwrap_or(source);
875            assert!(
876                !production.contains("lock poisoned") && production.contains(helper),
877                "{name} synchronized runtime state should recover poisoned locks instead of panicking"
878            );
879        }
880    }
881}
882
883#[cfg(test)]
884mod visual_theme_consistency_tests {
885    #[test]
886    fn hardened_colored_surfaces_use_theme_inverted_text_token() {
887        for (name, source) in [
888            ("tag", include_str!("tag.rs")),
889            ("progress", include_str!("progress.rs")),
890            ("badge", include_str!("badge.rs")),
891            ("pagination", include_str!("pagination.rs")),
892            ("bar_chart", include_str!("bar_chart.rs")),
893            ("pie_chart", include_str!("pie_chart.rs")),
894        ] {
895            let production = source.split("#[cfg(test)]").next().unwrap_or(source);
896            assert!(
897                production.contains("theme.neutral.inverted"),
898                "{name} should use theme.neutral.inverted for text on colored/dark surfaces"
899            );
900            assert!(
901                !production.contains("gpui::white()"),
902                "{name} production rendering should not hard-code white text"
903            );
904        }
905    }
906
907    #[test]
908    fn gradient_buttons_use_theme_inverted_text_token() {
909        let production = include_str!("button.rs")
910            .split("#[cfg(test)]")
911            .next()
912            .unwrap_or_default();
913
914        assert!(
915            production.contains("let text = theme.neutral.inverted"),
916            "gradient button text should use the semantic inverted text token"
917        );
918    }
919
920    #[test]
921    fn virtualized_components_use_theme_surface_border_and_radius_tokens() {
922        for (name, source) in [
923            ("virtualized_table", include_str!("virtualized_table.rs")),
924            ("virtualized_tree", include_str!("virtualized_tree.rs")),
925        ] {
926            assert!(
927                source.contains("theme.neutral.card"),
928                "{name} should use the themed card surface"
929            );
930            assert!(
931                source.contains("theme.neutral.border"),
932                "{name} should use themed borders"
933            );
934            assert!(
935                source.contains("theme.radius.md"),
936                "{name} should use themed radius tokens"
937            );
938        }
939    }
940
941    #[test]
942    fn modal_masks_and_loading_masks_use_theme_tokens() {
943        for (name, source, token) in [
944            ("dialog", include_str!("dialog.rs"), "theme.neutral.overlay"),
945            ("drawer", include_str!("drawer.rs"), "theme.neutral.overlay"),
946            ("tour", include_str!("tour.rs"), "theme.neutral.overlay"),
947            ("loading", include_str!("loading.rs"), "theme.neutral.mask"),
948        ] {
949            let production = source.split("#[cfg(test)]").next().unwrap_or(source);
950            assert!(production.contains(token), "{name} should use {token}");
951            assert!(
952                !production.contains("0x00000066") && !production.contains("0xFFFFFF99"),
953                "{name} should not hard-code light/dark mask colors"
954            );
955        }
956    }
957
958    #[test]
959    fn code_editor_and_window_frame_use_theme_interaction_tokens() {
960        let code_editor = include_str!("code_editor.rs")
961            .split("#[cfg(test)]")
962            .next()
963            .unwrap_or_default();
964        assert!(code_editor.contains("theme.neutral.border"));
965        assert!(!code_editor.contains("rgb(0xe2e8f0)"));
966
967        let window_frame = include_str!("window_frame.rs")
968            .split("#[cfg(test)]")
969            .next()
970            .unwrap_or_default();
971        assert!(window_frame.contains("theme.danger.base"));
972        assert!(window_frame.contains("theme.neutral.inverted"));
973        assert!(!window_frame.contains("gpui::red()"));
974        assert!(!window_frame.contains("gpui::white()"));
975    }
976}