use ely_gpui_component::forms::TextInput; use ely_office::{FindReplace, Finder, prelude::*}; use gpui::{AppContext as _, Entity, TestAppContext}; use crate::docs::Docs; use crate::tests::{blank, docs, settle, settled, text_of, type_into, with_window, written}; fn finder(cx: &TestAppContext, docs: &Entity, read: impl Fn(&Finder) -> R) -> R { cx.update(|app| { read( docs.read(app) .core .finder .as_ref() .expect("the finder is open"), ) }) } fn total(app: &gpui::App, docs: &Entity) -> Option { docs.read(app) .core .finder .as_ref() .map(|finder| finder.all.len()) } #[test] fn find_counts_steps_replaces_and_counts_words() { let dir = tempfile::tempdir().expect("a temp dir"); let mut cx = TestAppContext::single(); let Some(docs) = docs(&mut cx, dir.path()) else { eprintln!("SKIP: no LOK build"); return; }; let id = blank(&mut cx, &docs); type_into(&mut cx, &docs, "cat Cat cat dog"); settle(&cx, |app| { docs.read(app) .active_tab() .and_then(|tab| tab.extra.counts) .is_some_and(|counts| counts.words == 4) }); with_window(&mut cx, &docs, |docs, window, cx| { docs.on_find(&FindReplace, window, cx) }); let query = finder(&cx, &docs, |finder| finder.query.clone()); cx.update(|app| query.update(app, |query, cx| query.set_text("cat", cx))); settle(&cx, |app| total(app, &docs) == Some(3)); cx.update(|app| { docs.update(app, |docs, cx| { docs.core.finder.as_mut().expect("open").options.case = true; docs.count_matches(cx); }) }); settle(&cx, |app| total(app, &docs) == Some(2)); cx.update(|app| docs.update(app, |docs, cx| docs.step(true, cx))); settle(&cx, |app| { docs.read(app) .core .finder .as_ref() .is_some_and(|finder| finder.current == Some(0)) }); with_window(&mut cx, &docs, |docs, window, cx| { let finder = docs.core.finder.as_mut().expect("open"); finder.replace = Some(cx.new(|cx| TextInput::new(window, cx))); }); let with = finder(&cx, &docs, |finder| { finder.replace.clone().expect("a field") }); cx.update(|app| with.update(app, |with, cx| with.set_text("cow", cx))); cx.update(|app| docs.update(app, |docs, cx| docs.replace(false, cx))); settle(&cx, |app| total(app, &docs) == Some(1)); cx.update(|app| docs.update(app, |docs, cx| docs.replace(true, cx))); settle(&cx, |app| total(app, &docs) == Some(0)); settled(&cx, &docs); let path = dir.path().join("found.odt"); with_window(&mut cx, &docs, |docs, window, cx| { docs.write_as(id, path.clone(), None, window, cx) }); written(&cx, &path, None); let text = text_of(&mut cx, &docs, &path); assert!(text.contains("cow Cat cow dog"), "the cased matches go"); } #[test] fn a_misspelled_word_offers_suggestions() { let dir = tempfile::tempdir().expect("a temp dir"); let mut cx = TestAppContext::single(); let Some(docs) = docs(&mut cx, dir.path()) else { eprintln!("SKIP: no LOK build"); return; }; let id = blank(&mut cx, &docs); type_into(&mut cx, &docs, "Teh end"); with_window(&mut cx, &docs, |docs, window, cx| { docs.on_find(&FindReplace, window, cx) }); let query = finder(&cx, &docs, |finder| finder.query.clone()); cx.update(|app| query.update(app, |query, cx| query.set_text("Teh", cx))); settle(&cx, |app| total(app, &docs) == Some(1)); let (x, y, _, h) = finder(&cx, &docs, |finder| finder.all[0]); let (worker, doc) = cx.update(|app| { let docs = docs.read(app); ( docs.core.worker.clone(), docs.active_tab().expect("a tab").doc.clone(), ) }); { let mut worker = worker.lock().expect("the worker lock"); for (kind, buttons) in [(0, 1), (1, 1), (0, 4), (1, 4)] { let (x, y, count, modifiers) = (x + 60, y + h / 2, 1, 0); let event = ely_lok::Mouse { kind, x, y, count, buttons, modifiers, }; worker.post_mouse(&doc, event).expect("the click posts"); } } settle(&cx, |app| docs.read(app).offer.is_some()); let rule = cx.update(|app| { let offer = docs.read(app).offer.as_ref().expect("an offer"); let spelling = offer.what.spelling.as_ref().expect("a spelling menu"); let (_, rule) = spelling .suggestions .iter() .find(|(label, _)| label == "The") .expect("The is offered"); rule.clone() }); cx.update(|app| docs.update(app, |docs, cx| docs.run_active("spell.apply", &[&rule], cx))); settled(&cx, &docs); let path = dir.path().join("spelled.odt"); with_window(&mut cx, &docs, |docs, window, cx| { docs.write_as(id, path.clone(), None, window, cx) }); written(&cx, &path, None); assert!( text_of(&mut cx, &docs, &path).contains("The end"), "the suggestion lands" ); } #[test] fn edits_mark_the_count_and_steps_scroll_to_far_matches() { let dir = tempfile::tempdir().expect("a temp dir"); let mut cx = TestAppContext::single(); let Some(docs) = docs(&mut cx, dir.path()) else { eprintln!("SKIP: no LOK build"); return; }; let filler = "filler".repeat(100); let body = format!( r#" a b c{filler}needle"# ); let path = dir.path().join("long.fodt"); std::fs::write(&path, body).expect("the file writes"); with_window(&mut cx, &docs, |docs, _, cx| { docs.open_file(path.clone(), cx) }); settle(&cx, |app| docs.read(app).core.tabs.len() == 1); settled(&cx, &docs); with_window(&mut cx, &docs, |docs, window, cx| { docs.on_find(&FindReplace, window, cx) }); let query = finder(&cx, &docs, |finder| finder.query.clone()); cx.update(|app| query.update(app, |query, cx| query.set_text(" ", cx))); settle(&cx, |app| total(app, &docs) == Some(2)); cx.update(|app| query.update(app, |query, cx| query.set_text("needle", cx))); settle(&cx, |app| total(app, &docs) == Some(1)); cx.update(|app| docs.update(app, |docs, cx| docs.step(true, cx))); settle(&cx, |app| { let tab = docs.read(app).active_tab().expect("a tab"); tab.view.read(app).scroll().1 > 20_000 }); type_into(&mut cx, &docs, "needle needle"); settle(&cx, |app| { docs.read(app) .core .finder .as_ref() .is_some_and(|finder| finder.stale) }); cx.update(|app| docs.update(app, |docs, cx| docs.step(true, cx))); settle(&cx, |app| { let finder = docs.read(app).core.finder.as_ref().expect("open"); !finder.stale && finder.all.len() == 2 }); } #[test] fn a_step_asked_during_a_count_waits_for_it() { let dir = tempfile::tempdir().expect("a temp dir"); let mut cx = TestAppContext::single(); let Some(docs) = docs(&mut cx, dir.path()) else { eprintln!("SKIP: no LOK build"); return; }; blank(&mut cx, &docs); type_into(&mut cx, &docs, "cat cat cat"); with_window(&mut cx, &docs, |docs, window, cx| { docs.on_find(&FindReplace, window, cx) }); let query = finder(&cx, &docs, |finder| finder.query.clone()); cx.update(|app| query.update(app, |query, cx| query.set_text("cat", cx))); cx.update(|app| docs.update(app, |docs, cx| docs.step(true, cx))); cx.update(|app| docs.update(app, |docs, cx| docs.step(true, cx))); assert_eq!( finder(&cx, &docs, |finder| finder.queued.len()), 2, "both steps wait for the count" ); settle(&cx, |app| { docs.read(app) .core .finder .as_ref() .is_some_and(|finder| finder.all.len() == 3 && finder.current == Some(1)) }); cx.update(|app| query.update(app, |query, cx| query.set_text("ca", cx))); cx.update(|app| query.update(app, |query, cx| query.set_text("", cx))); settle(&cx, |app| { docs.read(app) .core .finder .as_ref() .is_some_and(|finder| finder.pending.is_empty()) }); assert_eq!( finder(&cx, &docs, |finder| finder.all.len()), 0, "a cleared query voids the count on its way" ); }