Get editor tests compiling
This commit is contained in:
parent
4c5d5105f3
commit
0e3fd92bd0
8 changed files with 1618 additions and 1426 deletions
|
@ -10056,76 +10056,76 @@ pub fn diagnostic_style(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn combine_syntax_and_fuzzy_match_highlights(
|
pub fn combine_syntax_and_fuzzy_match_highlights(
|
||||||
// text: &str,
|
text: &str,
|
||||||
// default_style: HighlightStyle,
|
default_style: HighlightStyle,
|
||||||
// syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
|
syntax_ranges: impl Iterator<Item = (Range<usize>, HighlightStyle)>,
|
||||||
// match_indices: &[usize],
|
match_indices: &[usize],
|
||||||
// ) -> Vec<(Range<usize>, HighlightStyle)> {
|
) -> Vec<(Range<usize>, HighlightStyle)> {
|
||||||
// let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
// let mut match_indices = match_indices.iter().copied().peekable();
|
let mut match_indices = match_indices.iter().copied().peekable();
|
||||||
|
|
||||||
// for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
|
for (range, mut syntax_highlight) in syntax_ranges.chain([(usize::MAX..0, Default::default())])
|
||||||
// {
|
{
|
||||||
// syntax_highlight.weight = None;
|
syntax_highlight.font_weight = None;
|
||||||
|
|
||||||
// // Add highlights for any fuzzy match characters before the next
|
// Add highlights for any fuzzy match characters before the next
|
||||||
// // syntax highlight range.
|
// syntax highlight range.
|
||||||
// while let Some(&match_index) = match_indices.peek() {
|
while let Some(&match_index) = match_indices.peek() {
|
||||||
// if match_index >= range.start {
|
if match_index >= range.start {
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// match_indices.next();
|
match_indices.next();
|
||||||
// let end_index = char_ix_after(match_index, text);
|
let end_index = char_ix_after(match_index, text);
|
||||||
// let mut match_style = default_style;
|
let mut match_style = default_style;
|
||||||
// match_style.weight = Some(FontWeight::BOLD);
|
match_style.font_weight = Some(FontWeight::BOLD);
|
||||||
// result.push((match_index..end_index, match_style));
|
result.push((match_index..end_index, match_style));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if range.start == usize::MAX {
|
if range.start == usize::MAX {
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Add highlights for any fuzzy match characters within the
|
// Add highlights for any fuzzy match characters within the
|
||||||
// // syntax highlight range.
|
// syntax highlight range.
|
||||||
// let mut offset = range.start;
|
let mut offset = range.start;
|
||||||
// while let Some(&match_index) = match_indices.peek() {
|
while let Some(&match_index) = match_indices.peek() {
|
||||||
// if match_index >= range.end {
|
if match_index >= range.end {
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// match_indices.next();
|
match_indices.next();
|
||||||
// if match_index > offset {
|
if match_index > offset {
|
||||||
// result.push((offset..match_index, syntax_highlight));
|
result.push((offset..match_index, syntax_highlight));
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let mut end_index = char_ix_after(match_index, text);
|
let mut end_index = char_ix_after(match_index, text);
|
||||||
// while let Some(&next_match_index) = match_indices.peek() {
|
while let Some(&next_match_index) = match_indices.peek() {
|
||||||
// if next_match_index == end_index && next_match_index < range.end {
|
if next_match_index == end_index && next_match_index < range.end {
|
||||||
// end_index = char_ix_after(next_match_index, text);
|
end_index = char_ix_after(next_match_index, text);
|
||||||
// match_indices.next();
|
match_indices.next();
|
||||||
// } else {
|
} else {
|
||||||
// break;
|
break;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let mut match_style = syntax_highlight;
|
let mut match_style = syntax_highlight;
|
||||||
// match_style.weight = Some(FontWeight::BOLD);
|
match_style.font_weight = Some(FontWeight::BOLD);
|
||||||
// result.push((match_index..end_index, match_style));
|
result.push((match_index..end_index, match_style));
|
||||||
// offset = end_index;
|
offset = end_index;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if offset < range.end {
|
if offset < range.end {
|
||||||
// result.push((offset..range.end, syntax_highlight));
|
result.push((offset..range.end, syntax_highlight));
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// fn char_ix_after(ix: usize, text: &str) -> usize {
|
fn char_ix_after(ix: usize, text: &str) -> usize {
|
||||||
// ix + text[ix..].chars().next().unwrap().len_utf8()
|
ix + text[ix..].chars().next().unwrap().len_utf8()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// result
|
result
|
||||||
// }
|
}
|
||||||
|
|
||||||
// pub fn styled_runs_for_code_label<'a>(
|
// pub fn styled_runs_for_code_label<'a>(
|
||||||
// label: &'a CodeLabel,
|
// label: &'a CodeLabel,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@ use serde_json::json;
|
||||||
use crate::{Editor, ToPoint};
|
use crate::{Editor, ToPoint};
|
||||||
use collections::HashSet;
|
use collections::HashSet;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use gpui::{json, View, ViewContext};
|
use gpui::{View, ViewContext, VisualTestContext};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
|
use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
|
||||||
use lsp::{notification, request};
|
use lsp::{notification, request};
|
||||||
|
@ -19,7 +19,7 @@ use project::Project;
|
||||||
use smol::stream::StreamExt;
|
use smol::stream::StreamExt;
|
||||||
use workspace::{AppState, Workspace, WorkspaceHandle};
|
use workspace::{AppState, Workspace, WorkspaceHandle};
|
||||||
|
|
||||||
use super::editor_test_context::EditorTestContext;
|
use super::editor_test_context::{AssertionContextManager, EditorTestContext};
|
||||||
|
|
||||||
pub struct EditorLspTestContext<'a> {
|
pub struct EditorLspTestContext<'a> {
|
||||||
pub cx: EditorTestContext<'a>,
|
pub cx: EditorTestContext<'a>,
|
||||||
|
@ -34,8 +34,6 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
capabilities: lsp::ServerCapabilities,
|
capabilities: lsp::ServerCapabilities,
|
||||||
cx: &'a mut gpui::TestAppContext,
|
cx: &'a mut gpui::TestAppContext,
|
||||||
) -> EditorLspTestContext<'a> {
|
) -> EditorLspTestContext<'a> {
|
||||||
use json::json;
|
|
||||||
|
|
||||||
let app_state = cx.update(AppState::test);
|
let app_state = cx.update(AppState::test);
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
|
@ -70,9 +68,10 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||||
let workspace = window.root(cx);
|
let workspace = window.root_view(cx).unwrap();
|
||||||
|
let mut cx = VisualTestContext::from_window(*window.deref(), cx);
|
||||||
project
|
project
|
||||||
.update(cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.find_or_create_local_worktree("/root", true, cx)
|
project.find_or_create_local_worktree("/root", true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -82,7 +81,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
|
|
||||||
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
|
||||||
let item = workspace
|
let item = workspace
|
||||||
.update(cx, |workspace, cx| {
|
.update(&mut cx, |workspace, cx| {
|
||||||
workspace.open_path(file, None, true, cx)
|
workspace.open_path(file, None, true, cx)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -92,7 +91,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
item.act_as::<Editor>(cx)
|
item.act_as::<Editor>(cx)
|
||||||
.expect("Opened test file wasn't an editor")
|
.expect("Opened test file wasn't an editor")
|
||||||
});
|
});
|
||||||
editor.update(cx, |_, cx| cx.focus_self());
|
editor.update(&mut cx, |editor, cx| editor.focus(cx));
|
||||||
|
|
||||||
let lsp = fake_servers.next().await.unwrap();
|
let lsp = fake_servers.next().await.unwrap();
|
||||||
|
|
||||||
|
@ -101,6 +100,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
cx,
|
cx,
|
||||||
window: window.into(),
|
window: window.into(),
|
||||||
editor,
|
editor,
|
||||||
|
assertion_cx: AssertionContextManager::new(),
|
||||||
},
|
},
|
||||||
lsp,
|
lsp,
|
||||||
workspace,
|
workspace,
|
||||||
|
@ -258,7 +258,7 @@ impl<'a> EditorLspTestContext<'a> {
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
|
F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
|
||||||
{
|
{
|
||||||
self.workspace.update(self.cx.cx, update)
|
self.workspace.update(&mut self.cx.cx, update)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_request<T, F, Fut>(
|
pub fn handle_request<T, F, Fut>(
|
||||||
|
|
|
@ -1,28 +1,37 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
|
||||||
};
|
};
|
||||||
|
use collections::BTreeMap;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
|
AnyWindowHandle, AppContext, ForegroundExecutor, Keystroke, ModelContext, View, ViewContext,
|
||||||
|
VisualTestContext, WindowHandle,
|
||||||
};
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
use itertools::Itertools;
|
||||||
use language::{Buffer, BufferSnapshot};
|
use language::{Buffer, BufferSnapshot};
|
||||||
|
use parking_lot::RwLock;
|
||||||
use project::{FakeFs, Project};
|
use project::{FakeFs, Project};
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
ops::{Deref, DerefMut, Range},
|
ops::{Deref, DerefMut, Range},
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use util::{
|
use util::{
|
||||||
assert_set_eq,
|
assert_set_eq,
|
||||||
test::{generate_marked_text, marked_text_ranges},
|
test::{generate_marked_text, marked_text_ranges},
|
||||||
};
|
};
|
||||||
|
|
||||||
// use super::build_editor_with_project;
|
use super::build_editor_with_project;
|
||||||
|
|
||||||
pub struct EditorTestContext<'a> {
|
pub struct EditorTestContext<'a> {
|
||||||
pub cx: &'a mut gpui::TestAppContext,
|
pub cx: gpui::VisualTestContext<'a>,
|
||||||
pub window: AnyWindowHandle,
|
pub window: AnyWindowHandle,
|
||||||
pub editor: View<Editor>,
|
pub editor: View<Editor>,
|
||||||
|
pub assertion_cx: AssertionContextManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> EditorTestContext<'a> {
|
impl<'a> EditorTestContext<'a> {
|
||||||
|
@ -43,15 +52,18 @@ impl<'a> EditorTestContext<'a> {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let window = cx.add_window(|cx| {
|
let editor = cx.add_window(|cx| {
|
||||||
cx.focus_self();
|
let editor =
|
||||||
build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx)
|
build_editor_with_project(project, MultiBuffer::build_from_buffer(buffer, cx), cx);
|
||||||
|
editor.focus(cx);
|
||||||
|
editor
|
||||||
});
|
});
|
||||||
let editor = window.root(cx);
|
let editor_view = editor.root_view(cx).unwrap();
|
||||||
Self {
|
Self {
|
||||||
cx,
|
cx: VisualTestContext::from_window(*editor.deref(), cx),
|
||||||
window: window.into(),
|
window: editor.into(),
|
||||||
editor,
|
editor: editor_view,
|
||||||
|
assertion_cx: AssertionContextManager::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,24 +71,27 @@ impl<'a> EditorTestContext<'a> {
|
||||||
&self,
|
&self,
|
||||||
predicate: impl FnMut(&Editor, &AppContext) -> bool,
|
predicate: impl FnMut(&Editor, &AppContext) -> bool,
|
||||||
) -> impl Future<Output = ()> {
|
) -> impl Future<Output = ()> {
|
||||||
self.editor.condition(self.cx, predicate)
|
self.editor.condition::<crate::Event>(&self.cx, predicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn editor<F, T>(&self, read: F) -> T
|
#[track_caller]
|
||||||
|
pub fn editor<F, T>(&mut self, read: F) -> T
|
||||||
where
|
where
|
||||||
F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
|
F: FnOnce(&Editor, &ViewContext<Editor>) -> T,
|
||||||
{
|
{
|
||||||
self.editor.update(self.cx, read)
|
self.editor
|
||||||
|
.update(&mut self.cx, |this, cx| read(&this, &cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
pub fn update_editor<F, T>(&mut self, update: F) -> T
|
pub fn update_editor<F, T>(&mut self, update: F) -> T
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
|
F: FnOnce(&mut Editor, &mut ViewContext<Editor>) -> T,
|
||||||
{
|
{
|
||||||
self.editor.update(self.cx, update)
|
self.editor.update(&mut self.cx, update)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn multibuffer<F, T>(&self, read: F) -> T
|
pub fn multibuffer<F, T>(&mut self, read: F) -> T
|
||||||
where
|
where
|
||||||
F: FnOnce(&MultiBuffer, &AppContext) -> T,
|
F: FnOnce(&MultiBuffer, &AppContext) -> T,
|
||||||
{
|
{
|
||||||
|
@ -90,11 +105,11 @@ impl<'a> EditorTestContext<'a> {
|
||||||
self.update_editor(|editor, cx| editor.buffer().update(cx, update))
|
self.update_editor(|editor, cx| editor.buffer().update(cx, update))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer_text(&self) -> String {
|
pub fn buffer_text(&mut self) -> String {
|
||||||
self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
|
self.multibuffer(|buffer, cx| buffer.snapshot(cx).text())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer<F, T>(&self, read: F) -> T
|
pub fn buffer<F, T>(&mut self, read: F) -> T
|
||||||
where
|
where
|
||||||
F: FnOnce(&Buffer, &AppContext) -> T,
|
F: FnOnce(&Buffer, &AppContext) -> T,
|
||||||
{
|
{
|
||||||
|
@ -114,10 +129,18 @@ impl<'a> EditorTestContext<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn buffer_snapshot(&self) -> BufferSnapshot {
|
pub fn buffer_snapshot(&mut self) -> BufferSnapshot {
|
||||||
self.buffer(|buffer, _| buffer.snapshot())
|
self.buffer(|buffer, _| buffer.snapshot())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_assertion_context(&self, context: String) -> ContextHandle {
|
||||||
|
self.assertion_cx.add_context(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assertion_context(&self) -> String {
|
||||||
|
self.assertion_cx.context()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
|
pub fn simulate_keystroke(&mut self, keystroke_text: &str) -> ContextHandle {
|
||||||
let keystroke_under_test_handle =
|
let keystroke_under_test_handle =
|
||||||
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
|
self.add_assertion_context(format!("Simulated Keystroke: {:?}", keystroke_text));
|
||||||
|
@ -141,16 +164,12 @@ impl<'a> EditorTestContext<'a> {
|
||||||
// before returning.
|
// before returning.
|
||||||
// NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
|
// NOTE: we don't do this in simulate_keystroke() because a possible cause of bugs is that typing too
|
||||||
// quickly races with async actions.
|
// quickly races with async actions.
|
||||||
if let Foreground::Deterministic { cx_id: _, executor } = self.cx.foreground().as_ref() {
|
self.cx.background_executor.run_until_parked();
|
||||||
executor.run_until_parked();
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
keystrokes_under_test_handle
|
keystrokes_under_test_handle
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ranges(&self, marked_text: &str) -> Vec<Range<usize>> {
|
pub fn ranges(&mut self, marked_text: &str) -> Vec<Range<usize>> {
|
||||||
let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
|
let (unmarked_text, ranges) = marked_text_ranges(marked_text, false);
|
||||||
assert_eq!(self.buffer_text(), unmarked_text);
|
assert_eq!(self.buffer_text(), unmarked_text);
|
||||||
ranges
|
ranges
|
||||||
|
@ -160,12 +179,12 @@ impl<'a> EditorTestContext<'a> {
|
||||||
let ranges = self.ranges(marked_text);
|
let ranges = self.ranges(marked_text);
|
||||||
let snapshot = self
|
let snapshot = self
|
||||||
.editor
|
.editor
|
||||||
.update(self.cx, |editor, cx| editor.snapshot(cx));
|
.update(&mut self.cx, |editor, cx| editor.snapshot(cx));
|
||||||
ranges[0].start.to_display_point(&snapshot)
|
ranges[0].start.to_display_point(&snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns anchors for the current buffer using `«` and `»`
|
// Returns anchors for the current buffer using `«` and `»`
|
||||||
pub fn text_anchor_range(&self, marked_text: &str) -> Range<language::Anchor> {
|
pub fn text_anchor_range(&mut self, marked_text: &str) -> Range<language::Anchor> {
|
||||||
let ranges = self.ranges(marked_text);
|
let ranges = self.ranges(marked_text);
|
||||||
let snapshot = self.buffer_snapshot();
|
let snapshot = self.buffer_snapshot();
|
||||||
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
|
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
|
||||||
|
@ -190,7 +209,7 @@ impl<'a> EditorTestContext<'a> {
|
||||||
marked_text.escape_debug().to_string()
|
marked_text.escape_debug().to_string()
|
||||||
));
|
));
|
||||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||||
self.editor.update(self.cx, |editor, cx| {
|
self.editor.update(&mut self.cx, |editor, cx| {
|
||||||
editor.set_text(unmarked_text, cx);
|
editor.set_text(unmarked_text, cx);
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.select_ranges(selection_ranges)
|
s.select_ranges(selection_ranges)
|
||||||
|
@ -206,7 +225,7 @@ impl<'a> EditorTestContext<'a> {
|
||||||
marked_text.escape_debug().to_string()
|
marked_text.escape_debug().to_string()
|
||||||
));
|
));
|
||||||
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
let (unmarked_text, selection_ranges) = marked_text_ranges(marked_text, true);
|
||||||
self.editor.update(self.cx, |editor, cx| {
|
self.editor.update(&mut self.cx, |editor, cx| {
|
||||||
assert_eq!(editor.text(cx), unmarked_text);
|
assert_eq!(editor.text(cx), unmarked_text);
|
||||||
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
s.select_ranges(selection_ranges)
|
s.select_ranges(selection_ranges)
|
||||||
|
@ -273,9 +292,12 @@ impl<'a> EditorTestContext<'a> {
|
||||||
self.assert_selections(expected_selections, expected_marked_text)
|
self.assert_selections(expected_selections, expected_marked_text)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn editor_selections(&self) -> Vec<Range<usize>> {
|
#[track_caller]
|
||||||
|
fn editor_selections(&mut self) -> Vec<Range<usize>> {
|
||||||
self.editor
|
self.editor
|
||||||
.read_with(self.cx, |editor, cx| editor.selections.all::<usize>(cx))
|
.update(&mut self.cx, |editor, cx| {
|
||||||
|
editor.selections.all::<usize>(cx)
|
||||||
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
if s.reversed {
|
if s.reversed {
|
||||||
|
@ -320,7 +342,7 @@ impl<'a> Deref for EditorTestContext<'a> {
|
||||||
type Target = gpui::TestAppContext;
|
type Target = gpui::TestAppContext;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
self.cx
|
&self.cx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,3 +351,50 @@ impl<'a> DerefMut for EditorTestContext<'a> {
|
||||||
&mut self.cx
|
&mut self.cx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tracks string context to be printed when assertions fail.
|
||||||
|
/// Often this is done by storing a context string in the manager and returning the handle.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AssertionContextManager {
|
||||||
|
id: Arc<AtomicUsize>,
|
||||||
|
contexts: Arc<RwLock<BTreeMap<usize, String>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssertionContextManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Arc::new(AtomicUsize::new(0)),
|
||||||
|
contexts: Arc::new(RwLock::new(BTreeMap::new())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_context(&self, context: String) -> ContextHandle {
|
||||||
|
let id = self.id.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let mut contexts = self.contexts.write();
|
||||||
|
contexts.insert(id, context);
|
||||||
|
ContextHandle {
|
||||||
|
id,
|
||||||
|
manager: self.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn context(&self) -> String {
|
||||||
|
let contexts = self.contexts.read();
|
||||||
|
format!("\n{}\n", contexts.values().join("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to track the lifetime of a piece of context so that it can be provided when an assertion fails.
|
||||||
|
/// For example, in the EditorTestContext, `set_state` returns a context handle so that if an assertion fails,
|
||||||
|
/// the state that was set initially for the failure can be printed in the error message
|
||||||
|
pub struct ContextHandle {
|
||||||
|
id: usize,
|
||||||
|
manager: AssertionContextManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for ContextHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let mut contexts = self.manager.contexts.write();
|
||||||
|
contexts.remove(&self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, bail};
|
use anyhow::{anyhow, bail};
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use std::{future::Future, rc::Rc, sync::Arc, time::Duration};
|
use std::{future::Future, ops::Deref, rc::Rc, sync::Arc, time::Duration};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TestAppContext {
|
pub struct TestAppContext {
|
||||||
|
@ -132,6 +132,18 @@ impl TestAppContext {
|
||||||
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
|
cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, VisualTestContext)
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut ViewContext<V>) -> V,
|
||||||
|
V: Render,
|
||||||
|
{
|
||||||
|
let mut cx = self.app.borrow_mut();
|
||||||
|
let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
|
||||||
|
drop(cx);
|
||||||
|
let view = window.root_view(self).unwrap();
|
||||||
|
(view, VisualTestContext::from_window(*window.deref(), self))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
pub fn spawn<Fut, R>(&self, f: impl FnOnce(AsyncAppContext) -> Fut) -> Task<R>
|
||||||
where
|
where
|
||||||
Fut: Future<Output = R> + 'static,
|
Fut: Future<Output = R> + 'static,
|
||||||
|
@ -158,7 +170,7 @@ impl TestAppContext {
|
||||||
Some(read(lock.try_global()?, &lock))
|
Some(read(lock.try_global()?, &lock))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_global<G: 'static, R>(&mut self, global: G) {
|
pub fn set_global<G: 'static>(&mut self, global: G) {
|
||||||
let mut lock = self.app.borrow_mut();
|
let mut lock = self.app.borrow_mut();
|
||||||
lock.set_global(global);
|
lock.set_global(global);
|
||||||
}
|
}
|
||||||
|
@ -277,6 +289,72 @@ impl<T: Send> Model<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<V> View<V> {
|
||||||
|
pub fn condition<Evt>(
|
||||||
|
&self,
|
||||||
|
cx: &TestAppContext,
|
||||||
|
mut predicate: impl FnMut(&V, &AppContext) -> bool,
|
||||||
|
) -> impl Future<Output = ()>
|
||||||
|
where
|
||||||
|
Evt: 'static,
|
||||||
|
V: EventEmitter<Evt>,
|
||||||
|
{
|
||||||
|
use postage::prelude::{Sink as _, Stream as _};
|
||||||
|
|
||||||
|
let (tx, mut rx) = postage::mpsc::channel(1024);
|
||||||
|
let timeout_duration = Duration::from_millis(100); //todo!() cx.condition_duration();
|
||||||
|
|
||||||
|
let mut cx = cx.app.borrow_mut();
|
||||||
|
let subscriptions = (
|
||||||
|
cx.observe(self, {
|
||||||
|
let mut tx = tx.clone();
|
||||||
|
move |_, _| {
|
||||||
|
tx.blocking_send(()).ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
cx.subscribe(self, {
|
||||||
|
let mut tx = tx.clone();
|
||||||
|
move |_, _: &Evt, _| {
|
||||||
|
tx.blocking_send(()).ok();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cx = cx.this.upgrade().unwrap();
|
||||||
|
let handle = self.downgrade();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
crate::util::timeout(timeout_duration, async move {
|
||||||
|
loop {
|
||||||
|
{
|
||||||
|
let cx = cx.borrow();
|
||||||
|
let cx = &*cx;
|
||||||
|
if predicate(
|
||||||
|
handle
|
||||||
|
.upgrade()
|
||||||
|
.expect("view dropped with pending condition")
|
||||||
|
.read(cx),
|
||||||
|
cx,
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo!(start_waiting)
|
||||||
|
// cx.borrow().foreground_executor().start_waiting();
|
||||||
|
rx.recv()
|
||||||
|
.await
|
||||||
|
.expect("view dropped with pending condition");
|
||||||
|
// cx.borrow().foreground_executor().finish_waiting();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.expect("condition timed out");
|
||||||
|
drop(subscriptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use derive_more::{Deref, DerefMut};
|
use derive_more::{Deref, DerefMut};
|
||||||
#[derive(Deref, DerefMut)]
|
#[derive(Deref, DerefMut)]
|
||||||
pub struct VisualTestContext<'a> {
|
pub struct VisualTestContext<'a> {
|
||||||
|
|
|
@ -167,7 +167,7 @@ impl TryFrom<&'_ str> for Rgba {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Copy, Clone, Debug, PartialEq, PartialOrd)]
|
#[derive(Default, Copy, Clone, Debug)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Hsla {
|
pub struct Hsla {
|
||||||
pub h: f32,
|
pub h: f32,
|
||||||
|
@ -176,6 +176,35 @@ pub struct Hsla {
|
||||||
pub a: f32,
|
pub a: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Hsla {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.h
|
||||||
|
.total_cmp(&other.h)
|
||||||
|
.then(self.s.total_cmp(&other.s))
|
||||||
|
.then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a)))
|
||||||
|
.is_eq()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Hsla {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
// SAFETY: The total ordering relies on this always being Some()
|
||||||
|
Some(
|
||||||
|
self.h
|
||||||
|
.total_cmp(&other.h)
|
||||||
|
.then(self.s.total_cmp(&other.s))
|
||||||
|
.then(self.l.total_cmp(&other.l).then(self.a.total_cmp(&other.a))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Hsla {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
// SAFETY: The partial comparison is a total comparison
|
||||||
|
unsafe { self.partial_cmp(other).unwrap_unchecked() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Hsla {
|
impl Hsla {
|
||||||
pub fn to_rgb(self) -> Rgba {
|
pub fn to_rgb(self) -> Rgba {
|
||||||
self.into()
|
self.into()
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures::Future;
|
||||||
|
use smol::future::FutureExt;
|
||||||
pub use util::*;
|
pub use util::*;
|
||||||
|
|
||||||
// pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
|
||||||
// where
|
where
|
||||||
// F: Future<Output = T>,
|
F: Future<Output = T>,
|
||||||
// {
|
{
|
||||||
// let timer = async {
|
let timer = async {
|
||||||
// smol::Timer::after(timeout).await;
|
smol::Timer::after(timeout).await;
|
||||||
// Err(())
|
Err(())
|
||||||
// };
|
};
|
||||||
// let future = async move { Ok(f.await) };
|
let future = async move { Ok(f.await) };
|
||||||
// timer.race(future).await
|
timer.race(future).await
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
|
pub struct CwdBacktrace<'a>(pub &'a backtrace::Backtrace);
|
||||||
|
|
|
@ -4200,24 +4200,24 @@ impl ViewId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub trait WorkspaceHandle {
|
pub trait WorkspaceHandle {
|
||||||
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
|
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath>;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl WorkspaceHandle for View<Workspace> {
|
impl WorkspaceHandle for View<Workspace> {
|
||||||
// fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
|
fn file_project_paths(&self, cx: &AppContext) -> Vec<ProjectPath> {
|
||||||
// self.read(cx)
|
self.read(cx)
|
||||||
// .worktrees(cx)
|
.worktrees(cx)
|
||||||
// .flat_map(|worktree| {
|
.flat_map(|worktree| {
|
||||||
// let worktree_id = worktree.read(cx).id();
|
let worktree_id = worktree.read(cx).id();
|
||||||
// worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
|
worktree.read(cx).files(true, 0).map(move |f| ProjectPath {
|
||||||
// worktree_id,
|
worktree_id,
|
||||||
// path: f.path.clone(),
|
path: f.path.clone(),
|
||||||
// })
|
})
|
||||||
// })
|
})
|
||||||
// .collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// impl std::fmt::Debug for OpenPaths {
|
// impl std::fmt::Debug for OpenPaths {
|
||||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue