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
38extern crate gpui as open_gpui;
39pub mod affix;
40pub mod alert;
41pub mod anchor;
42pub mod area_chart;
43pub mod autocomplete;
44pub mod avatar;
45pub mod backtop;
46pub mod badge;
47pub mod bar_chart;
48pub mod breadcrumb;
49pub mod button;
50pub mod button_group;
51pub mod calendar;
52pub mod card;
53pub mod carousel;
54pub mod cascader;
55pub mod chart;
56mod chart_frame;
57pub mod chart_scale;
58pub mod chart_shape;
59pub mod checkbox;
60pub mod checkbox_group;
61pub mod code_block;
62pub mod code_editor;
63pub mod col;
64pub mod collapse;
65pub mod color_picker;
66pub mod container;
67pub mod date_picker;
68pub mod date_time_picker;
69pub mod descriptions;
70pub mod dialog;
71pub mod divider;
72pub mod draggable;
73pub mod drawer;
74pub mod dropdown;
75pub mod empty;
76pub mod flex;
77pub mod form;
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
251/// Initialize Liora's recommended application runtime in one call.
252///
253/// This is the high-level setup entry point application authors should use for
254/// normal Liora + GPUI apps. It initializes the core theme/portal state with
255/// [`liora_core::ThemeMode::System`], installs global component services such as
256/// [`MessageManager`], and registers key bindings for interactive components.
257///
258/// Use [`init_liora_with_mode`] when a product needs an explicit Light or Dark
259/// startup mode. The lower-level `liora_core::init_liora(...)` functions remain
260/// available for advanced crate-local setup, but they intentionally initialize
261/// only core/theme state and not component services.
262pub fn init_liora(cx: &mut gpui::App) {
263    init_liora_with_mode(cx, ThemeMode::System);
264}
265
266/// Initialize Liora's recommended application runtime with an explicit theme mode.
267pub 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(&registration),
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(&registration),
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}