Merge pull request #2 from JMR-dev/fix-test-coverage

Test additions
This commit was merged in pull request #2.
This commit is contained in:
Jason Ross
2026-06-17 16:05:36 -05:00
committed by GitHub
3 changed files with 271 additions and 0 deletions
+53
View File
@@ -79,3 +79,56 @@ pub fn extract_icons(worker: &ShellWorker, keys: Vec<IconKey>) -> Vec<(IconKey,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn ext(name: &str) -> IconKey {
IconKey::Ext(name.to_string())
}
#[test]
fn take_unrequested_returns_each_key_once_across_calls() {
let mut cache = IconCache::default();
// First sight of these keys: all are handed back for extraction.
let first = cache.take_unrequested([IconKey::Folder, ext("txt")]);
assert_eq!(first, vec![IconKey::Folder, ext("txt")]);
// They're now in-flight, so asking again yields nothing — each icon is
// only extracted once, even before its handle has come back.
assert!(
cache
.take_unrequested([IconKey::Folder, ext("txt")])
.is_empty()
);
// A genuinely new key still comes through.
assert_eq!(cache.take_unrequested([ext("rs")]), vec![ext("rs")]);
}
#[test]
fn take_unrequested_dedupes_within_a_single_call() {
let mut cache = IconCache::default();
// The same key repeated in one batch is requested only once.
let out = cache.take_unrequested([ext("txt"), ext("txt"), IconKey::Folder]);
assert_eq!(out, vec![ext("txt"), IconKey::Folder]);
}
#[test]
fn cached_keys_are_not_requested_again() {
let mut cache = IconCache::default();
let image = IconImage {
width: 1,
height: 1,
rgba: vec![0, 0, 0, 0],
};
assert!(cache.get(&IconKey::Folder).is_none());
cache.insert(IconKey::Folder, image);
// Once a handle is cached, get() resolves it and take_unrequested skips it.
assert!(cache.get(&IconKey::Folder).is_some());
assert!(cache.take_unrequested([IconKey::Folder]).is_empty());
}
}
+187
View File
@@ -194,6 +194,29 @@ pub fn format_time(time: SystemTime) -> String {
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::time::{Duration, UNIX_EPOCH};
use librarian_core::{Attributes, EntryKind};
fn file_entry(name: &str, size: u64) -> Entry {
Entry {
name: name.to_string(),
path: PathBuf::from(format!(r"C:\dir\{name}")),
kind: EntryKind::File,
size,
modified: None,
created: None,
attrs: Attributes::default(),
}
}
fn dir_entry(name: &str) -> Entry {
Entry {
kind: EntryKind::Directory,
..file_entry(name, 0)
}
}
#[test]
fn human_size_scales_units() {
@@ -202,4 +225,168 @@ mod tests {
assert_eq!(human_size(1024), "1.0 KB");
assert_eq!(human_size(1_500_000), "1.4 MB");
}
#[test]
fn type_label_is_folder_or_uppercase_extension() {
assert_eq!(type_label(&dir_entry("Photos")), "File folder");
assert_eq!(type_label(&file_entry("notes.txt", 0)), "TXT File");
// No extension falls back to the bare "File".
assert_eq!(type_label(&file_entry("README", 0)), "File");
// A leading-dot name (no real extension) is also just "File".
assert_eq!(type_label(&file_entry(".gitignore", 0)), "File");
}
#[test]
fn file_row_carries_size_type_and_extension_icon() {
let row = row_from_entry(&file_entry("notes.txt", 42));
assert_eq!(row.label, "notes.txt");
assert!(!row.is_container);
assert_eq!(row.size, Some(42));
assert_eq!(row.type_label, "TXT File");
assert_eq!(row.icon, IconKey::Ext("txt".to_string()));
assert_eq!(
row.target,
Location::Path(PathBuf::from(r"C:\dir\notes.txt"))
);
}
#[test]
fn dir_row_is_a_container_with_no_size() {
let row = row_from_entry(&dir_entry("Photos"));
assert!(row.is_container);
assert_eq!(row.size, None);
assert_eq!(row.icon, IconKey::Folder);
assert_eq!(row.type_label, "File folder");
}
#[test]
fn drive_row_formats_label_and_falls_back_to_kind() {
let drive = DriveInfo {
letter: 'C',
root: PathBuf::from(r"C:\"),
label: "Windows".to_string(),
kind: DriveKind::Fixed,
total_bytes: 0,
free_bytes: 0,
};
let row = row_from_drive(&drive);
assert_eq!(row.label, "Windows (C:)");
assert_eq!(row.type_label, "Local Disk");
assert!(row.is_container);
assert_eq!(row.size, None);
assert_eq!(row.icon, IconKey::Path(PathBuf::from(r"C:\")));
// An unlabeled drive shows its kind in place of a name.
let unlabeled = DriveInfo {
label: String::new(),
kind: DriveKind::Removable,
..drive
};
let row = row_from_drive(&unlabeled);
assert_eq!(row.label, "Removable Disk (C:)");
assert_eq!(row.type_label, "Removable Disk");
}
#[test]
fn drive_kind_names_cover_every_variant() {
let cases = [
(DriveKind::Fixed, "Local Disk"),
(DriveKind::Removable, "Removable Disk"),
(DriveKind::Network, "Network Drive"),
(DriveKind::CdRom, "CD Drive"),
(DriveKind::RamDisk, "RAM Disk"),
(DriveKind::Unknown, "Disk"),
];
for (kind, expected) in cases {
assert_eq!(drive_kind_name(kind), expected);
}
}
#[test]
fn known_folder_row_uses_its_own_shell_icon() {
let folder = KnownFolder {
name: "Downloads",
path: PathBuf::from(r"C:\Users\j\Downloads"),
};
let row = row_from_known(&folder);
assert_eq!(row.label, "Downloads");
assert_eq!(row.type_label, "File folder");
assert!(row.is_container);
assert_eq!(
row.icon,
IconKey::Path(PathBuf::from(r"C:\Users\j\Downloads"))
);
}
#[test]
fn distro_row_targets_the_unc_root_with_the_wsl_icon() {
let distro = WslDistro {
name: "Ubuntu".to_string(),
};
let row = row_from_distro(&distro);
assert_eq!(row.label, "Ubuntu");
assert_eq!(row.icon, IconKey::Wsl);
assert_eq!(row.type_label, "Linux distribution");
assert_eq!(row.target, Location::Path(distro_unc_path("Ubuntu")));
}
#[test]
fn search_hit_label_is_relative_to_the_search_root() {
let root = Path::new(r"C:\proj");
let hit = SearchHit {
path: PathBuf::from(r"C:\proj\src\main.rs"),
matches: None,
is_dir: false,
};
let row = row_from_hit(&hit, root);
// The path is shown relative to the root, like an editor's search panel.
assert_eq!(row.label, r"src\main.rs");
assert!(!row.is_container);
assert_eq!(row.type_label, "RS File");
assert_eq!(row.icon, IconKey::Ext("rs".to_string()));
}
#[test]
fn search_hit_directory_navigates() {
let root = Path::new(r"C:\proj");
let hit = SearchHit {
path: PathBuf::from(r"C:\proj\src"),
matches: None,
is_dir: true,
};
let row = row_from_hit(&hit, root);
assert!(row.is_container);
assert_eq!(row.type_label, "File folder");
assert_eq!(row.icon, IconKey::Folder);
}
#[test]
fn contents_hit_reports_match_count_with_pluralization() {
let root = Path::new(r"C:\proj");
let hit = |n: u64| SearchHit {
path: PathBuf::from(r"C:\proj\notes.txt"),
matches: Some(n),
is_dir: false,
};
assert_eq!(row_from_hit(&hit(1), root).type_label, "1 match");
assert_eq!(row_from_hit(&hit(3), root).type_label, "3 matches");
}
#[test]
fn format_time_renders_minute_precision() {
// Local time varies by host, so assert the shape, not an exact instant:
// `YYYY-MM-DD HH:MM`, exactly 16 chars, all fields zero-padded.
let t = UNIX_EPOCH + Duration::from_secs(1_700_000_000);
let s = format_time(t);
assert_eq!(s.len(), 16, "expected `YYYY-MM-DD HH:MM`, got {s:?}");
let bytes = s.as_bytes();
for (i, b) in bytes.iter().enumerate() {
match i {
4 | 7 => assert_eq!(*b, b'-'),
10 => assert_eq!(*b, b' '),
13 => assert_eq!(*b, b':'),
_ => assert!(b.is_ascii_digit(), "non-digit at {i} in {s:?}"),
}
}
}
}
+31
View File
@@ -11,3 +11,34 @@ pub(crate) fn wide_to_string(buf: &[u16]) -> String {
let len = buf.iter().position(|&c| c == 0).unwrap_or(buf.len());
String::from_utf16_lossy(&buf[..len])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_wide_encodes_and_nul_terminates() {
// ASCII encodes one u16 per char, with a trailing NUL the FFI needs.
assert_eq!(to_wide("Hi"), vec![0x48, 0x69, 0x00]);
// The empty string is just the terminator.
assert_eq!(to_wide(""), vec![0x00]);
}
#[test]
fn wide_to_string_stops_at_first_nul() {
// Bytes past the NUL (here a stray 'X') are ignored — buffers come back
// from Win32 padded with garbage after the terminator.
assert_eq!(wide_to_string(&[0x48, 0x69, 0x00, 0x58]), "Hi");
// No NUL at all: the whole buffer is decoded.
assert_eq!(wide_to_string(&[0x48, 0x69]), "Hi");
// A leading NUL is the empty string.
assert_eq!(wide_to_string(&[0x00, 0x69]), "");
}
#[test]
fn round_trips_through_utf16_including_non_ascii() {
for s in ["", "C:\\Windows", "Ubuntu", "café — résumé", "日本語"] {
assert_eq!(wide_to_string(&to_wide(s)), s);
}
}
}