// MIT License // Copyright (c) 2025 Mahesh @ HeshApps // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! Unit tests for Music module for Unibar use super::*; use std::process::Command; use crate::bar_modules::BarModuleActions; /// Helper function to create test options fn create_test_options(with_progress: bool) -> common::AppOptions { common::AppOptions { interval: 1, weather_units: common::TemperatureUnits::Metric, weather_station: "test".to_string(), music_progress: with_progress, debug: false, debug_modules: false, debug_update: false, } } /// Test that module handles missing mpc command gracefully. /// Note: This needs ROOT permissions, hence excluding from normal /// testing #[test] #[ignore] fn test_missing_mpc_command() { let mut module = UnibarModuleMusic::new(create_test_options(true)); // Temporarily rename mpc command if it exists let has_mpc = Command::new("which") .arg("mpc") .output() .map(|output| output.status.success()) .unwrap_or(false); let original_mpc_path = "/usr/bin/mpc"; let backup_mpc_path = "/tmp/mpc.bak"; if has_mpc { // Create a backup std::fs::copy(original_mpc_path, backup_mpc_path).expect("Failed to backup mpc"); // Rename the original std::fs::rename(original_mpc_path, "/tmp/mpc_renamed").expect("Failed to rename mpc"); } // Generate data should not panic module.generate_data(); // Should show appropriate error state assert_eq!(module.get_icon(), "⏹"); assert!(module.get_content().contains("No player")); // Restore mpc if it existed if has_mpc { // Remove the renamed file std::fs::remove_file("/tmp/mpc_renamed").expect("Failed to remove renamed mpc"); // Restore from backup std::fs::rename(backup_mpc_path, original_mpc_path).expect("Failed to restore mpc"); } } /// Test handling of mpc command failing #[test] fn test_mpc_command_failure() { let mut module = UnibarModuleMusic::new(create_test_options(true)); // Mock mpc command to always fail module.current_stdout = "Error: Command not found".to_string(); module.progress_stdout = "".to_string(); module.state_stdout = "stopped".to_string(); // Generate data should not panic // module.generate_data(); // Should show appropriate error state assert_eq!(module.get_icon(), "⏹"); assert!(module.get_content().contains("Command not found")); } /// Test handling of various mpc output formats #[test] fn test_unexpected_mpc_output() { let mut module = UnibarModuleMusic::new(create_test_options(true)); // Test cases for current_stdout let test_cases = vec![ "", // Empty output "\n", // Just newline //"invalid utf8 \x80", // Invalid UTF-8 "very\nlong\noutput\n", // Multiple lines " ", // Just whitespace "🎵 unicode 🎶", // Unicode characters ]; for test_case in test_cases { module.current_stdout = test_case.to_string(); // get_content should not panic let content = module.get_content(); assert!(!content.is_empty(), "Content should not be empty"); } } /// Test state handling and icon selection #[test] fn test_player_states() { let mut module = UnibarModuleMusic::new(create_test_options(false)); // Test valid states let states = vec![ ("playing", "𝄞"), ("paused", "⏸"), ("stopped", "⏹"), ]; for (state, expected_icon) in states { module.state_stdout = state.to_string(); assert_eq!(module.get_icon(), expected_icon); } // Test invalid states let invalid_states = vec![ "", "invalid", "PLAYING", "unknown", "\n", "playing\n", ]; for state in invalid_states { module.state_stdout = state.to_string(); assert_eq!(module.get_icon(), "𝄞", "Invalid state should default to playing icon"); } } /// Test progress display formatting #[test] fn test_progress_display() { let mut module = UnibarModuleMusic::new(create_test_options(true)); // Test various progress formats let test_cases = vec![ ("50% 3:30", "[50% 3:30]"), ("", "[]"), ("invalid", "[invalid]"), ("100% 0:00", "[100% 0:00]"), (" 50% 3:30 ", "[50% 3:30]"), // Extra whitespace ]; for (progress, expected) in test_cases { module.progress_stdout = progress.to_string(); module.current_stdout = "Test Song".to_string(); let content = module.get_content(); assert!(content.contains(expected)); } // Test with progress disabled let mut module = UnibarModuleMusic::new(create_test_options(false)); module.progress_stdout = "50% 3:30".to_string(); module.current_stdout = "Test Song".to_string(); assert_eq!(module.get_content(), "Test Song"); } /// Test module initialization #[test] fn test_module_initialization() { let module = UnibarModuleMusic::new(create_test_options(true)); assert_eq!(module.current_stdout, ""); assert_eq!(module.progress_stdout, ""); assert_eq!(module.state_stdout, "stopped"); assert_eq!(module.get_icon(), "⏹"); } /// Test clear functionality #[test] fn test_clear() { let mut module = UnibarModuleMusic::new(create_test_options(true)); // Set some data module.current_stdout = "Test Song".to_string(); module.progress_stdout = "50% 3:30".to_string(); module.state_stdout = "playing".to_string(); // Clear the module module.clear(); // Verify cleared state assert_eq!(module.current_stdout, ""); assert_eq!(module.progress_stdout, ""); assert_eq!(module.state_stdout, "stopped"); }