1use dioxus::prelude::*;
4#[allow(unused_imports)]
5use dioxus_router::prelude::*;
6
7#[allow(unused_imports)]
8use crate::ui::{
9 pages::{EmptyState, PageWrapper},
10 router::Route,
11 state::use_app_state,
12};
13
14#[component]
16pub fn Plugins() -> Element {
17 let mut active_tab = use_signal(|| "installed".to_string());
18 let mut search_query = use_signal(String::new);
19 let mut loading = use_signal(|| false);
20
21 let installed_plugins = get_installed_plugins();
23 let available_plugins = get_available_plugins();
24
25 rsx! {
26 PageWrapper {
27 title: "Plugins".to_string(),
28 subtitle: Some("Extend your application with plugins".to_string()),
29 actions: Some(rsx! {
30 div {
31 class: "flex space-x-3",
32 button {
33 r#type: "button",
34 class: "inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
35 onclick: move |_| {
36 loading.set(true);
37 spawn(async move {
38 #[cfg(not(target_arch = "wasm32"))]
39 tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
40 #[cfg(target_arch = "wasm32")]
41 gloo_timers::future::TimeoutFuture::new(1000).await;
42 loading.set(false);
43 });
44 },
45 if loading() {
46 svg {
47 class: "animate-spin -ml-1 mr-2 h-4 w-4",
48 xmlns: "http://www.w3.org/2000/svg",
49 fill: "none",
50 view_box: "0 0 24 24",
51 circle {
52 class: "opacity-25",
53 cx: "12",
54 cy: "12",
55 r: "10",
56 stroke: "currentColor",
57 stroke_width: "4"
58 }
59 path {
60 class: "opacity-75",
61 fill: "currentColor",
62 d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
63 }
64 }
65 } else {
66 svg {
67 class: "-ml-1 mr-2 h-4 w-4",
68 xmlns: "http://www.w3.org/2000/svg",
69 fill: "none",
70 view_box: "0 0 24 24",
71 stroke: "currentColor",
72 path {
73 stroke_linecap: "round",
74 stroke_linejoin: "round",
75 stroke_width: "2",
76 d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
77 }
78 }
79 }
80 "Refresh"
81 }
82 button {
83 r#type: "button",
84 class: "inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
85 svg {
86 class: "-ml-1 mr-2 h-4 w-4",
87 xmlns: "http://www.w3.org/2000/svg",
88 fill: "none",
89 view_box: "0 0 24 24",
90 stroke: "currentColor",
91 path {
92 stroke_linecap: "round",
93 stroke_linejoin: "round",
94 stroke_width: "2",
95 d: "M12 6v6m0 0v6m0-6h6m-6 0H6"
96 }
97 }
98 "Install Plugin"
99 }
100 }
101 }),
102
103 div {
105 class: "mb-6",
106 div {
107 class: "relative",
108 input {
109 r#type: "text",
110 placeholder: "Search plugins...",
111 class: "block w-full pr-12 border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500 sm:text-sm",
112 value: "{search_query}",
113 oninput: move |e| search_query.set(e.value())
114 }
115 div {
116 class: "absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none",
117 svg {
118 class: "h-5 w-5 text-gray-400",
119 xmlns: "http://www.w3.org/2000/svg",
120 view_box: "0 0 20 20",
121 fill: "currentColor",
122 path {
123 fill_rule: "evenodd",
124 d: "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
125 clip_rule: "evenodd"
126 }
127 }
128 }
129 }
130 }
131
132 div {
134 class: "border-b border-gray-200 mb-6",
135 nav {
136 class: "-mb-px flex space-x-8",
137 button {
138 r#type: "button",
139 class: format!(
140 "py-2 px-1 border-b-2 font-medium text-sm {}",
141 if active_tab() == "installed" {
142 "border-blue-500 text-blue-600"
143 } else {
144 "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
145 }
146 ),
147 onclick: move |_| active_tab.set("installed".to_string()),
148 "Installed ({installed_plugins.len()})"
149 }
150 button {
151 r#type: "button",
152 class: format!(
153 "py-2 px-1 border-b-2 font-medium text-sm {}",
154 if active_tab() == "available" {
155 "border-blue-500 text-blue-600"
156 } else {
157 "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
158 }
159 ),
160 onclick: move |_| active_tab.set("available".to_string()),
161 "Available ({available_plugins.len()})"
162 }
163 button {
164 r#type: "button",
165 class: format!(
166 "py-2 px-1 border-b-2 font-medium text-sm {}",
167 if active_tab() == "updates" {
168 "border-blue-500 text-blue-600"
169 } else {
170 "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
171 }
172 ),
173 onclick: move |_| active_tab.set("updates".to_string()),
174 "Updates (2)"
175 }
176 }
177 }
178
179 match active_tab().as_str() {
181 "installed" => rsx! {
182 InstalledPluginsTab {
183 plugins: installed_plugins,
184 search_query: search_query()
185 }
186 },
187 "available" => rsx! {
188 AvailablePluginsTab {
189 plugins: available_plugins,
190 search_query: search_query()
191 }
192 },
193 "updates" => rsx! {
194 UpdatesTab {}
195 },
196 _ => rsx! { div { "Unknown tab" } }
197 }
198 }
199 }
200}
201
202#[component]
204fn InstalledPluginsTab(plugins: Vec<PluginInfo>, search_query: String) -> Element {
205 let filtered_plugins: Vec<PluginInfo> = plugins
206 .into_iter()
207 .filter(|p| {
208 if search_query.is_empty() {
209 true
210 } else {
211 p.name.to_lowercase().contains(&search_query.to_lowercase())
212 || p.description
213 .to_lowercase()
214 .contains(&search_query.to_lowercase())
215 }
216 })
217 .collect();
218
219 rsx! {
220 if filtered_plugins.is_empty() {
221 EmptyState {
222 icon: "🧩".to_string(),
223 title: if search_query.is_empty() {
224 "No plugins installed".to_string()
225 } else {
226 "No plugins found".to_string()
227 },
228 description: if search_query.is_empty() {
229 "Install plugins to extend your application functionality".to_string()
230 } else {
231 "Try adjusting your search terms".to_string()
232 },
233 }
234 } else {
235 div {
236 class: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3",
237 for plugin in filtered_plugins {
238 PluginCard {
239 key: "{plugin.id}",
240 plugin: plugin.clone(),
241 is_installed: true
242 }
243 }
244 }
245 }
246 }
247}
248
249#[component]
251fn AvailablePluginsTab(plugins: Vec<PluginInfo>, search_query: String) -> Element {
252 let filtered_plugins: Vec<PluginInfo> = plugins
253 .into_iter()
254 .filter(|p| {
255 if search_query.is_empty() {
256 true
257 } else {
258 p.name.to_lowercase().contains(&search_query.to_lowercase())
259 || p.description
260 .to_lowercase()
261 .contains(&search_query.to_lowercase())
262 }
263 })
264 .collect();
265
266 rsx! {
267 if filtered_plugins.is_empty() {
268 EmptyState {
269 icon: "🔍".to_string(),
270 title: "No plugins found".to_string(),
271 description: "Try adjusting your search terms".to_string(),
272 }
273 } else {
274 div {
275 class: "grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3",
276 for plugin in filtered_plugins {
277 PluginCard {
278 key: "{plugin.id}",
279 plugin: plugin.clone(),
280 is_installed: false
281 }
282 }
283 }
284 }
285 }
286}
287
288#[component]
290fn UpdatesTab() -> Element {
291 let updates = get_plugin_updates();
292
293 rsx! {
294 if updates.is_empty() {
295 EmptyState {
296 icon: "✅".to_string(),
297 title: "All plugins up to date".to_string(),
298 description: "Your plugins are running the latest versions".to_string(),
299 }
300 } else {
301 div {
302 class: "space-y-4",
303 for update in updates {
304 UpdateCard {
305 key: "{update.plugin_id}",
306 update: update.clone()
307 }
308 }
309 }
310 }
311 }
312}
313
314#[component]
316fn PluginCard(plugin: PluginInfo, is_installed: bool) -> Element {
317 let mut installing = use_signal(|| false);
318 let mut uninstalling = use_signal(|| false);
319
320 let handle_install = {
321 move |_| {
322 installing.set(true);
323 spawn(async move {
324 #[cfg(not(target_arch = "wasm32"))]
325 tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
326 #[cfg(target_arch = "wasm32")]
327 gloo_timers::future::TimeoutFuture::new(2000).await;
328 installing.set(false);
329 });
330 }
331 };
332
333 let handle_uninstall = {
334 move |_| {
335 uninstalling.set(true);
336 spawn(async move {
337 #[cfg(not(target_arch = "wasm32"))]
338 tokio::time::sleep(std::time::Duration::from_millis(1500)).await;
339 #[cfg(target_arch = "wasm32")]
340 gloo_timers::future::TimeoutFuture::new(1500).await;
341 uninstalling.set(false);
342 });
343 }
344 };
345
346 rsx! {
347 div {
348 class: "bg-white overflow-hidden shadow rounded-lg hover:shadow-md transition-shadow",
349 div {
350 class: "p-6",
351 div {
352 class: "flex items-center justify-between",
353 div {
354 class: "flex items-center",
355 div {
356 class: "flex-shrink-0",
357 span {
358 class: "text-3xl",
359 "{plugin.icon}"
360 }
361 }
362 div {
363 class: "ml-4",
364 h3 {
365 class: "text-lg font-medium text-gray-900",
366 "{plugin.name}"
367 }
368 p {
369 class: "text-sm text-gray-500",
370 "v{plugin.version} by {plugin.author}"
371 }
372 }
373 }
374 div {
375 class: "flex-shrink-0",
376 if is_installed {
377 span {
378 class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800",
379 "Installed"
380 }
381 } else {
382 span {
383 class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800",
384 "Available"
385 }
386 }
387 }
388 }
389
390 div {
391 class: "mt-4",
392 p {
393 class: "text-sm text-gray-600",
394 "{plugin.description}"
395 }
396 }
397
398 div {
400 class: "mt-4 flex items-center text-xs text-gray-500 space-x-4",
401 div {
402 class: "flex items-center",
403 svg {
404 class: "h-4 w-4 mr-1",
405 xmlns: "http://www.w3.org/2000/svg",
406 view_box: "0 0 20 20",
407 fill: "currentColor",
408 path {
409 d: "M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"
410 }
411 }
412 "{plugin.rating}/5"
413 }
414 div {
415 class: "flex items-center",
416 svg {
417 class: "h-4 w-4 mr-1",
418 xmlns: "http://www.w3.org/2000/svg",
419 view_box: "0 0 20 20",
420 fill: "currentColor",
421 path {
422 fill_rule: "evenodd",
423 d: "M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z",
424 clip_rule: "evenodd"
425 }
426 }
427 "{plugin.downloads}"
428 }
429 div {
430 class: "flex items-center",
431 svg {
432 class: "h-4 w-4 mr-1",
433 xmlns: "http://www.w3.org/2000/svg",
434 view_box: "0 0 20 20",
435 fill: "currentColor",
436 path {
437 fill_rule: "evenodd",
438 d: "M12.316 3.051a1 1 0 01.633 1.265l-4 12a1 1 0 11-1.898-.632l4-12a1 1 0 011.265-.633zM5.707 6.293a1 1 0 010 1.414L3.414 10l2.293 2.293a1 1 0 11-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0zm8.586 0a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 11-1.414-1.414L16.586 10l-2.293-2.293a1 1 0 010-1.414z",
439 clip_rule: "evenodd"
440 }
441 }
442 "{plugin.category}"
443 }
444 }
445
446 div {
448 class: "mt-6 flex space-x-3",
449 if is_installed {
450 Link {
451 to: Route::Plugin { plugin_id: plugin.id.clone() },
452 class: "flex-1 bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 text-center",
453 "Configure"
454 }
455 button {
456 r#type: "button",
457 class: "flex-1 bg-red-600 py-2 px-3 border border-transparent rounded-md shadow-sm text-sm leading-4 font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 disabled:opacity-50",
458 disabled: uninstalling(),
459 onclick: handle_uninstall,
460 if uninstalling() { "Uninstalling..." } else { "Uninstall" }
461 }
462 } else {
463 button {
464 r#type: "button",
465 class: "flex-1 bg-blue-600 py-2 px-3 border border-transparent rounded-md shadow-sm text-sm leading-4 font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50",
466 disabled: installing(),
467 onclick: handle_install,
468 if installing() { "Installing..." } else { "Install" }
469 }
470 button {
471 r#type: "button",
472 class: "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
473 "Details"
474 }
475 }
476 }
477 }
478 }
479 }
480}
481
482#[component]
484fn UpdateCard(update: PluginUpdate) -> Element {
485 let mut updating = use_signal(|| false);
486
487 let handle_update = {
488 move |_| {
489 updating.set(true);
490 spawn(async move {
491 #[cfg(not(target_arch = "wasm32"))]
492 tokio::time::sleep(std::time::Duration::from_millis(2000)).await;
493 #[cfg(target_arch = "wasm32")]
494 gloo_timers::future::TimeoutFuture::new(2000).await;
495 updating.set(false);
496 });
497 }
498 };
499
500 rsx! {
501 div {
502 class: "bg-white overflow-hidden shadow rounded-lg border-l-4 border-yellow-400",
503 div {
504 class: "p-6",
505 div {
506 class: "flex items-center justify-between",
507 div {
508 class: "flex items-center",
509 div {
510 class: "flex-shrink-0",
511 span {
512 class: "text-2xl",
513 "{update.icon}"
514 }
515 }
516 div {
517 class: "ml-4",
518 h3 {
519 class: "text-lg font-medium text-gray-900",
520 "{update.name}"
521 }
522 p {
523 class: "text-sm text-gray-500",
524 "Update available: v{update.current_version} → v{update.new_version}"
525 }
526 }
527 }
528 div {
529 class: "flex space-x-3",
530 button {
531 r#type: "button",
532 class: "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500",
533 "View Changes"
534 }
535 button {
536 r#type: "button",
537 class: "bg-yellow-600 py-2 px-3 border border-transparent rounded-md shadow-sm text-sm leading-4 font-medium text-white hover:bg-yellow-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-yellow-500 disabled:opacity-50",
538 disabled: updating(),
539 onclick: handle_update,
540 if updating() { "Updating..." } else { "Update" }
541 }
542 }
543 }
544
545 if !update.changelog.is_empty() {
546 div {
547 class: "mt-4",
548 h4 {
549 class: "text-sm font-medium text-gray-900 mb-2",
550 "What's New:"
551 }
552 ul {
553 class: "text-sm text-gray-600 space-y-1",
554 for item in &update.changelog {
555 li {
556 class: "flex items-start",
557 span {
558 class: "text-green-500 mr-2",
559 "•"
560 }
561 "{item}"
562 }
563 }
564 }
565 }
566 }
567 }
568 }
569 }
570}
571
572#[component]
574pub fn PluginView(plugin_id: String, #[props(default = None)] page: Option<String>) -> Element {
575 rsx! {
576 PageWrapper {
577 title: format!("Plugin: {}", plugin_id),
578 subtitle: Some("Plugin configuration and management".to_string()),
579
580 div {
581 class: "bg-white shadow rounded-lg p-6",
582 h2 {
583 class: "text-xl font-semibold text-gray-900 mb-4",
584 "Plugin: {plugin_id}"
585 }
586
587 if let Some(page_name) = page {
588 p {
589 class: "text-gray-600 mb-4",
590 "Page: {page_name}"
591 }
592 }
593
594 p {
595 class: "text-gray-600",
596 "This would show the plugin's interface and configuration options."
597 }
598
599 div {
600 class: "mt-6 p-4 bg-blue-50 rounded-md",
601 p {
602 class: "text-sm text-blue-800",
603 "🔌 Plugin content would be rendered here dynamically based on the plugin's configuration."
604 }
605 }
606 }
607 }
608 }
609}
610
611#[derive(Debug, Clone, PartialEq)]
613struct PluginInfo {
614 id: String,
615 name: String,
616 version: String,
617 author: String,
618 description: String,
619 icon: String,
620 rating: f32,
621 downloads: String,
622 category: String,
623}
624
625#[derive(Debug, Clone, PartialEq)]
626struct PluginUpdate {
627 plugin_id: String,
628 name: String,
629 icon: String,
630 current_version: String,
631 new_version: String,
632 changelog: Vec<String>,
633}
634
635fn get_installed_plugins() -> Vec<PluginInfo> {
636 vec![
637 PluginInfo {
638 id: "inventory".to_string(),
639 name: "Inventory Management".to_string(),
640 version: "2.1.0".to_string(),
641 author: "QorzenTech".to_string(),
642 description: "Complete inventory tracking and management system with barcode support"
643 .to_string(),
644 icon: "📦".to_string(),
645 rating: 4.8,
646 downloads: "12.5k".to_string(),
647 category: "Business".to_string(),
648 },
649 PluginInfo {
650 id: "analytics".to_string(),
651 name: "Analytics Dashboard".to_string(),
652 version: "1.5.2".to_string(),
653 author: "DataViz Inc".to_string(),
654 description: "Advanced analytics and reporting with beautiful visualizations"
655 .to_string(),
656 icon: "📊".to_string(),
657 rating: 4.6,
658 downloads: "8.2k".to_string(),
659 category: "Analytics".to_string(),
660 },
661 PluginInfo {
662 id: "backup".to_string(),
663 name: "Backup & Sync".to_string(),
664 version: "3.0.1".to_string(),
665 author: "SecureData".to_string(),
666 description: "Automated backup and synchronization across multiple cloud providers"
667 .to_string(),
668 icon: "☁️".to_string(),
669 rating: 4.9,
670 downloads: "15.1k".to_string(),
671 category: "Utility".to_string(),
672 },
673 ]
674}
675
676fn get_available_plugins() -> Vec<PluginInfo> {
677 vec![
678 PluginInfo {
679 id: "crm".to_string(),
680 name: "Customer Relations".to_string(),
681 version: "1.2.0".to_string(),
682 author: "CRM Solutions".to_string(),
683 description: "Comprehensive customer relationship management system".to_string(),
684 icon: "👥".to_string(),
685 rating: 4.4,
686 downloads: "6.8k".to_string(),
687 category: "Business".to_string(),
688 },
689 PluginInfo {
690 id: "payments".to_string(),
691 name: "Payment Processing".to_string(),
692 version: "2.3.1".to_string(),
693 author: "PayTech".to_string(),
694 description: "Secure payment processing with multiple gateway support".to_string(),
695 icon: "💳".to_string(),
696 rating: 4.7,
697 downloads: "9.4k".to_string(),
698 category: "Finance".to_string(),
699 },
700 PluginInfo {
701 id: "notifications".to_string(),
702 name: "Smart Notifications".to_string(),
703 version: "1.0.5".to_string(),
704 author: "NotifyMe".to_string(),
705 description: "Advanced notification system with email, SMS, and push support"
706 .to_string(),
707 icon: "🔔".to_string(),
708 rating: 4.2,
709 downloads: "3.1k".to_string(),
710 category: "Communication".to_string(),
711 },
712 PluginInfo {
713 id: "scheduler".to_string(),
714 name: "Task Scheduler".to_string(),
715 version: "1.8.0".to_string(),
716 author: "TimeKeeper".to_string(),
717 description: "Powerful task scheduling and automation system".to_string(),
718 icon: "⏰".to_string(),
719 rating: 4.5,
720 downloads: "5.7k".to_string(),
721 category: "Productivity".to_string(),
722 },
723 ]
724}
725
726fn get_plugin_updates() -> Vec<PluginUpdate> {
727 vec![
728 PluginUpdate {
729 plugin_id: "inventory".to_string(),
730 name: "Inventory Management".to_string(),
731 icon: "📦".to_string(),
732 current_version: "2.1.0".to_string(),
733 new_version: "2.2.0".to_string(),
734 changelog: vec![
735 "Added bulk import/export functionality".to_string(),
736 "Improved barcode scanning accuracy".to_string(),
737 "Fixed issue with low stock alerts".to_string(),
738 "Enhanced reporting capabilities".to_string(),
739 ],
740 },
741 PluginUpdate {
742 plugin_id: "analytics".to_string(),
743 name: "Analytics Dashboard".to_string(),
744 icon: "📊".to_string(),
745 current_version: "1.5.2".to_string(),
746 new_version: "1.6.0".to_string(),
747 changelog: vec![
748 "New real-time dashboard widgets".to_string(),
749 "Added data export to Excel".to_string(),
750 "Performance improvements for large datasets".to_string(),
751 ],
752 },
753 ]
754}
755
756#[cfg(test)]
757mod tests {
758 use super::*;
759
760 #[test]
761 fn test_plugins_component_creation() {
762 let _plugins = rsx! { Plugins {} };
763 }
764
765 #[test]
766 fn test_plugin_view_creation() {
767 let _plugin_view = rsx! {
768 PluginView {
769 plugin_id: "test".to_string(),
770 page: Some("config".to_string())
771 }
772 };
773 }
774
775 #[test]
776 fn test_mock_data() {
777 let installed = get_installed_plugins();
778 let available = get_available_plugins();
779 let updates = get_plugin_updates();
780
781 assert!(!installed.is_empty());
782 assert!(!available.is_empty());
783 assert!(!updates.is_empty());
784 }
785}