use serde_json::json;
use crate::db::{SearchMode, SearchResults};
use crate::model::{
build_item, build_representation, build_snapshot, CaptureContext, CaptureEvent,
CaptureStoreResult, DoctorReport, SearchHit, SnapshotDetails, SnapshotKind, TimelineEvent,
};
use super::super::commands::{CaptureOnceOutput, CaptureOnceStoredOutput};
use super::{
render_capture_once_text, render_doctor_text, render_get_markdown, render_hits_text,
render_list_markdown, render_list_toon, render_recall_markdown, render_recall_toon,
render_search_results_text, render_snapshot_text, render_timeline_text, GetEnvelope,
ListEnvelope, ListRow, RecallEnvelope, RecallMatchConfidence, RecallOutputRow, SnapshotListRow,
TimelineListRow, OUTPUT_SCHEMA_VERSION,
};
#[test]
pub(in crate::cli) fn render_capture_once_text_reports_summary_lines() {
let text = render_capture_once_text(&CaptureOnceOutput::Stored(CaptureOnceStoredOutput {
store: CaptureStoreResult::new(9, 12, true),
snapshot: build_snapshot(
CaptureContext::new(3)
.with_frontmost_app_name("Terminal")
.with_frontmost_app_bundle_id("com.apple.Terminal"),
vec![build_item(
0,
vec![build_representation(
"public.utf8-plain-text".to_string(),
None,
b"git status".to_vec(),
)],
)],
),
}));
assert!(text.contains("stored snapshot 9 via event 12"));
assert!(text.contains("kind: plain_text"));
assert!(text.contains("preview: git status"));
}
#[test]
pub(in crate::cli) fn render_capture_once_text_skips_preview_for_filtered_content() {
let text = render_capture_once_text(&CaptureOnceOutput::Skipped(
super::super::commands::CaptureOnceSkippedOutput {
status: "skipped",
reason: crate::db::CaptureSkipReason::ApiKeyFilter,
kind: "plain_text".to_string(),
total_bytes: 48,
frontmost_app_name: Some("Terminal".to_string()),
frontmost_app_bundle_id: Some("com.apple.Terminal".to_string()),
},
));
assert!(text.contains("skipped capture reason=api_key_filter"));
assert!(text.contains("kind: plain_text"));
assert!(!text.contains("preview:"));
}
#[test]
pub(in crate::cli) fn render_hits_text_includes_match_score_urls_and_files() {
let text = render_hits_text(&[SearchHit::new(
42,
77,
"abc123".to_string(),
SnapshotKind::PlainText,
"git clone".to_string(),
"git clone".to_string(),
Some("⟦git⟧ clone".to_string()),
vec!["best_text".to_string(), "search_text".to_string()],
3,
"2026-04-16 10:00:00".to_string(),
"2026-04-16 11:00:00".to_string(),
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
vec!["https://example.com".to_string()],
vec!["/Users/test/repo".to_string()],
9,
1,
Some(0.125),
)]);
assert!(text.contains("[42] plain_text"));
assert!(text.contains("match: ⟦git⟧ clone"));
assert!(text.contains("urls: https://example.com"));
assert!(text.contains("files: /Users/test/repo"));
assert!(text.contains("score: 0.125"));
}
#[test]
pub(in crate::cli) fn render_snapshot_text_lists_items_and_events() {
let text = render_snapshot_text(&SnapshotDetails::new(
7,
"abc123".to_string(),
SnapshotKind::PlainText,
"git push".to_string(),
"git push".to_string(),
1,
8,
"2026-04-16 10:00:00".to_string(),
2,
"2026-04-16 10:00:00".to_string(),
"2026-04-16 11:00:00".to_string(),
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
None,
None,
vec![CaptureEvent::new(
21,
"2026-04-16 11:00:00".to_string(),
3,
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
)],
vec![build_item(
0,
vec![build_representation(
"public.utf8-plain-text".to_string(),
None,
b"git push".to_vec(),
)],
)],
));
assert!(text.contains("snapshot 7"));
assert!(text.contains("item 0 · kind=plain_text"));
assert!(text.contains("event 21"));
}
#[test]
pub(in crate::cli) fn render_doctor_text_lists_compile_options() {
let text = render_doctor_text(&DoctorReport::new(
"/tmp/clipmem.sqlite3".to_string(),
"3.46.0".to_string(),
"wal".to_string(),
true,
true,
vec!["ENABLE_FTS5".to_string()],
));
assert!(text.contains("database: /tmp/clipmem.sqlite3"));
assert!(text.contains("compile options:"));
assert!(text.contains("ENABLE_FTS5"));
}
#[test]
pub(in crate::cli) fn render_search_results_text_reports_effective_mode() {
let text = render_search_results_text(&SearchResults::new(
SearchMode::Literal,
vec![SearchHit::new(
7,
21,
"abc123".to_string(),
SnapshotKind::PlainText,
"git push".to_string(),
"git push".to_string(),
Some("git push".to_string()),
vec!["best_text".to_string(), "search_text".to_string()],
2,
"2026-04-16 10:00:00".to_string(),
"2026-04-16 11:00:00".to_string(),
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
Vec::new(),
Vec::new(),
8,
1,
None,
)],
false,
));
assert!(text.contains("search mode: literal"));
}
#[test]
pub(in crate::cli) fn render_timeline_text_reports_event_history() {
let text = render_timeline_text(&[TimelineEvent::new(
21,
7,
"2026-04-16 11:00:00".to_string(),
3,
"abc123".to_string(),
SnapshotKind::PlainText,
"git push".to_string(),
"git push".to_string(),
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
vec!["https://example.com".to_string()],
vec!["/Users/test/repo".to_string()],
8,
1,
)]);
assert!(text.contains("event 21"));
assert!(text.contains("change_count=3"));
assert!(text.contains("best: git push"));
}
#[test]
pub(in crate::cli) fn markdown_and_toon_render_envelopes() {
let row = ListRow::Snapshot(SnapshotListRow {
snapshot_id: 1,
event_id: 2,
sha256: "abc".to_string(),
kind: "plain_text".to_string(),
observed_at: "2026-04-17T10:00:00Z".to_string(),
first_seen_at: "2026-04-17T09:00:00Z".to_string(),
last_seen_at: "2026-04-17T10:00:00Z".to_string(),
app_name: Some("Terminal".to_string()),
app_bundle_id: Some("com.apple.Terminal".to_string()),
best_text: "git status".to_string(),
best_text_uti: Some("public.utf8-plain-text".to_string()),
text_fragments: Vec::new(),
urls: vec!["https://example.com".to_string()],
file_paths: Vec::new(),
html_text: None,
rtf_text: None,
ocr_text: None,
ocr_status: None,
text_summary: "git status".to_string(),
preview_text: "git status".to_string(),
item_count: 1,
total_bytes: 10,
capture_count: 1,
score: Some(0.3),
why_matched: Some("git status".to_string()),
matched_fields: vec!["best_text".to_string(), "search_text".to_string()],
});
let envelope = ListEnvelope {
schema_version: OUTPUT_SCHEMA_VERSION,
command: "search",
generated_at: "2026-04-17T10:00:00Z".to_string(),
applied_filters: json!({
"query": "git",
"requested_mode": "auto",
"mode_used": "literal",
"apps": ["Terminal", "Safari"],
"window": {
"hours": 24,
},
"flags": [
{
"name": "prefer_recent",
"enabled": true,
}
],
"limit": 10,
"cursor": null,
}),
truncated: false,
next_cursor: None,
results: vec![row],
};
let markdown = render_list_markdown(&envelope);
let toon = render_list_toon(&envelope);
assert!(markdown.contains("| snapshot_id | kind | observed_at |"));
assert!(toon.contains(
"results[#1\t]{snapshot_id\tevent_id\tobserved_at\tfirst_seen_at\tlast_seen_at\tkind\tapp_name\tapp_bundle_id\tdisplay_text\tcapture_count\titem_count\ttotal_bytes\tscore\twhy_matched}:"
));
assert!(toon.contains("git status"));
assert!(!toon.contains("sha256"));
assert!(!toon.contains("text_fragments"));
assert!(toon.contains("apps[#2\t]: Terminal\tSafari"));
assert!(toon.contains("window:"));
assert!(toon.contains("hours: 24"));
assert!(toon.contains("flags[#1]:"));
assert!(toon.contains("name: prefer_recent"));
}
#[test]
pub(in crate::cli) fn get_markdown_renders_summary_items_and_events() {
let markdown = render_get_markdown(&GetEnvelope {
schema_version: OUTPUT_SCHEMA_VERSION,
command: "get",
generated_at: "2026-04-17T10:00:00Z".to_string(),
applied_filters: json!({}),
snapshot: SnapshotDetails::new(
7,
"abc123".to_string(),
SnapshotKind::PlainText,
"git push".to_string(),
"git push".to_string(),
1,
8,
"2026-04-16 10:00:00".to_string(),
2,
"2026-04-16 10:00:00".to_string(),
"2026-04-16 11:00:00".to_string(),
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
None,
None,
vec![CaptureEvent::new(
21,
"2026-04-16 11:00:00".to_string(),
3,
Some("Terminal".to_string()),
Some("com.apple.Terminal".to_string()),
)],
vec![build_item(
0,
vec![build_representation(
"public.utf8-plain-text".to_string(),
None,
b"git push".to_vec(),
)],
)],
),
});
assert!(markdown.contains("## Items"));
assert!(markdown.contains("## Recent Events"));
assert!(markdown.contains("Snapshot: 7"));
}
#[test]
pub(in crate::cli) fn recall_markdown_and_toon_render_best_match_and_alternatives() {
let envelope = RecallEnvelope {
schema_version: OUTPUT_SCHEMA_VERSION,
command: "recall",
generated_at: "2026-04-17T10:00:00Z".to_string(),
applied_filters: json!({
"limit": 5,
"prefer_recent": true,
}),
query: Some("git".to_string()),
best_candidate: RecallOutputRow {
snapshot_id: 1,
event_id: 2,
sha256: "abc".to_string(),
kind: "plain_text".to_string(),
observed_at: "2026-04-17T10:00:00Z".to_string(),
first_seen_at: "2026-04-17T09:00:00Z".to_string(),
last_seen_at: "2026-04-17T10:00:00Z".to_string(),
app_name: Some("Terminal".to_string()),
app_bundle_id: Some("com.apple.Terminal".to_string()),
best_text: "".to_string(),
best_text_uti: Some("public.utf8-plain-text".to_string()),
text_fragments: Vec::new(),
urls: vec!["https://example.com".to_string()],
file_paths: vec!["/tmp/file.txt".to_string()],
html_text: None,
rtf_text: None,
ocr_text: None,
ocr_status: None,
text_summary: "git status".to_string(),
preview_text: "git status".to_string(),
item_count: 1,
total_bytes: 10,
capture_count: 1,
score: Some(0.1),
why_matched: Some("git status".to_string()),
matched_fields: vec!["best_text".to_string(), "search_text".to_string()],
snippet: "quoted snippet".to_string(),
},
alternatives: vec![RecallOutputRow {
snapshot_id: 3,
event_id: 4,
sha256: "def".to_string(),
kind: "plain_text".to_string(),
observed_at: "2026-04-17T09:00:00Z".to_string(),
first_seen_at: "2026-04-17T08:00:00Z".to_string(),
last_seen_at: "2026-04-17T09:00:00Z".to_string(),
app_name: Some("Editor".to_string()),
app_bundle_id: Some("com.example.Editor".to_string()),
best_text: "git commit".to_string(),
best_text_uti: Some("public.utf8-plain-text".to_string()),
text_fragments: Vec::new(),
urls: Vec::new(),
file_paths: Vec::new(),
html_text: None,
rtf_text: None,
ocr_text: None,
ocr_status: None,
text_summary: "git commit".to_string(),
preview_text: "git commit".to_string(),
item_count: 1,
total_bytes: 10,
capture_count: 1,
score: None,
why_matched: None,
matched_fields: Vec::new(),
snippet: String::new(),
}],
best_match_confidence: RecallMatchConfidence::High,
best_match_score: Some(0.91),
why_selected: "Selected the strongest search match".to_string(),
quoted_text: Some("git status".to_string()),
};
let markdown = render_recall_markdown(&envelope);
let toon = render_recall_toon(&envelope);
assert!(markdown.contains("# Best Match"));
assert!(markdown.contains("## Alternatives"));
assert!(markdown.contains("> git status"));
assert!(toon.contains(
"best_candidate[#1\t]{snapshot_id\tevent_id\tobserved_at\tfirst_seen_at\tlast_seen_at\tkind\tapp_name\tapp_bundle_id\tdisplay_text\tcapture_count\titem_count\ttotal_bytes\tscore\twhy_matched}:"
));
assert!(toon.contains("alternatives[#1\t]{snapshot_id\tevent_id\tobserved_at\tfirst_seen_at\tlast_seen_at\tkind\tapp_name\tapp_bundle_id\tdisplay_text\tcapture_count\titem_count\ttotal_bytes\tscore\twhy_matched}:"));
assert!(toon.contains("quoted snippet"));
assert!(toon.contains("git commit"));
assert!(!toon.contains("\tsnippet}:"));
assert!(!toon.contains("matched_fields"));
assert!(!toon.contains("sha256"));
}
#[test]
pub(in crate::cli) fn timeline_toon_uses_scalar_projection_display_text_fallback() {
let envelope = ListEnvelope {
schema_version: OUTPUT_SCHEMA_VERSION,
command: "timeline",
generated_at: "2026-04-17T10:00:00Z".to_string(),
applied_filters: json!({ "hours": 24 }),
truncated: false,
next_cursor: None,
results: vec![ListRow::Timeline(TimelineListRow {
event_id: 9,
snapshot_id: 3,
observed_at: "2026-04-17T10:00:00Z".to_string(),
change_count: 2,
kind: "plain_text".to_string(),
app_name: Some("Terminal".to_string()),
app_bundle_id: Some("com.apple.Terminal".to_string()),
best_text: String::new(),
best_text_uti: None,
text_fragments: Vec::new(),
urls: Vec::new(),
file_paths: Vec::new(),
html_text: None,
rtf_text: None,
ocr_text: None,
ocr_status: None,
text_summary: "git summary".to_string(),
preview_text: "git preview".to_string(),
total_bytes: 42,
sha256: "abc123".to_string(),
item_count: 1,
})],
};
let toon = render_list_toon(&envelope);
assert!(toon.contains(
"results[#1\t]{event_id\tsnapshot_id\tobserved_at\tchange_count\tkind\tapp_name\tapp_bundle_id\tdisplay_text\titem_count\ttotal_bytes}:"
));
assert!(toon.contains("git preview"));
assert!(!toon.contains("best_text_uti"));
assert!(!toon.contains("sha256"));
}