//! M2: Native Workspace validation tests. //! //! Validates toast system, menu sync logic, dialog i18n, //! and keyboard shortcut coverage. use bds_core::i18n::{translate, UiLocale}; use bds_ui::state::toast::{Toast, ToastLevel}; use bds_ui::platform::menu::MenuAction; // ── Toast system ── #[test] fn toast_ids_are_monotonically_increasing() { let a = Toast::new(ToastLevel::Info, "first".into()); let b = Toast::new(ToastLevel::Warning, "second".into()); let c = Toast::new(ToastLevel::Error, "third".into()); assert!(b.id > a.id); assert!(c.id > b.id); } #[test] fn toast_level_variants() { let info = Toast::new(ToastLevel::Info, "info".into()); let success = Toast::new(ToastLevel::Success, "ok".into()); let warning = Toast::new(ToastLevel::Warning, "warn".into()); let error = Toast::new(ToastLevel::Error, "err".into()); assert_eq!(info.level, ToastLevel::Info); assert_eq!(success.level, ToastLevel::Success); assert_eq!(warning.level, ToastLevel::Warning); assert_eq!(error.level, ToastLevel::Error); } #[test] fn fresh_toast_is_not_expired() { let t = Toast::new(ToastLevel::Info, "test".into()); assert!(!t.is_expired()); } #[test] fn toast_preserves_message() { let t = Toast::new(ToastLevel::Error, "something failed".into()); assert_eq!(t.message, "something failed"); } // ── Menu enable/disable rules ── // (These test the expected invariants, not the BdsApp method directly, // since BdsApp::new() requires main thread for muda.) #[test] fn menu_actions_that_need_project() { let project_actions = [ MenuAction::NewPost, MenuAction::ImportMedia, MenuAction::OpenDataFolder, MenuAction::EditMenu, MenuAction::RebuildDatabase, MenuAction::ReindexText, MenuAction::MetadataDiff, MenuAction::RegenerateCalendar, MenuAction::ValidateTranslations, MenuAction::GenerateSitemap, MenuAction::ValidateSite, ]; // All should have i18n keys for action in &project_actions { let key = action.i18n_key(); let label = translate(UiLocale::En, key); assert_ne!(label, key, "missing translation for {key}"); } assert_eq!(project_actions.len(), 11); } #[test] fn menu_actions_that_need_tab() { let tab_actions = [ MenuAction::Save, MenuAction::OpenInBrowser, MenuAction::Find, MenuAction::Replace, ]; for action in &tab_actions { let key = action.i18n_key(); let label = translate(UiLocale::En, key); assert_ne!(label, key, "missing translation for {key}"); } assert_eq!(tab_actions.len(), 4); } #[test] fn menu_actions_gated_by_offline() { let online_only = [ MenuAction::FillMissingTranslations, MenuAction::UploadSite, ]; for action in &online_only { let key = action.i18n_key(); let label = translate(UiLocale::En, key); assert_ne!(label, key, "missing translation for {key}"); } assert_eq!(online_only.len(), 2); } // ── Dialog i18n ── #[test] fn dialog_keys_exist_in_all_locales() { let keys = ["dialog.selectFolder", "dialog.importMedia", "dialog.imageFilter"]; for locale in UiLocale::all() { for key in &keys { let label = translate(*locale, key); assert_ne!(label, *key, "missing {key} for locale {locale}"); } } } // ── Keyboard shortcut coverage ── #[test] fn accelerator_actions_match_spec() { // M2 spec: these actions must have keyboard shortcuts let accelerated = [ MenuAction::NewPost, // Cmd+N MenuAction::ImportMedia, // Cmd+I MenuAction::Save, // Cmd+S MenuAction::Find, // Cmd+F MenuAction::Replace, // Cmd+H MenuAction::EditPreferences, // Cmd+, MenuAction::ViewPosts, // Cmd+1 MenuAction::ViewMedia, // Cmd+2 MenuAction::ToggleSidebar, // Cmd+B MenuAction::TogglePanel, // Cmd+J MenuAction::PublishSelected, // Cmd+Shift+P MenuAction::PreviewPost, // Cmd+Shift+V MenuAction::GenerateSitemap, // Cmd+R MenuAction::ValidateSite, // Cmd+Shift+L MenuAction::UploadSite, // Cmd+Shift+U ]; // All must be valid MenuAction variants with i18n keys for action in &accelerated { assert!(!action.i18n_key().is_empty()); } assert_eq!(accelerated.len(), 15, "M2 spec has 15 accelerator-bound actions"); } // ── Toast i18n keys ── #[test] fn toast_keys_exist_in_all_locales() { let keys = [ "projectSelector.toast.switched", "projectSelector.toast.switchFailed", "projectSelector.toast.created", "projectSelector.toast.createFailed", "projectSelector.toast.deleteFailed", ]; for locale in UiLocale::all() { for key in &keys { let label = translate(*locale, key); assert_ne!(label, *key, "missing {key} for locale {locale}"); } } }