1mod com;
40mod context_menu;
41mod error;
42#[cfg(feature = "ffi")]
43mod ffi;
44mod hidden_window;
45mod invoke;
46mod menu_items;
47mod shell_item;
48mod util;
49
50pub use com::ComGuard;
51pub use context_menu::ContextMenu;
52pub use error::{Error, Result};
53pub use menu_items::{InvokeParams, MenuItem, SelectedItem};
54pub use shell_item::ShellItems;
55
56#[cfg(feature = "ffi")]
57pub use ffi::*;
58
59use std::path::Path;
60
61pub fn init_com() -> Result<ComGuard> {
79 ComGuard::new()
80}
81
82pub fn show_context_menu(path: impl AsRef<Path>) -> Result<Option<SelectedItem>> {
93 let items = ShellItems::from_path(path)?;
94 ContextMenu::new(items)?.show()
95}
96
97pub fn show_extended_context_menu(path: impl AsRef<Path>) -> Result<Option<SelectedItem>> {
100 let items = ShellItems::from_path(path)?;
101 ContextMenu::new(items)?.extended(true).show()
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_com_guard_init_and_drop() {
110 let guard = init_com();
111 assert!(guard.is_ok());
112 }
113
114 #[test]
115 fn test_shell_items_from_path() {
116 let _com = init_com().unwrap();
117 let result = ShellItems::from_path(r"C:\Windows\notepad.exe");
118 assert!(result.is_ok());
119 }
120
121 #[test]
122 fn test_shell_items_nonexistent() {
123 let _com = init_com().unwrap();
124 let result = ShellItems::from_path(r"C:\This\Does\Not\Exist\file.txt");
125 assert!(result.is_err());
126 }
127
128 #[test]
129 fn test_enumerate_notepad() {
130 let _com = init_com().unwrap();
131 let items = ShellItems::from_path(r"C:\Windows\notepad.exe").unwrap();
132 let menu = ContextMenu::new(items).unwrap();
133 let entries = menu.enumerate().unwrap();
134
135 assert!(!entries.is_empty(), "Context menu should have items");
136
137 let has_properties = entries.iter().any(|e| {
138 e.command_string
139 .as_deref()
140 .is_some_and(|s| s == "properties")
141 });
142 assert!(has_properties, "Should have a 'properties' verb");
143 }
144
145 #[test]
146 fn test_enumerate_extended() {
147 let _com = init_com().unwrap();
148 let items = ShellItems::from_path(r"C:\Windows\notepad.exe").unwrap();
149 let normal = ContextMenu::new(items).unwrap().enumerate().unwrap();
150
151 let items2 = ShellItems::from_path(r"C:\Windows\notepad.exe").unwrap();
152 let extended = ContextMenu::new(items2)
153 .unwrap()
154 .extended(true)
155 .enumerate()
156 .unwrap();
157
158 assert!(
159 extended.len() >= normal.len(),
160 "Extended menu should have at least as many items as normal"
161 );
162 }
163
164 #[test]
165 fn test_folder_background() {
166 let _com = init_com().unwrap();
167 let result = ShellItems::folder_background(r"C:\Windows");
168 assert!(result.is_ok());
169 }
170
171 #[test]
172 fn test_multi_paths_same_folder() {
173 let _com = init_com().unwrap();
174 let result = ShellItems::from_paths(&[
175 r"C:\Windows\notepad.exe",
176 r"C:\Windows\regedit.exe",
177 ]);
178 assert!(result.is_ok());
179 }
180
181 #[test]
182 fn test_multi_paths_different_folders_fails() {
183 let _com = init_com().unwrap();
184 let result = ShellItems::from_paths(&[
185 r"C:\Windows\notepad.exe",
186 r"C:\Users",
187 ]);
188 assert!(result.is_err());
189 }
190}