1#![allow(dead_code)]
23
24#[cfg(feature = "alloc")]
25extern crate alloc;
26
27pub mod cjk;
28pub mod clipboard;
29pub mod dnd;
30pub mod font_render;
31pub mod shortcuts;
32pub mod theme;
33
34pub use cjk::{char_width, is_cjk_wide, CellContent, ImeState};
39#[cfg(feature = "alloc")]
40pub use cjk::{string_width, truncate_to_width, ImeCandidate, InputMethodEditor};
41#[cfg(feature = "alloc")]
42pub use clipboard::ClipboardManager;
43pub use clipboard::{ClipboardEntry, ClipboardError, ClipboardMime, SelectionType};
44pub use dnd::{DndError, DndEvent, DndState};
46#[cfg(feature = "alloc")]
47pub use dnd::{DndManager, DragSource, DropTarget};
48#[cfg(feature = "alloc")]
49pub use font_render::{
50 apply_hinting, rasterize_outline, render_glyph, GlyphBitmap, GlyphCache, GlyphContour,
51 GlyphOutline,
52};
53pub use font_render::{
55 FontError, HeadTable, HheaTable, HintingMode, MaxpTable, OutlinePoint, SubpixelMode,
56 TableEntry, TableTag, TtfParser,
57};
58#[cfg(feature = "alloc")]
59pub use shortcuts::ShortcutManager;
60pub use shortcuts::{KeyBinding, KeyCode, ModifierMask, ShortcutAction, ShortcutPriority};
62pub use theme::{IconTheme, StyleProperty, ThemeColor, ThemeColors, ThemeManager, ThemePreset};
64
65#[cfg(test)]
70mod tests {
71 #[allow(unused_imports)]
72 use alloc::{string::String, vec, vec::Vec};
73
74 use super::{
75 clipboard::{CLIPBOARD_HISTORY_MAX, CLIPBOARD_MAX_DATA_SIZE},
76 font_render::{read_u16_be, read_u32_be, GLYPH_CACHE_SIZE},
77 *,
78 };
79
80 #[test]
83 fn test_clipboard_copy_paste() {
84 let mut mgr = ClipboardManager::new();
85 let data = vec![0x48, 0x65, 0x6C, 0x6C, 0x6F]; mgr.copy(
87 SelectionType::Clipboard,
88 1,
89 ClipboardMime::TextPlain,
90 data.clone(),
91 )
92 .unwrap();
93 let result = mgr
94 .paste(SelectionType::Clipboard, ClipboardMime::TextPlain)
95 .unwrap();
96 assert_eq!(result, &data[..]);
97 }
98
99 #[test]
100 fn test_clipboard_paste_empty() {
101 let mgr = ClipboardManager::new();
102 assert_eq!(
103 mgr.paste(SelectionType::Clipboard, ClipboardMime::TextPlain),
104 Err(ClipboardError::Empty)
105 );
106 }
107
108 #[test]
109 fn test_clipboard_paste_wrong_mime() {
110 let mut mgr = ClipboardManager::new();
111 mgr.copy(
112 SelectionType::Clipboard,
113 1,
114 ClipboardMime::TextPlain,
115 vec![1, 2, 3],
116 )
117 .unwrap();
118 assert_eq!(
119 mgr.paste(SelectionType::Clipboard, ClipboardMime::ImagePng),
120 Err(ClipboardError::MimeNotFound)
121 );
122 }
123
124 #[test]
125 fn test_clipboard_primary_selection() {
126 let mut mgr = ClipboardManager::new();
127 mgr.copy(SelectionType::Primary, 1, ClipboardMime::TextPlain, vec![1])
128 .unwrap();
129 assert!(mgr.has_data(SelectionType::Primary));
130 assert!(!mgr.has_data(SelectionType::Clipboard));
131 }
132
133 #[test]
134 fn test_clipboard_history() {
135 let mut mgr = ClipboardManager::new();
136 for i in 0..10u8 {
137 mgr.copy(
138 SelectionType::Clipboard,
139 1,
140 ClipboardMime::TextPlain,
141 vec![i],
142 )
143 .unwrap();
144 }
145 assert!(mgr.history().len() <= CLIPBOARD_HISTORY_MAX);
147 }
148
149 #[test]
150 fn test_clipboard_data_too_large() {
151 let mut mgr = ClipboardManager::new();
152 let big = vec![0u8; CLIPBOARD_MAX_DATA_SIZE + 1];
153 assert_eq!(
154 mgr.copy(SelectionType::Clipboard, 1, ClipboardMime::TextPlain, big),
155 Err(ClipboardError::DataTooLarge)
156 );
157 }
158
159 #[test]
160 fn test_clipboard_negotiate_mime() {
161 let mut mgr = ClipboardManager::new();
162 mgr.copy(
163 SelectionType::Clipboard,
164 1,
165 ClipboardMime::TextHtml,
166 vec![1],
167 )
168 .unwrap();
169 let result = mgr.negotiate_mime(
170 SelectionType::Clipboard,
171 &[ClipboardMime::TextPlain, ClipboardMime::TextHtml],
172 );
173 assert_eq!(result, Some(ClipboardMime::TextHtml));
174 }
175
176 #[test]
177 fn test_clipboard_clear() {
178 let mut mgr = ClipboardManager::new();
179 mgr.copy(
180 SelectionType::Clipboard,
181 1,
182 ClipboardMime::TextPlain,
183 vec![1],
184 )
185 .unwrap();
186 mgr.clear(SelectionType::Clipboard);
187 assert!(!mgr.has_data(SelectionType::Clipboard));
188 }
189
190 #[test]
191 fn test_clipboard_restore_history() {
192 let mut mgr = ClipboardManager::new();
193 mgr.copy(
194 SelectionType::Clipboard,
195 1,
196 ClipboardMime::TextPlain,
197 vec![1],
198 )
199 .unwrap();
200 mgr.copy(
201 SelectionType::Clipboard,
202 1,
203 ClipboardMime::TextPlain,
204 vec![2],
205 )
206 .unwrap();
207 mgr.restore_from_history(0).unwrap();
209 let result = mgr
210 .paste(SelectionType::Clipboard, ClipboardMime::TextPlain)
211 .unwrap();
212 assert_eq!(result, &[1]);
213 }
214
215 #[test]
218 fn test_dnd_start_drag() {
219 let mut dnd = DndManager::new();
220 dnd.start_drag(1, vec![ClipboardMime::TextPlain], 10, 20, 32, 32)
221 .unwrap();
222 assert_eq!(dnd.state(), DndState::Dragging);
223 }
224
225 #[test]
226 fn test_dnd_double_drag_error() {
227 let mut dnd = DndManager::new();
228 dnd.start_drag(1, vec![ClipboardMime::TextPlain], 0, 0, 32, 32)
229 .unwrap();
230 assert_eq!(
231 dnd.start_drag(2, vec![], 0, 0, 32, 32),
232 Err(DndError::AlreadyDragging)
233 );
234 }
235
236 #[test]
237 fn test_dnd_motion_no_drag() {
238 let mut dnd = DndManager::new();
239 assert_eq!(dnd.motion(10, 10), Err(DndError::NotDragging));
240 }
241
242 #[test]
243 fn test_dnd_enter_leave_events() {
244 let mut dnd = DndManager::new();
245 dnd.register_target(DropTarget {
246 surface_id: 42,
247 accepted_mimes: vec![ClipboardMime::TextPlain],
248 x: 100,
249 y: 100,
250 width: 200,
251 height: 200,
252 });
253 dnd.start_drag(1, vec![ClipboardMime::TextPlain], 0, 0, 32, 32)
254 .unwrap();
255 dnd.drain_events(); dnd.motion(150, 150).unwrap();
259 let events = dnd.drain_events();
260 assert!(events
261 .iter()
262 .any(|e| matches!(e, DndEvent::Enter { surface_id: 42, .. })));
263
264 dnd.motion(0, 0).unwrap();
266 let events = dnd.drain_events();
267 assert!(events
268 .iter()
269 .any(|e| matches!(e, DndEvent::Leave { surface_id: 42 })));
270 }
271
272 #[test]
273 fn test_dnd_drop_action() {
274 let mut dnd = DndManager::new();
275 dnd.register_target(DropTarget {
276 surface_id: 5,
277 accepted_mimes: vec![ClipboardMime::TextPlain],
278 x: 0,
279 y: 0,
280 width: 100,
281 height: 100,
282 });
283 dnd.start_drag(1, vec![ClipboardMime::TextPlain], 50, 50, 16, 16)
284 .unwrap();
285 dnd.motion(50, 50).unwrap();
286 let result = dnd.drop_action();
287 assert!(result.is_ok());
288 }
289
290 #[test]
291 fn test_dnd_cancel() {
292 let mut dnd = DndManager::new();
293 dnd.start_drag(1, vec![], 0, 0, 10, 10).unwrap();
294 dnd.cancel();
295 assert_eq!(dnd.state(), DndState::Idle);
296 }
297
298 #[test]
299 fn test_drop_target_contains() {
300 let t = DropTarget {
301 surface_id: 1,
302 accepted_mimes: vec![],
303 x: 10,
304 y: 20,
305 width: 100,
306 height: 50,
307 };
308 assert!(t.contains(10, 20));
309 assert!(t.contains(109, 69));
310 assert!(!t.contains(110, 20));
311 assert!(!t.contains(5, 20));
312 }
313
314 #[test]
317 fn test_shortcut_manager_defaults() {
318 let mgr = ShortcutManager::new();
319 assert!(mgr.binding_count() > 0);
321 }
322
323 #[test]
324 fn test_shortcut_process_key() {
325 let mgr = ShortcutManager::new();
326 let result = mgr.process_key(ModifierMask::ALT, 0x0F);
328 assert_eq!(result, Some(ShortcutAction::SwitchNextWindow));
329 }
330
331 #[test]
332 fn test_shortcut_no_match() {
333 let mgr = ShortcutManager::new();
334 let result = mgr.process_key(ModifierMask::NONE, 0x99);
335 assert_eq!(result, None);
336 }
337
338 #[test]
339 fn test_shortcut_register_unregister() {
340 let mut mgr = ShortcutManager::new();
341 let count = mgr.binding_count();
342 let id = mgr.register(KeyBinding::new(
343 ModifierMask::CTRL,
344 0x1E,
345 ShortcutAction::Custom(42),
346 ));
347 assert_eq!(mgr.binding_count(), count + 1);
348 mgr.unregister(id);
349 assert_eq!(mgr.binding_count(), count);
350 }
351
352 #[test]
353 fn test_shortcut_disabled() {
354 let mut mgr = ShortcutManager::new();
355 mgr.set_enabled(false);
356 let result = mgr.process_key(ModifierMask::ALT, 0x0F);
357 assert_eq!(result, None);
358 }
359
360 #[test]
361 fn test_modifier_mask_combine() {
362 let m = ModifierMask::CTRL.combine(ModifierMask::ALT);
363 assert!(m.has(ModifierMask::CTRL));
364 assert!(m.has(ModifierMask::ALT));
365 assert!(!m.has(ModifierMask::SHIFT));
366 }
367
368 #[test]
371 fn test_theme_default_dark() {
372 let mgr = ThemeManager::new();
373 assert_eq!(mgr.current_preset(), ThemePreset::Dark);
374 }
375
376 #[test]
377 fn test_theme_switch() {
378 let mut mgr = ThemeManager::new();
379 mgr.set_theme(ThemePreset::Nord);
380 assert_eq!(mgr.current_preset(), ThemePreset::Nord);
381 assert_eq!(mgr.colors().accent, ThemeColor::from_rgb(0x88, 0xC0, 0xD0));
383 }
384
385 #[test]
386 fn test_theme_all_presets_load() {
387 let mut mgr = ThemeManager::new();
388 let presets = [
389 ThemePreset::Light,
390 ThemePreset::Dark,
391 ThemePreset::SolarizedDark,
392 ThemePreset::SolarizedLight,
393 ThemePreset::Nord,
394 ThemePreset::Dracula,
395 ];
396 for preset in &presets {
397 mgr.set_theme(*preset);
398 assert_eq!(mgr.current_preset(), *preset);
399 }
400 }
401
402 #[test]
403 fn test_theme_color_components() {
404 let c = ThemeColor::from_argb(0x80, 0xFF, 0x00, 0xAA);
405 assert_eq!(c.alpha(), 0x80);
406 assert_eq!(c.red(), 0xFF);
407 assert_eq!(c.green(), 0x00);
408 assert_eq!(c.blue(), 0xAA);
409 }
410
411 #[test]
412 fn test_theme_color_darken() {
413 let c = ThemeColor::from_rgb(100, 200, 50);
414 let d = c.darken(50);
415 assert_eq!(d.red(), 50);
416 assert_eq!(d.green(), 100);
417 assert_eq!(d.blue(), 25);
418 }
419
420 #[test]
421 fn test_theme_style_property() {
422 let mgr = ThemeManager::new();
423 assert_eq!(mgr.map_style_property(StyleProperty::FontSize), 14);
424 assert_eq!(mgr.map_style_property(StyleProperty::BorderWidth), 1);
425 }
426
427 #[test]
428 fn test_theme_gtk_name() {
429 let mut mgr = ThemeManager::new();
430 mgr.set_theme(ThemePreset::Dracula);
431 assert_eq!(mgr.gtk_theme_name(), "Dracula");
432 }
433
434 #[test]
437 fn test_ttf_parser_invalid_data() {
438 let result = TtfParser::new(&[0, 1, 2, 3]);
439 assert!(result.is_err());
440 }
441
442 #[test]
443 fn test_ttf_parser_empty() {
444 let result = TtfParser::new(&[]);
445 assert!(matches!(result, Err(FontError::InvalidFont)));
446 }
447
448 #[test]
449 fn test_read_u16_be() {
450 assert_eq!(read_u16_be(&[0x01, 0x02], 0), Some(0x0102));
451 assert_eq!(read_u16_be(&[0xFF, 0x00], 0), Some(0xFF00));
452 assert_eq!(read_u16_be(&[0x01], 0), None);
453 }
454
455 #[test]
456 fn test_read_u32_be() {
457 assert_eq!(read_u32_be(&[0x00, 0x01, 0x00, 0x00], 0), Some(0x00010000));
458 assert_eq!(read_u32_be(&[0x01, 0x02], 0), None);
459 }
460
461 #[test]
462 fn test_glyph_cache_insert_lookup() {
463 let mut cache = GlyphCache::new();
464 let bmp = GlyphBitmap {
465 data: vec![128; 16],
466 width: 4,
467 height: 4,
468 bearing_x: 0,
469 bearing_y: 4,
470 advance: 5,
471 };
472 cache.insert(65, 16, bmp.clone());
473 assert_eq!(cache.len(), 1);
474 let result = cache.get(65, 16);
475 assert!(result.is_some());
476 assert_eq!(result.unwrap().width, 4);
477 }
478
479 #[test]
480 fn test_glyph_cache_miss() {
481 let mut cache = GlyphCache::new();
482 assert!(cache.get(65, 16).is_none());
483 }
484
485 #[test]
486 fn test_glyph_cache_hit_rate() {
487 let mut cache = GlyphCache::new();
488 cache.insert(
489 65,
490 16,
491 GlyphBitmap {
492 data: vec![0; 4],
493 width: 2,
494 height: 2,
495 bearing_x: 0,
496 bearing_y: 2,
497 advance: 3,
498 },
499 );
500 cache.get(65, 16); cache.get(66, 16); assert_eq!(cache.hit_rate_percent(), 50);
503 }
504
505 #[test]
506 fn test_glyph_cache_eviction() {
507 let mut cache = GlyphCache::new();
508 for i in 0..GLYPH_CACHE_SIZE + 10 {
509 cache.insert(
510 i as u32,
511 12,
512 GlyphBitmap {
513 data: vec![0; 1],
514 width: 1,
515 height: 1,
516 bearing_x: 0,
517 bearing_y: 1,
518 advance: 1,
519 },
520 );
521 }
522 assert!(cache.len() <= GLYPH_CACHE_SIZE);
523 }
524
525 #[test]
526 fn test_rasterize_empty_outline() {
527 let outline = GlyphOutline {
528 contours: Vec::new(),
529 x_min: 0,
530 y_min: 0,
531 x_max: 0,
532 y_max: 0,
533 advance_width: 0,
534 lsb: 0,
535 };
536 let bmp = rasterize_outline(&outline, 16, 2048);
537 assert_eq!(bmp.width, 0);
538 assert_eq!(bmp.height, 0);
539 }
540
541 #[test]
542 fn test_table_tag_constants() {
543 assert_eq!(TableTag::CMAP.0, *b"cmap");
544 assert_eq!(TableTag::HEAD.0, *b"head");
545 assert_eq!(TableTag::GLYF.0, *b"glyf");
546 }
547
548 #[test]
551 fn test_is_cjk_wide_basic() {
552 assert!(is_cjk_wide('\u{4E00}')); assert!(is_cjk_wide('\u{9FFF}')); assert!(is_cjk_wide('\u{AC00}')); assert!(is_cjk_wide('\u{3042}')); assert!(is_cjk_wide('\u{30A2}')); assert!(is_cjk_wide('\u{FF01}')); }
559
560 #[test]
561 fn test_is_cjk_wide_false() {
562 assert!(!is_cjk_wide('A'));
563 assert!(!is_cjk_wide('z'));
564 assert!(!is_cjk_wide(' '));
565 assert!(!is_cjk_wide('\u{00E9}')); }
567
568 #[test]
569 fn test_char_width() {
570 assert_eq!(char_width('A'), 1);
571 assert_eq!(char_width('\u{4E00}'), 2);
572 assert_eq!(char_width('\0'), 0);
573 assert_eq!(char_width('\u{0300}'), 0); assert_eq!(char_width('\u{200B}'), 0); }
576
577 #[test]
578 fn test_string_width() {
579 assert_eq!(string_width("Hello"), 5);
580 assert_eq!(string_width("\u{4F60}\u{597D}"), 4); assert_eq!(string_width("A\u{4E00}B"), 4); }
583
584 #[test]
585 fn test_truncate_to_width() {
586 let s = "Hello, World!";
587 let truncated = truncate_to_width(s, 10);
588 assert!(string_width(&truncated) <= 10);
589 }
590
591 #[test]
592 fn test_cell_content_default() {
593 assert_eq!(CellContent::default(), CellContent::Empty);
594 }
595
596 #[test]
599 fn test_ime_disabled_passthrough() {
600 let mut ime = InputMethodEditor::new();
601 ime.feed_char('a');
603 assert_eq!(ime.state(), ImeState::Committed);
604 assert_eq!(ime.take_committed(), "a");
605 }
606
607 #[test]
608 fn test_ime_composing() {
609 let mut ime = InputMethodEditor::new();
610 ime.set_enabled(true);
611 ime.feed_char('n');
612 ime.feed_char('i');
613 assert_eq!(ime.state(), ImeState::Composing);
614 assert_eq!(ime.preedit(), "ni");
615 assert!(!ime.candidates().is_empty());
616 }
617
618 #[test]
619 fn test_ime_select_candidate() {
620 let mut ime = InputMethodEditor::new();
621 ime.set_enabled(true);
622 ime.feed_char('n');
623 ime.feed_char('i');
624 ime.feed_char('1'); assert_eq!(ime.state(), ImeState::Committed);
626 let committed = ime.take_committed();
627 assert_eq!(committed, "\u{4F60}"); }
629
630 #[test]
631 fn test_ime_backspace() {
632 let mut ime = InputMethodEditor::new();
633 ime.set_enabled(true);
634 ime.feed_char('h');
635 ime.feed_char('a');
636 ime.feed_backspace();
637 assert_eq!(ime.preedit(), "h");
638 ime.feed_backspace();
639 assert_eq!(ime.state(), ImeState::Inactive);
640 }
641
642 #[test]
643 fn test_ime_escape_cancels() {
644 let mut ime = InputMethodEditor::new();
645 ime.set_enabled(true);
646 ime.feed_char('s');
647 ime.feed_char('h');
648 ime.feed_char('i');
649 ime.feed_escape();
650 assert_eq!(ime.state(), ImeState::Inactive);
651 assert!(ime.preedit().is_empty());
652 }
653
654 #[test]
655 fn test_ime_space_commits_first() {
656 let mut ime = InputMethodEditor::new();
657 ime.set_enabled(true);
658 ime.feed_char('w');
659 ime.feed_char('o');
660 ime.feed_char(' '); assert_eq!(ime.state(), ImeState::Committed);
662 let committed = ime.take_committed();
663 assert_eq!(committed, "\u{6211}"); }
665}