use super::{DropReadError, deliver_dropped_files, warn_read_failed};
use crate::ecs::window::OnFilesDropped;
use crate::ecs::window_proc::lifecycle::DESPAWNED_SKIP_TAG;
use crate::ecs::world::EcsWorld;
use bevy_ecs::prelude::*;
use log_capture_kit::CapturedEvent;
use std::cell::RefCell;
use std::path::PathBuf;
use std::rc::Rc;
use tracing::Level;
use windows::Win32::Foundation::LRESULT;
#[derive(Resource, Default)]
struct Received(Vec<Vec<PathBuf>>);
fn record_drop(world: &mut World, _entity: Entity, paths: Vec<PathBuf>) {
world
.get_resource_or_insert_with(Received::default)
.0
.push(paths);
}
fn drop_world() -> (Rc<RefCell<EcsWorld>>, Entity, Entity) {
let world = Rc::new(RefCell::new(EcsWorld::new()));
let (hooked, plain) = {
let mut w = world.borrow_mut();
let w = w.world_mut();
let hooked = w.spawn(OnFilesDropped(record_drop)).id();
let plain = w.spawn_empty().id();
(hooked, plain)
};
(world, hooked, plain)
}
fn fake_paths() -> Vec<PathBuf> {
vec![
PathBuf::from(r"C:\drop\a.txt"),
PathBuf::from(r"C:\drop\フォルダ"),
]
}
fn received(world: &Rc<RefCell<EcsWorld>>) -> Option<Vec<Vec<PathBuf>>> {
world
.borrow()
.world()
.get_resource::<Received>()
.map(|r| r.0.clone())
}
fn events_named<'a>(events: &'a [CapturedEvent], name: &str) -> Vec<&'a CapturedEvent> {
events
.iter()
.filter(|e| e.field_str("event") == Some(name))
.collect()
}
fn warn_count(events: &[CapturedEvent]) -> usize {
events.iter().filter(|e| e.level <= Level::WARN).count()
}
#[test]
fn deliver_calls_receiver_once_with_the_same_list() {
let (world, hooked, _) = drop_world();
let (ret, events) =
log_capture_kit::capture(|| deliver_dropped_files(&world, hooked, fake_paths()));
assert_eq!(ret, Some(LRESULT(0)));
assert_eq!(
received(&world),
Some(vec![fake_paths()]),
"受け手が同じ一覧で 1 回だけ呼ばれていない"
);
let hits = events_named(&events, "files_dropped");
assert_eq!(
hits.len(),
1,
"files_dropped がちょうど 1 行でない: {events:?}"
);
assert_eq!(hits[0].level, Level::INFO);
assert_eq!(hits[0].field("count"), Some("2"));
assert_eq!(warn_count(&events), 0, "警告以上が出ている: {events:?}");
}
#[test]
fn deliver_without_receiver_only_logs_debug() {
let (world, _, plain) = drop_world();
let (ret, events) =
log_capture_kit::capture(|| deliver_dropped_files(&world, plain, fake_paths()));
assert_eq!(ret, Some(LRESULT(0)));
assert_eq!(received(&world), None, "部品の無い窓で受け手が呼ばれた");
let levels: Vec<Level> = events_named(&events, "files_dropped_no_receiver")
.iter()
.map(|e| e.level)
.collect();
assert_eq!(levels, vec![Level::DEBUG]);
assert!(events_named(&events, "files_dropped").is_empty());
assert_eq!(warn_count(&events), 0, "警告以上が出ている: {events:?}");
}
#[test]
fn deliver_on_despawned_entity_skips_as_normal_teardown() {
let ((), probe) = log_capture_kit::capture(|| {
tracing::warn!("[harness-selfcheck] 捕捉可能性の自己証明");
});
assert_eq!(
warn_count(&probe),
1,
"捕捉が tracing の WARN を見られない。後段の「警告 0」が空虚になる: {probe:?}"
);
let (world, hooked, _) = drop_world();
assert!(world.borrow_mut().world_mut().despawn(hooked));
let (ret, events) =
log_capture_kit::capture(|| deliver_dropped_files(&world, hooked, fake_paths()));
assert_eq!(ret, Some(LRESULT(0)));
assert_eq!(
received(&world),
None,
"破棄済みの entity で受け手が呼ばれた"
);
let skips: Vec<&CapturedEvent> = events
.iter()
.filter(|e| e.message().contains(DESPAWNED_SKIP_TAG))
.collect();
assert_eq!(
skips.len(),
1,
"破棄済みの打ち切りが 1 行でない: {events:?}"
);
assert_eq!(skips[0].level, Level::DEBUG);
assert!(
skips[0].message().contains("WM_DROPFILES"),
"打ち切り行が自分の相(WM_DROPFILES)を名乗っていない: {:?}",
skips[0]
);
assert_eq!(warn_count(&events), 0, "警告以上が出ている: {events:?}");
}
#[test]
fn deliver_while_world_borrowed_warns_without_call() {
let (world, hooked, _) = drop_world();
let held = world.borrow_mut();
let (ret, events) =
log_capture_kit::capture(|| deliver_dropped_files(&world, hooked, fake_paths()));
drop(held);
assert_eq!(ret, Some(LRESULT(0)));
assert_eq!(received(&world), None, "借用中に受け手が呼ばれた");
let hits = events_named(&events, "files_dropped_world_busy");
assert_eq!(
hits.len(),
1,
"files_dropped_world_busy が 1 行でない: {events:?}"
);
assert_eq!(hits[0].level, Level::WARN);
assert_eq!(hits[0].field("count"), Some("2"));
}
#[test]
fn dispatch_null_hdrop_warns_once_without_call() {
use crate::ecs::window_proc::dispatch_window_message;
use crate::executor::util::WindowMessage;
use windows::Win32::Foundation::{HWND, LPARAM, WPARAM};
use windows::Win32::UI::WindowsAndMessaging::WM_DROPFILES;
let (world, hooked, _) = drop_world();
let m = WindowMessage {
hwnd: HWND(std::ptr::null_mut()),
msg: WM_DROPFILES,
wparam: WPARAM(0),
lparam: LPARAM(0),
};
let (ret, events) = log_capture_kit::capture(|| dispatch_window_message(&world, hooked, &m));
assert_eq!(ret, Some(LRESULT(0)), "WM_DROPFILES が既定処理へ流れた");
assert_eq!(received(&world), None, "取っ手 0 で受け手が呼ばれた");
let hits = events_named(&events, "files_dropped_read_failed");
assert_eq!(
hits.len(),
1,
"files_dropped_read_failed がちょうど 1 行でない: {events:?}"
);
assert_eq!(hits[0].level, Level::WARN);
assert!(
hits[0]
.field("error")
.is_some_and(|e| e.contains("NullHandle")),
"error 欄が NullHandle を名乗っていない: {:?}",
hits[0]
);
assert_eq!(warn_count(&events), 1, "警告が 1 行でない: {events:?}");
}
#[test]
fn read_error_variants_are_distinguishable_in_the_record() {
let (_, hooked, _) = drop_world();
let ((), events) = log_capture_kit::capture(|| {
warn_read_failed(hooked, &DropReadError::NullHandle);
warn_read_failed(hooked, &DropReadError::Query { index: 1, total: 3 });
});
let errors: Vec<&str> = events_named(&events, "files_dropped_read_failed")
.iter()
.map(|e| {
assert_eq!(e.level, Level::WARN);
e.field("error").unwrap_or("")
})
.collect();
assert_eq!(errors.len(), 2, "2 行でない: {events:?}");
assert!(errors[0].contains("NullHandle"), "{errors:?}");
assert!(
errors[1].contains("Query") && errors[1].contains('1') && errors[1].contains('3'),
"Query が何番目・何本のうちかを載せていない: {errors:?}"
);
assert_ne!(errors[0], errors[1]);
}