diff --git a/crates/ide-host/tests/plugins.rs b/crates/ide-host/tests/plugins.rs index 324add15..d303fd52 100644 --- a/crates/ide-host/tests/plugins.rs +++ b/crates/ide-host/tests/plugins.rs @@ -609,8 +609,9 @@ fn providers_of_several_plugins_run_concurrently() { text: text.into(), }; let at = ide_core::Pos::new(0, 0); - // Activation is not what is measured. - assert_eq!(host.provide_completion(&doc("fast"), at, None).len(), 2); + // Activation is not what is measured: a first call that starts an instance + // can run past the edit-path budget, and its answer is dropped. + assert!(eventually(|| host.provide_completion(&doc("fast"), at, None).len() == 2)); let started = Instant::now(); let items = host.provide_completion(&doc("sleep:500"), at, None); @@ -1676,22 +1677,21 @@ fn official_ai_completion_cancels_superseded_requests() { }; let at = ide_core::Pos::new(1, 4); let host = Arc::new(f); - // A slow request, superseded 200 ms later by the next keystroke's. + // A slow request, superseded by the next keystroke's once the server has it + // (starting the instance comes first, and takes longer on a slow runner). let first = ide_core::CancellationToken::new(); let slow = { let (host, doc, first) = (host.clone(), doc.clone(), first.clone()); - std::thread::spawn(move || { - let started = Instant::now(); - let r = host.host.provide_inline_completion(&doc, at, &first); - (r, started.elapsed()) - }) + std::thread::spawn(move || (host.host.provide_inline_completion(&doc, at, &first), Instant::now())) }; - std::thread::sleep(Duration::from_millis(200)); + assert!(eventually(|| seen.lock().len() == 1), "the first request reaches the server"); + let cancelled = Instant::now(); first.cancel(); let started = Instant::now(); let second = host.host.provide_inline_completion(&doc, at, &ide_core::CancellationToken::new()); let took = started.elapsed(); - let (old, old_took) = slow.join().unwrap(); + let (old, old_done) = slow.join().unwrap(); + let old_took = old_done.saturating_duration_since(cancelled); assert!(old.is_none(), "the superseded request yields nothing"); assert!(old_took < Duration::from_secs(1), "cancelled, not waited out: {old_took:?}"); assert_eq!(second.map(|(_, s)| s.insert_text).as_deref(), Some("a + b")); @@ -1869,7 +1869,12 @@ fn official_mybatis_navigates_between_interface_and_xml() { assert_eq!((target.path, target.range.start), (xml_path.clone(), ide_core::Pos::new(1, 14))); let xdoc = doc(&xml_path, "xml", xml); - let to_java = f.host.provide_definition(&xdoc, ide_core::Pos::new(1, 16)); + // Reads the interface from disk: on a slow machine that too can run past the budget. + let mut to_java = Vec::new(); + assert!(eventually(|| { + to_java = f.host.provide_definition(&xdoc, ide_core::Pos::new(1, 16)); + !to_java.is_empty() + })); assert_eq!( to_java.iter().map(|l| (l.path.clone(), l.range.start)).collect::>(), [(java_path.clone(), ide_core::Pos::new(3, 9))] @@ -2437,7 +2442,12 @@ fn completion_quick_fixes_problems_and_event_kinds_of_api_1() { }; let at = ide_core::Pos::new(0, 0); - let items = f.host.provide_completion(&doc("rich"), at, None); + // The first call starts the instance, which may take longer than the edit-path budget. + let mut items = Vec::new(); + assert!(eventually(|| { + items = f.host.provide_completion(&doc("rich"), at, None); + !items.is_empty() + })); let item = &items[0].1; assert_eq!( (item.insert_text.as_str(), item.snippet, item.filter_text.as_deref()), diff --git a/src/ui/SourcePreview.test.tsx b/src/ui/SourcePreview.test.tsx index 46e9a408..15f442f3 100644 --- a/src/ui/SourcePreview.test.tsx +++ b/src/ui/SourcePreview.test.tsx @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { SourcePreview } from "./SourcePreview"; /** A stable loader reference (a new function reloads, like the real module-level ones). */ @@ -24,6 +24,7 @@ describe("SourcePreview", () => { const { container } = render(); await screen.findByText("// TODO"); // The scroll pane is a selection scope: drags stay inside, text stays copyable. - expect(container.querySelector("[data-selection-scope]")).toBeTruthy(); + // Set by an effect, which can run after the text is on screen. + await waitFor(() => expect(container.querySelector("[data-selection-scope]")).toBeTruthy()); }); });