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 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
257pub fn init_liora(cx: &mut gpui::App) {
269 init_liora_with_mode(cx, ThemeMode::System);
270}
271
272pub 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(®istration),
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(®istration),
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}