Major additions: - Video editing engine with AI features (transcription, captions, TTS, scene detection) - RBAC middleware and organization management - Security enhancements (MFA, passkey, DLP, encryption, audit) - Billing and subscription management - Contacts management - Dashboards module - Learn/LMS module - Social features - Compliance (SOC2, SOP middleware, vulnerability scanner) - New migrations for RBAC, learn, and video tables
744 lines
23 KiB
Rust
744 lines
23 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum MenuItemType {
|
|
App,
|
|
Section,
|
|
Divider,
|
|
Link,
|
|
SubMenu,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum AppType {
|
|
Docs,
|
|
Paper,
|
|
Forms,
|
|
Sites,
|
|
Drive,
|
|
Sheet,
|
|
Slides,
|
|
Calendar,
|
|
Meet,
|
|
Tasks,
|
|
Mail,
|
|
Chat,
|
|
Analytics,
|
|
Monitoring,
|
|
Settings,
|
|
Admin,
|
|
}
|
|
|
|
impl AppType {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
Self::Docs => "docs",
|
|
Self::Paper => "paper",
|
|
Self::Forms => "forms",
|
|
Self::Sites => "sites",
|
|
Self::Drive => "drive",
|
|
Self::Sheet => "sheet",
|
|
Self::Slides => "slides",
|
|
Self::Calendar => "calendar",
|
|
Self::Meet => "meet",
|
|
Self::Tasks => "tasks",
|
|
Self::Mail => "mail",
|
|
Self::Chat => "chat",
|
|
Self::Analytics => "analytics",
|
|
Self::Monitoring => "monitoring",
|
|
Self::Settings => "settings",
|
|
Self::Admin => "admin",
|
|
}
|
|
}
|
|
|
|
pub fn display_name(&self) -> &'static str {
|
|
match self {
|
|
Self::Docs => "Docs",
|
|
Self::Paper => "Paper",
|
|
Self::Forms => "Forms",
|
|
Self::Sites => "Sites",
|
|
Self::Drive => "Drive",
|
|
Self::Sheet => "Sheet",
|
|
Self::Slides => "Slides",
|
|
Self::Calendar => "Calendar",
|
|
Self::Meet => "Meet",
|
|
Self::Tasks => "Tasks",
|
|
Self::Mail => "Mail",
|
|
Self::Chat => "Chat",
|
|
Self::Analytics => "Analytics",
|
|
Self::Monitoring => "Monitoring",
|
|
Self::Settings => "Settings",
|
|
Self::Admin => "Admin",
|
|
}
|
|
}
|
|
|
|
pub fn icon(&self) -> &'static str {
|
|
match self {
|
|
Self::Docs => "📝",
|
|
Self::Paper => "📓",
|
|
Self::Forms => "📋",
|
|
Self::Sites => "🌐",
|
|
Self::Drive => "💾",
|
|
Self::Sheet => "📊",
|
|
Self::Slides => "📽️",
|
|
Self::Calendar => "📅",
|
|
Self::Meet => "📹",
|
|
Self::Tasks => "✅",
|
|
Self::Mail => "✉️",
|
|
Self::Chat => "💬",
|
|
Self::Analytics => "📈",
|
|
Self::Monitoring => "🔍",
|
|
Self::Settings => "⚙️",
|
|
Self::Admin => "🛡️",
|
|
}
|
|
}
|
|
|
|
pub fn route(&self) -> &'static str {
|
|
match self {
|
|
Self::Docs => "/docs",
|
|
Self::Paper => "/paper",
|
|
Self::Forms => "/forms",
|
|
Self::Sites => "/sites",
|
|
Self::Drive => "/drive",
|
|
Self::Sheet => "/sheet",
|
|
Self::Slides => "/slides",
|
|
Self::Calendar => "/calendar",
|
|
Self::Meet => "/meet",
|
|
Self::Tasks => "/tasks",
|
|
Self::Mail => "/mail",
|
|
Self::Chat => "/chat",
|
|
Self::Analytics => "/analytics",
|
|
Self::Monitoring => "/monitoring",
|
|
Self::Settings => "/settings",
|
|
Self::Admin => "/admin",
|
|
}
|
|
}
|
|
|
|
pub fn description(&self) -> &'static str {
|
|
match self {
|
|
Self::Docs => "Full-featured document editor with rich text formatting",
|
|
Self::Paper => "Quick notes and lightweight text editor",
|
|
Self::Forms => "Create surveys, quizzes, and data collection forms",
|
|
Self::Sites => "Build and publish websites",
|
|
Self::Drive => "File storage and management",
|
|
Self::Sheet => "Spreadsheets and data analysis",
|
|
Self::Slides => "Presentations and slideshows",
|
|
Self::Calendar => "Schedule and event management",
|
|
Self::Meet => "Video conferencing and meetings",
|
|
Self::Tasks => "Task and project management",
|
|
Self::Mail => "Email client",
|
|
Self::Chat => "Instant messaging and conversations",
|
|
Self::Analytics => "Data analytics and reporting",
|
|
Self::Monitoring => "System monitoring and health",
|
|
Self::Settings => "Application settings",
|
|
Self::Admin => "Administration panel",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MenuItem {
|
|
pub id: String,
|
|
pub item_type: MenuItemType,
|
|
pub label: String,
|
|
pub icon: Option<String>,
|
|
pub route: Option<String>,
|
|
pub app_type: Option<AppType>,
|
|
pub children: Vec<MenuItem>,
|
|
pub badge: Option<String>,
|
|
pub tooltip: Option<String>,
|
|
pub visible: bool,
|
|
pub enabled: bool,
|
|
pub order: i32,
|
|
pub permissions: Vec<String>,
|
|
pub metadata: HashMap<String, serde_json::Value>,
|
|
}
|
|
|
|
impl MenuItem {
|
|
pub fn app(app_type: AppType, order: i32) -> Self {
|
|
Self {
|
|
id: app_type.as_str().to_string(),
|
|
item_type: MenuItemType::App,
|
|
label: app_type.display_name().to_string(),
|
|
icon: Some(app_type.icon().to_string()),
|
|
route: Some(app_type.route().to_string()),
|
|
app_type: Some(app_type),
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn section(id: &str, label: &str, order: i32) -> Self {
|
|
Self {
|
|
id: id.to_string(),
|
|
item_type: MenuItemType::Section,
|
|
label: label.to_string(),
|
|
icon: None,
|
|
route: None,
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn divider(order: i32) -> Self {
|
|
Self {
|
|
id: format!("divider_{order}"),
|
|
item_type: MenuItemType::Divider,
|
|
label: String::new(),
|
|
icon: None,
|
|
route: None,
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn with_children(mut self, children: Vec<MenuItem>) -> Self {
|
|
self.children = children;
|
|
self
|
|
}
|
|
|
|
pub fn with_badge(mut self, badge: &str) -> Self {
|
|
self.badge = Some(badge.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn with_tooltip(mut self, tooltip: &str) -> Self {
|
|
self.tooltip = Some(tooltip.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn with_permissions(mut self, permissions: Vec<&str>) -> Self {
|
|
self.permissions = permissions.into_iter().map(String::from).collect();
|
|
self
|
|
}
|
|
|
|
pub fn hidden(mut self) -> Self {
|
|
self.visible = false;
|
|
self
|
|
}
|
|
|
|
pub fn disabled(mut self) -> Self {
|
|
self.enabled = false;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MenuSection {
|
|
pub id: String,
|
|
pub label: String,
|
|
pub icon: Option<String>,
|
|
pub items: Vec<MenuItem>,
|
|
pub collapsed: bool,
|
|
pub order: i32,
|
|
}
|
|
|
|
impl MenuSection {
|
|
pub fn new(id: &str, label: &str, order: i32) -> Self {
|
|
Self {
|
|
id: id.to_string(),
|
|
label: label.to_string(),
|
|
icon: None,
|
|
items: Vec::new(),
|
|
collapsed: false,
|
|
order,
|
|
}
|
|
}
|
|
|
|
pub fn with_icon(mut self, icon: &str) -> Self {
|
|
self.icon = Some(icon.to_string());
|
|
self
|
|
}
|
|
|
|
pub fn with_items(mut self, items: Vec<MenuItem>) -> Self {
|
|
self.items = items;
|
|
self
|
|
}
|
|
|
|
pub fn collapsed(mut self) -> Self {
|
|
self.collapsed = true;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MenuConfig {
|
|
pub sections: Vec<MenuSection>,
|
|
pub footer_items: Vec<MenuItem>,
|
|
pub user_menu_items: Vec<MenuItem>,
|
|
pub quick_actions: Vec<MenuItem>,
|
|
}
|
|
|
|
impl Default for MenuConfig {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl MenuConfig {
|
|
pub fn new() -> Self {
|
|
let productivity_section = MenuSection::new("productivity", "Productivity", 1)
|
|
.with_icon("⚡")
|
|
.with_items(vec![
|
|
MenuItem::app(AppType::Chat, 1),
|
|
MenuItem::app(AppType::Mail, 2),
|
|
MenuItem::app(AppType::Calendar, 3),
|
|
MenuItem::app(AppType::Tasks, 4),
|
|
MenuItem::app(AppType::Meet, 5),
|
|
]);
|
|
|
|
let library_section = MenuSection::new("library", "Library", 2)
|
|
.with_icon("📚")
|
|
.with_items(vec![
|
|
MenuItem::app(AppType::Drive, 1),
|
|
MenuItem::app(AppType::Docs, 2)
|
|
.with_tooltip("Full-featured document editor with rich text formatting, collaboration, and export options"),
|
|
MenuItem::app(AppType::Paper, 3)
|
|
.with_tooltip("Quick notes and lightweight text editor for fast capture"),
|
|
MenuItem::app(AppType::Sheet, 4),
|
|
MenuItem::app(AppType::Slides, 5),
|
|
MenuItem::app(AppType::Forms, 6)
|
|
.with_tooltip("Create surveys, quizzes, and data collection forms"),
|
|
MenuItem::app(AppType::Sites, 7)
|
|
.with_tooltip("Build and publish websites"),
|
|
]);
|
|
|
|
let insights_section = MenuSection::new("insights", "Insights", 3)
|
|
.with_icon("📊")
|
|
.with_items(vec![
|
|
MenuItem::app(AppType::Analytics, 1),
|
|
MenuItem::app(AppType::Monitoring, 2),
|
|
]);
|
|
|
|
let admin_section = MenuSection::new("admin", "Administration", 4)
|
|
.with_icon("🛡️")
|
|
.with_items(vec![
|
|
MenuItem::app(AppType::Settings, 1),
|
|
MenuItem::app(AppType::Admin, 2)
|
|
.with_permissions(vec!["admin", "owner"]),
|
|
]);
|
|
|
|
let footer_items = vec![
|
|
MenuItem {
|
|
id: "help".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "Help & Support".to_string(),
|
|
icon: Some("❓".to_string()),
|
|
route: Some("/help".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 1,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "feedback".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "Send Feedback".to_string(),
|
|
icon: Some("💭".to_string()),
|
|
route: Some("/feedback".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 2,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
];
|
|
|
|
let user_menu_items = vec![
|
|
MenuItem {
|
|
id: "profile".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "Profile".to_string(),
|
|
icon: Some("👤".to_string()),
|
|
route: Some("/profile".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 1,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "preferences".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "Preferences".to_string(),
|
|
icon: Some("⚙️".to_string()),
|
|
route: Some("/preferences".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 2,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem::divider(3),
|
|
MenuItem {
|
|
id: "logout".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "Sign Out".to_string(),
|
|
icon: Some("🚪".to_string()),
|
|
route: Some("/logout".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 4,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
];
|
|
|
|
let quick_actions = vec![
|
|
MenuItem {
|
|
id: "new_doc".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "New Document".to_string(),
|
|
icon: Some("📝".to_string()),
|
|
route: Some("/docs/new".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: Some("Create a new full document".to_string()),
|
|
visible: true,
|
|
enabled: true,
|
|
order: 1,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "new_note".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "New Note".to_string(),
|
|
icon: Some("📓".to_string()),
|
|
route: Some("/paper/new".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: Some("Create a quick note".to_string()),
|
|
visible: true,
|
|
enabled: true,
|
|
order: 2,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "new_sheet".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "New Spreadsheet".to_string(),
|
|
icon: Some("📊".to_string()),
|
|
route: Some("/sheet/new".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 3,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "new_form".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "New Form".to_string(),
|
|
icon: Some("📋".to_string()),
|
|
route: Some("/forms/new".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 4,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "new_site".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "New Site".to_string(),
|
|
icon: Some("🌐".to_string()),
|
|
route: Some("/sites/new".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 5,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
MenuItem {
|
|
id: "schedule_meeting".to_string(),
|
|
item_type: MenuItemType::Link,
|
|
label: "Schedule Meeting".to_string(),
|
|
icon: Some("📹".to_string()),
|
|
route: Some("/meet/schedule".to_string()),
|
|
app_type: None,
|
|
children: Vec::new(),
|
|
badge: None,
|
|
tooltip: None,
|
|
visible: true,
|
|
enabled: true,
|
|
order: 6,
|
|
permissions: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
},
|
|
];
|
|
|
|
Self {
|
|
sections: vec![
|
|
productivity_section,
|
|
library_section,
|
|
insights_section,
|
|
admin_section,
|
|
],
|
|
footer_items,
|
|
user_menu_items,
|
|
quick_actions,
|
|
}
|
|
}
|
|
|
|
pub fn get_all_items(&self) -> Vec<&MenuItem> {
|
|
let mut items = Vec::new();
|
|
|
|
for section in &self.sections {
|
|
for item in §ion.items {
|
|
items.push(item);
|
|
for child in &item.children {
|
|
items.push(child);
|
|
}
|
|
}
|
|
}
|
|
|
|
for item in &self.footer_items {
|
|
items.push(item);
|
|
}
|
|
|
|
items
|
|
}
|
|
|
|
pub fn get_item_by_id(&self, id: &str) -> Option<&MenuItem> {
|
|
self.get_all_items().into_iter().find(|item| item.id == id)
|
|
}
|
|
|
|
pub fn get_section_by_id(&self, id: &str) -> Option<&MenuSection> {
|
|
self.sections.iter().find(|section| section.id == id)
|
|
}
|
|
|
|
pub fn filter_by_permissions(&self, user_permissions: &[String]) -> Self {
|
|
let mut filtered = self.clone();
|
|
|
|
for section in &mut filtered.sections {
|
|
section.items.retain(|item| {
|
|
if item.permissions.is_empty() {
|
|
return true;
|
|
}
|
|
item.permissions.iter().any(|p| user_permissions.contains(p))
|
|
});
|
|
}
|
|
|
|
filtered.sections.retain(|section| !section.items.is_empty());
|
|
|
|
filtered
|
|
}
|
|
|
|
pub fn to_json(&self) -> Result<String, serde_json::Error> {
|
|
serde_json::to_string_pretty(self)
|
|
}
|
|
|
|
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
|
|
serde_json::from_str(json)
|
|
}
|
|
}
|
|
|
|
pub fn get_docs_paper_clarification() -> HashMap<&'static str, &'static str> {
|
|
let mut clarification = HashMap::new();
|
|
|
|
clarification.insert(
|
|
"docs_purpose",
|
|
"Docs is a full-featured document editor similar to Google Docs or Microsoft Word. \
|
|
It supports rich text formatting, images, tables, collaboration, comments, \
|
|
version history, and export to multiple formats (PDF, DOCX, HTML)."
|
|
);
|
|
|
|
clarification.insert(
|
|
"paper_purpose",
|
|
"Paper is a lightweight notes application similar to Apple Notes or Google Keep. \
|
|
It's designed for quick capture, simple formatting, and fast access to notes. \
|
|
Perfect for meeting notes, quick thoughts, and simple text."
|
|
);
|
|
|
|
clarification.insert(
|
|
"when_to_use_docs",
|
|
"Use Docs when you need: rich formatting, collaboration features, \
|
|
document templates, export capabilities, version history, or formal documents."
|
|
);
|
|
|
|
clarification.insert(
|
|
"when_to_use_paper",
|
|
"Use Paper when you need: quick note-taking, simple lists, \
|
|
fast capture without formatting overhead, or lightweight text storage."
|
|
);
|
|
|
|
clarification.insert(
|
|
"key_differences",
|
|
"Docs = Full document editor (Word-like), Paper = Quick notes (Notes-like). \
|
|
Different tools for different purposes - both available in the Library section."
|
|
);
|
|
|
|
clarification
|
|
}
|
|
|
|
pub fn get_library_section_info() -> HashMap<&'static str, &'static str> {
|
|
let mut info = HashMap::new();
|
|
|
|
info.insert(
|
|
"name",
|
|
"Library"
|
|
);
|
|
|
|
info.insert(
|
|
"description",
|
|
"The Library section contains all content creation and storage apps. \
|
|
This includes Drive (file storage), Docs (full documents), Paper (quick notes), \
|
|
Sheet (spreadsheets), Slides (presentations), Forms (surveys/forms), \
|
|
and Sites (website builder)."
|
|
);
|
|
|
|
info.insert(
|
|
"previous_name",
|
|
"Sources"
|
|
);
|
|
|
|
info.insert(
|
|
"rename_reason",
|
|
"Renamed from 'Sources' to 'Library' for better clarity. \
|
|
Library better represents a collection of content and documents, \
|
|
while 'Sources' was ambiguous and could be confused with data sources or integrations."
|
|
);
|
|
|
|
info
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_default_menu_config() {
|
|
let config = MenuConfig::default();
|
|
assert!(!config.sections.is_empty());
|
|
assert!(!config.footer_items.is_empty());
|
|
assert!(!config.user_menu_items.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_library_section_exists() {
|
|
let config = MenuConfig::default();
|
|
let library = config.get_section_by_id("library");
|
|
assert!(library.is_some());
|
|
assert_eq!(library.map(|s| s.label.as_str()), Some("Library"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_docs_and_paper_in_library() {
|
|
let config = MenuConfig::default();
|
|
let library = config.get_section_by_id("library").unwrap();
|
|
|
|
let has_docs = library.items.iter().any(|i| i.id == "docs");
|
|
let has_paper = library.items.iter().any(|i| i.id == "paper");
|
|
let has_forms = library.items.iter().any(|i| i.id == "forms");
|
|
let has_sites = library.items.iter().any(|i| i.id == "sites");
|
|
|
|
assert!(has_docs, "Library should contain Docs");
|
|
assert!(has_paper, "Library should contain Paper");
|
|
assert!(has_forms, "Library should contain Forms");
|
|
assert!(has_sites, "Library should contain Sites");
|
|
}
|
|
|
|
#[test]
|
|
fn test_app_type_properties() {
|
|
assert_eq!(AppType::Docs.as_str(), "docs");
|
|
assert_eq!(AppType::Paper.as_str(), "paper");
|
|
assert_eq!(AppType::Docs.route(), "/docs");
|
|
assert_eq!(AppType::Paper.route(), "/paper");
|
|
}
|
|
|
|
#[test]
|
|
fn test_permission_filtering() {
|
|
let config = MenuConfig::default();
|
|
let user_permissions = vec!["member".to_string()];
|
|
let filtered = config.filter_by_permissions(&user_permissions);
|
|
|
|
for section in &filtered.sections {
|
|
for item in §ion.items {
|
|
if !item.permissions.is_empty() {
|
|
assert!(item.permissions.iter().any(|p| user_permissions.contains(p)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_json_serialization() {
|
|
let config = MenuConfig::default();
|
|
let json = config.to_json().unwrap();
|
|
let deserialized = MenuConfig::from_json(&json).unwrap();
|
|
assert_eq!(config.sections.len(), deserialized.sections.len());
|
|
}
|
|
|
|
#[test]
|
|
fn test_docs_paper_clarification() {
|
|
let clarification = get_docs_paper_clarification();
|
|
assert!(clarification.contains_key("docs_purpose"));
|
|
assert!(clarification.contains_key("paper_purpose"));
|
|
assert!(clarification.contains_key("key_differences"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_library_section_info() {
|
|
let info = get_library_section_info();
|
|
assert_eq!(info.get("name"), Some(&"Library"));
|
|
assert_eq!(info.get("previous_name"), Some(&"Sources"));
|
|
}
|
|
}
|