use std::process::Command;
use tauri::command;
use crate::process_ext::HideWindow;
/// Generate PowerShell script for Windows Toast Notification
fn generate_powershell_toast_script(title: &str, body: &str) -> String {
format!(
r#"
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null
$APP_ID = 'Hikari Desktop'
$template = @"
{}
{}
"@
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($template)
$toast = New-Object Windows.UI.Notifications.ToastNotification $xml
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($APP_ID).Show($toast)
"#,
title.replace("\"", "`\""),
body.replace("\"", "`\"")
)
}
/// Format simple notification message
fn format_simple_notification(title: &str, body: &str) -> String {
format!("{}\n\n{}", title, body)
}
/// Build notify-send command for testing (doesn't execute)
#[cfg(test)]
fn build_notify_send_command(title: &str, body: &str) -> (String, Vec) {
(
"notify-send".to_string(),
vec![
title.to_string(),
body.to_string(),
"--urgency=normal".to_string(),
"--app-name=Hikari Desktop".to_string(),
],
)
}
/// Build Windows PowerShell command for testing (doesn't execute)
#[cfg(test)]
fn build_windows_powershell_command(title: &str, body: &str) -> (String, Vec) {
let script = generate_powershell_toast_script(title, body);
(
"pwsh.exe".to_string(),
vec![
"-NoProfile".to_string(),
"-WindowStyle".to_string(),
"Hidden".to_string(),
"-Command".to_string(),
script,
],
)
}
/// Build simple notification command for testing (doesn't execute)
#[cfg(test)]
fn build_simple_notification_command(title: &str, body: &str) -> (String, Vec) {
let message = format_simple_notification(title, body);
(
"cmd.exe".to_string(),
vec!["/c".to_string(), "msg".to_string(), "*".to_string(), message],
)
}
#[command]
pub async fn send_notify_send(title: String, body: String) -> Result<(), String> {
// Use notify-send for Linux/WSL
let output = Command::new("notify-send")
.hide_window()
.arg(&title)
.arg(&body)
.arg("--urgency=normal")
.arg("--app-name=Hikari Desktop")
.output()
.map_err(|e| {
format!(
"Failed to execute notify-send: {}. Make sure libnotify-bin is installed.",
e
)
})?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(format!("notify-send failed: {}", error));
}
Ok(())
}
#[command]
pub async fn send_windows_notification(title: String, body: String) -> Result<(), String> {
// Create PowerShell script for Windows Toast Notification
let ps_script = generate_powershell_toast_script(&title, &body);
// Try PowerShell Core first (pwsh), then fall back to Windows PowerShell
let output = Command::new("pwsh.exe")
.hide_window()
.arg("-NoProfile")
.arg("-WindowStyle")
.arg("Hidden")
.arg("-Command")
.arg(&ps_script)
.output()
.or_else(|_| {
Command::new("powershell.exe")
.hide_window()
.arg("-NoProfile")
.arg("-WindowStyle")
.arg("Hidden")
.arg("-Command")
.arg(&ps_script)
.output()
})
.map_err(|e| format!("Failed to execute PowerShell: {}", e))?;
if !output.status.success() {
let error = String::from_utf8_lossy(&output.stderr);
return Err(format!("PowerShell script failed: {}", error));
}
Ok(())
}
// Alternative: Use Windows built-in MSG command for simple notifications
#[command]
pub async fn send_simple_notification(title: String, body: String) -> Result<(), String> {
let message = format_simple_notification(&title, &body);
Command::new("cmd.exe")
.hide_window()
.arg("/c")
.arg("msg")
.arg("*")
.arg(&message)
.output()
.map_err(|e| format!("Failed to send message: {}", e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_powershell_toast_script_basic() {
let script = generate_powershell_toast_script("Title", "Body");
assert!(script.contains("Hikari Desktop"));
assert!(script.contains("Title"));
assert!(script.contains("Body"));
assert!(script.contains("ToastNotification"));
}
#[test]
fn test_generate_powershell_toast_script_escapes_quotes() {
let script = generate_powershell_toast_script("Title with \"quotes\"", "Body with \"quotes\"");
// Quotes should be escaped as `" in PowerShell
assert!(script.contains("Title with `\"quotes`\""));
assert!(script.contains("Body with `\"quotes`\""));
}
#[test]
fn test_generate_powershell_toast_script_with_special_chars() {
let script = generate_powershell_toast_script("Title: Test", "Body\nwith\nnewlines");
assert!(script.contains("Title: Test"));
assert!(script.contains("Body\nwith\nnewlines"));
}
#[test]
fn test_generate_powershell_toast_script_unicode() {
let script = generate_powershell_toast_script("ę„ę¬čŖ Title", "Unicode: š");
assert!(script.contains("ę„ę¬čŖ Title"));
assert!(script.contains("Unicode: š"));
}
#[test]
fn test_generate_powershell_toast_script_empty() {
let script = generate_powershell_toast_script("", "");
// Should still contain the structure
assert!(script.contains("Hikari Desktop"));
assert!(script.contains("ToastNotification"));
}
#[test]
fn test_format_simple_notification_basic() {
let message = format_simple_notification("Title", "Body");
assert_eq!(message, "Title\n\nBody");
}
#[test]
fn test_format_simple_notification_with_newlines() {
let message = format_simple_notification("Multi\nLine\nTitle", "Multi\nLine\nBody");
assert!(message.contains("Multi\nLine\nTitle"));
assert!(message.contains("\n\n"));
assert!(message.contains("Multi\nLine\nBody"));
}
#[test]
fn test_format_simple_notification_unicode() {
let message = format_simple_notification("ę„ę¬čŖ", "š Unicode");
assert_eq!(message, "ę„ę¬čŖ\n\nš Unicode");
}
#[test]
fn test_format_simple_notification_empty() {
let message = format_simple_notification("", "");
assert_eq!(message, "\n\n");
}
#[test]
fn test_format_simple_notification_long_text() {
let long_title = "A".repeat(1000);
let long_body = "B".repeat(1000);
let message = format_simple_notification(&long_title, &long_body);
assert!(message.starts_with(&long_title));
assert!(message.ends_with(&long_body));
assert!(message.contains("\n\n"));
}
#[test]
fn test_generate_powershell_toast_script_multiple_quotes() {
let script = generate_powershell_toast_script(
"\"Quoted\" \"Multiple\" \"Times\"",
"\"More\" \"Quotes\" \"Here\""
);
// Each quote should be escaped
assert!(script.contains("`\"Quoted`\" `\"Multiple`\" `\"Times`\""));
assert!(script.contains("`\"More`\" `\"Quotes`\" `\"Here`\""));
}
// E2E Integration Tests - Command Structure Verification
#[test]
fn test_e2e_notify_send_command_structure() {
let (command, args) = build_notify_send_command("Test Title", "Test Body");
assert_eq!(command, "notify-send");
assert_eq!(args.len(), 4);
assert_eq!(args[0], "Test Title");
assert_eq!(args[1], "Test Body");
assert_eq!(args[2], "--urgency=normal");
assert_eq!(args[3], "--app-name=Hikari Desktop");
}
#[test]
fn test_e2e_notify_send_with_special_chars() {
let (command, args) =
build_notify_send_command("Title with \"quotes\"", "Body\nwith\nnewlines");
assert_eq!(command, "notify-send");
assert_eq!(args[0], "Title with \"quotes\"");
assert_eq!(args[1], "Body\nwith\nnewlines");
// notify-send handles these directly
}
#[test]
fn test_e2e_windows_powershell_command_structure() {
let (command, args) = build_windows_powershell_command("Test Title", "Test Body");
assert_eq!(command, "pwsh.exe");
assert_eq!(args.len(), 5);
assert_eq!(args[0], "-NoProfile");
assert_eq!(args[1], "-WindowStyle");
assert_eq!(args[2], "Hidden");
assert_eq!(args[3], "-Command");
// Verify the script in args[4] contains expected elements
let script = &args[4];
assert!(script.contains("Test Title"));
assert!(script.contains("Test Body"));
assert!(script.contains("Hikari Desktop"));
assert!(script.contains("ToastNotification"));
}
#[test]
fn test_e2e_windows_powershell_quote_escaping() {
let (_, args) =
build_windows_powershell_command("Title with \"quotes\"", "Body with \"quotes\"");
let script = &args[4];
// Verify quotes are properly escaped in the PowerShell script
assert!(script.contains("Title with `\"quotes`\""));
assert!(script.contains("Body with `\"quotes`\""));
}
#[test]
fn test_e2e_simple_notification_command_structure() {
let (command, args) = build_simple_notification_command("Test Title", "Test Body");
assert_eq!(command, "cmd.exe");
assert_eq!(args.len(), 4);
assert_eq!(args[0], "/c");
assert_eq!(args[1], "msg");
assert_eq!(args[2], "*");
assert_eq!(args[3], "Test Title\n\nTest Body");
}
#[test]
fn test_e2e_simple_notification_multiline() {
let (_, args) =
build_simple_notification_command("Multi\nLine\nTitle", "Multi\nLine\nBody");
let message = &args[3];
assert!(message.contains("Multi\nLine\nTitle"));
assert!(message.contains("\n\n"));
assert!(message.contains("Multi\nLine\nBody"));
}
#[test]
fn test_e2e_command_consistency_across_platforms() {
// Test that different platforms use consistent parameters
let title = "Consistency Test";
let body = "Testing cross-platform consistency";
// Linux command
let (notify_cmd, notify_args) = build_notify_send_command(title, body);
assert!(notify_cmd.contains("notify"));
assert!(notify_args.iter().any(|arg| arg.contains("Hikari Desktop")));
// Windows PowerShell command
let (ps_cmd, ps_args) = build_windows_powershell_command(title, body);
assert!(ps_cmd.contains("pwsh") || ps_cmd.contains("powershell"));
let ps_script = &ps_args[4];
assert!(ps_script.contains("Hikari Desktop"));
// Windows simple command
let (msg_cmd, msg_args) = build_simple_notification_command(title, body);
assert!(msg_cmd.contains("cmd"));
assert!(msg_args[3].contains(title));
assert!(msg_args[3].contains(body));
}
#[test]
fn test_e2e_unicode_support_across_platforms() {
let title = "ę„ę¬čŖ Title";
let body = "Unicode: š";
// Verify all platforms preserve unicode
let (_, notify_args) = build_notify_send_command(title, body);
assert_eq!(notify_args[0], title);
assert_eq!(notify_args[1], body);
let (_, ps_args) = build_windows_powershell_command(title, body);
let ps_script = &ps_args[4];
assert!(ps_script.contains(title));
assert!(ps_script.contains(body));
let (_, msg_args) = build_simple_notification_command(title, body);
assert!(msg_args[3].contains(title));
assert!(msg_args[3].contains(body));
}
#[test]
fn test_e2e_empty_input_handling() {
// Test that empty inputs are handled gracefully
let (_, notify_args) = build_notify_send_command("", "");
assert_eq!(notify_args[0], "");
assert_eq!(notify_args[1], "");
let (_, ps_args) = build_windows_powershell_command("", "");
let ps_script = &ps_args[4];
assert!(ps_script.contains("Hikari Desktop")); // Still has app name
let (_, msg_args) = build_simple_notification_command("", "");
assert_eq!(msg_args[3], "\n\n");
}
}