ferro_json_ui/runtime/
mod.rs1mod dismissibles;
9mod dropdowns;
10mod form_guards;
11mod hero_lazy;
12mod kanban;
13mod modals;
14mod notifications;
15mod product_tiles;
16mod scroll_preserve;
17mod sidebar;
18mod sse;
19mod tabs;
20mod toasts;
21
22use std::sync::LazyLock;
23
24pub static FERRO_RUNTIME_JS: LazyLock<String> = LazyLock::new(|| {
28 let mut s = String::with_capacity(8 * 1024);
29 s.push_str("(function() {\n 'use strict';\n");
30 s.push_str(sse::SOURCE);
31 s.push_str(tabs::SOURCE);
32 s.push_str(toasts::SOURCE);
33 s.push_str(dismissibles::SOURCE);
34 s.push_str(notifications::SOURCE);
35 s.push_str(dropdowns::SOURCE);
36 s.push_str(modals::SOURCE);
37 s.push_str(sidebar::SOURCE);
38 s.push_str(form_guards::SOURCE);
39 s.push_str(product_tiles::SOURCE);
40 s.push_str(kanban::SOURCE);
41 s.push_str(scroll_preserve::SOURCE);
42 s.push_str(hero_lazy::SOURCE);
43 s.push_str(
44 "\n function ferroRuntime() {\n\
45 \x20 setupScrollPreserve();\n\
46 \x20 setupSSE();\n\
47 \x20 setupTabs();\n\
48 \x20 setupDismissibles();\n\
49 \x20 setupNotifications();\n\
50 \x20 setupDropdowns();\n\
51 \x20 setupKanban();\n\
52 \x20 setupSidebar();\n\
53 \x20 setupFormGuards();\n\
54 \x20 setupProductTiles();\n\
55 \x20 setupModals();\n\
56 \x20 setupToasts();\n\
57 \x20 setupLazyHeroes();\n\
58 \x20 }\n\
59 \x20 document.addEventListener('DOMContentLoaded', ferroRuntime);\n\
60 })();\n",
61 );
62 s
63});
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn variant_classes_use_semantic_tokens() {
71 assert!(FERRO_RUNTIME_JS.contains("bg-primary"));
72 assert!(FERRO_RUNTIME_JS.contains("bg-success"));
73 assert!(FERRO_RUNTIME_JS.contains("bg-warning"));
74 assert!(FERRO_RUNTIME_JS.contains("bg-destructive"));
75 assert!(!FERRO_RUNTIME_JS.contains("bg-blue-500"));
76 assert!(!FERRO_RUNTIME_JS.contains("bg-green-500"));
77 assert!(!FERRO_RUNTIME_JS.contains("bg-yellow-500"));
78 assert!(!FERRO_RUNTIME_JS.contains("bg-red-500"));
79 }
80
81 #[test]
82 fn tab_switcher_uses_semantic_tokens() {
83 assert!(FERRO_RUNTIME_JS.contains("border-primary"));
84 assert!(FERRO_RUNTIME_JS.contains("text-primary"));
85 assert!(FERRO_RUNTIME_JS.contains("text-text-muted"));
86 assert!(!FERRO_RUNTIME_JS.contains("border-blue-600"));
87 assert!(!FERRO_RUNTIME_JS.contains("text-blue-600"));
88 assert!(!FERRO_RUNTIME_JS.contains("text-gray-500"));
89 }
90
91 #[test]
92 fn toast_uses_semantic_text_color() {
93 assert!(FERRO_RUNTIME_JS.contains("text-primary-foreground"));
94 assert!(!FERRO_RUNTIME_JS.contains("text-white"));
95 }
96
97 #[test]
102 fn test_runtime_contains_popover_dropdown_wiring() {
103 assert!(FERRO_RUNTIME_JS.contains("data-popover-menu"));
104 assert!(FERRO_RUNTIME_JS.contains(":popover-open"));
105 assert!(FERRO_RUNTIME_JS.contains("hidePopover"));
106 assert!(FERRO_RUNTIME_JS.contains("positionUnderTrigger"));
107 assert!(FERRO_RUNTIME_JS.contains("getBoundingClientRect"));
108 }
109
110 #[test]
111 fn test_runtime_contains_modal_wiring() {
112 assert!(FERRO_RUNTIME_JS.contains("setupModals"));
113 assert!(FERRO_RUNTIME_JS.contains("data-modal-open"));
114 assert!(FERRO_RUNTIME_JS.contains("showModal"));
115 assert!(FERRO_RUNTIME_JS.contains("data-modal-close"));
116 }
117
118 #[test]
119 fn test_runtime_contains_toast_from_url() {
120 assert!(FERRO_RUNTIME_JS.contains("initToastFromUrl"));
121 assert!(FERRO_RUNTIME_JS.contains("URLSearchParams"));
122 assert!(FERRO_RUNTIME_JS.contains("history.replaceState"));
123 }
124
125 #[test]
126 fn runtime_contains_init_tab_from_url() {
127 assert!(
128 FERRO_RUNTIME_JS.contains("initTabFromUrl"),
129 "FERRO_RUNTIME_JS must include initTabFromUrl for F3 — URL-driven tab init"
130 );
131 assert!(
132 FERRO_RUNTIME_JS.contains("URLSearchParams"),
133 "FERRO_RUNTIME_JS must use URLSearchParams to parse ?tab= for initTabFromUrl"
134 );
135 }
136
137 #[test]
138 fn bundle_contains_dispatcher() {
139 assert!(FERRO_RUNTIME_JS.contains("function ferroRuntime()"));
140 assert!(FERRO_RUNTIME_JS.contains("DOMContentLoaded"));
141 assert!(FERRO_RUNTIME_JS.contains("ferroRuntime"));
142 }
143
144 #[test]
145 fn bundle_contains_all_setup_functions() {
146 for fn_name in [
147 "setupSSE",
148 "setupTabs",
149 "setupToasts",
150 "setupSidebar",
151 "setupDropdowns",
152 "setupModals",
153 "setupDismissibles",
154 "setupNotifications",
155 "setupFormGuards",
156 "setupProductTiles",
157 "setupKanban",
158 "setupScrollPreserve",
159 "setupLazyHeroes",
160 ] {
161 assert!(
162 FERRO_RUNTIME_JS.contains(fn_name),
163 "bundle missing {fn_name}"
164 );
165 }
166 }
167
168 #[test]
169 fn bundle_is_single_iife() {
170 assert!(FERRO_RUNTIME_JS.starts_with("(function() {"));
171 assert!(FERRO_RUNTIME_JS.trim_end().ends_with("})();"));
172 }
173
174 #[test]
175 fn dispatcher_invokes_every_setup() {
176 let js: &str = FERRO_RUNTIME_JS.as_str();
177 let dispatcher_start = js.find("function ferroRuntime()").unwrap();
178 let dispatcher = &js[dispatcher_start..];
179 for call in [
180 "setupSSE();",
181 "setupTabs();",
182 "setupToasts();",
183 "setupSidebar();",
184 "setupDropdowns();",
185 "setupModals();",
186 "setupDismissibles();",
187 "setupNotifications();",
188 "setupFormGuards();",
189 "setupProductTiles();",
190 "setupKanban();",
191 "setupScrollPreserve();",
192 "setupLazyHeroes();",
193 ] {
194 assert!(dispatcher.contains(call), "dispatcher missing {call}");
195 }
196 }
197
198 #[test]
199 fn runtime_contains_lazy_hero_setup() {
200 assert!(FERRO_RUNTIME_JS.contains("setupLazyHeroes"));
201 assert!(FERRO_RUNTIME_JS.contains("data-lazy-hero"));
202 assert!(FERRO_RUNTIME_JS.contains("data-lazy-hero-margin"));
203 assert!(FERRO_RUNTIME_JS.contains("data-lazy-hero-promoted"));
204 assert!(FERRO_RUNTIME_JS.contains("IntersectionObserver"));
205 assert!(FERRO_RUNTIME_JS.contains("preload"));
206 assert!(FERRO_RUNTIME_JS.contains("'auto'"));
209 assert!(FERRO_RUNTIME_JS.contains("unobserve"));
210 }
211}