review(p9-fb-08): 회차 1 nit 반영
- `SearchState.worker_thread` 필드 제거 — `JoinHandle` 을 저장만 하고
어디서도 join 안 했음. fire_search 가 spawn 후 handle 을 즉시 drop
하면 OS 가 thread 를 detach (search 는 pure read 라 cleanup 의무
없음). YAGNI — ask.rs 의 thread 와 달리 cancel/observe 수요가 없는
fire-and-forget. doc 으로 의도 명시.
- `debounce_due` 가 `pub` 으로 노출 — 새 skip 분기 (`searching && 같은
query`) 회귀 테스트 추가:
- `debounce_due_skips_when_in_flight_for_same_query`: 같은 input/mode
재입력 시 spawn 안 함 (worker 누적 방지)
- `debounce_due_fires_when_in_flight_for_different_query`: 사용자가
in-flight 보다 빠르게 새 query 입력하면 정상 spawn (poll_worker 의
stale guard 가 이전 결과 처리)
- `search_state_with` 헬퍼: `SearchState::default()` + field 재할당
패턴이 clippy `field_reassign_with_default` 위반 → `#[allow(...)]`
로 lint 무시 (테스트 helper 의 가독성 우선).
23 tests/search.rs + 35 lib + 18 ask + 12 inspect + 10 library = 98
통과. clippy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ use kebab_core::{
|
||||
};
|
||||
use kebab_tui::{
|
||||
App, KeyOutcome, Pane, SearchState, SearchWorkerMessage, build_jump_command,
|
||||
handle_key_search, poll_search_worker, render_search,
|
||||
handle_key_search, poll_search_worker, render_search, search_debounce_due,
|
||||
};
|
||||
use ratatui::Terminal;
|
||||
use ratatui::backend::TestBackend;
|
||||
@@ -410,6 +410,56 @@ fn poll_worker_noop_when_no_rx() {
|
||||
assert!(s.worker_rx.is_none());
|
||||
}
|
||||
|
||||
/// Helper for the debounce_due tests — build a state with the four
|
||||
/// fields the test cares about set, others default.
|
||||
#[allow(clippy::field_reassign_with_default)]
|
||||
fn search_state_with(input: &str, mode: SearchMode, searching: bool, last_query: Option<(String, SearchMode)>) -> SearchState {
|
||||
let mut s = SearchState::default();
|
||||
s.input = input.into();
|
||||
s.mode = mode;
|
||||
s.searching = searching;
|
||||
s.last_query = last_query;
|
||||
s.input_dirty_at = Some(
|
||||
time::OffsetDateTime::now_utc() - time::Duration::seconds(1),
|
||||
);
|
||||
s
|
||||
}
|
||||
|
||||
/// p9-fb-08 — `debounce_due` skips when an in-flight worker is
|
||||
/// already running for the same `(input, mode)` pair. Without this
|
||||
/// guard, a "phantom keystroke" (re-typing the same chars) would
|
||||
/// pile up workers and burn CPU.
|
||||
#[test]
|
||||
fn debounce_due_skips_when_in_flight_for_same_query() {
|
||||
let s = search_state_with(
|
||||
"hello",
|
||||
SearchMode::Hybrid,
|
||||
true,
|
||||
Some(("hello".into(), SearchMode::Hybrid)),
|
||||
);
|
||||
assert!(
|
||||
!search_debounce_due(&s),
|
||||
"in-flight worker for same query → debounce must skip"
|
||||
);
|
||||
}
|
||||
|
||||
/// p9-fb-08 — `debounce_due` still fires when a different query is
|
||||
/// in flight (user typed past the in-flight one). The new spawn
|
||||
/// makes the prior result stale (handled by `poll_worker`).
|
||||
#[test]
|
||||
fn debounce_due_fires_when_in_flight_for_different_query() {
|
||||
let s = search_state_with(
|
||||
"hello world",
|
||||
SearchMode::Hybrid,
|
||||
true,
|
||||
Some(("hello".into(), SearchMode::Hybrid)),
|
||||
);
|
||||
assert!(
|
||||
search_debounce_due(&s),
|
||||
"in-flight worker for old query → new query still spawns"
|
||||
);
|
||||
}
|
||||
|
||||
/// p9-fb-08 — disconnected channel (worker panicked) clears the rx
|
||||
/// + searching flag so the next debounce tick can re-fire cleanly.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user