test.rs

  1pub mod editor_lsp_test_context;
  2pub mod editor_test_context;
  3
  4use std::{rc::Rc, sync::LazyLock};
  5
  6pub use crate::rust_analyzer_ext::expand_macro_recursively;
  7use crate::{
  8    DisplayPoint, Editor, EditorMode, FoldPlaceholder, MultiBuffer, SelectionEffects,
  9    display_map::{
 10        Block, BlockPlacement, CustomBlockId, DisplayMap, DisplayRow, DisplaySnapshot,
 11        ToDisplayPoint,
 12    },
 13};
 14use collections::HashMap;
 15use gpui::{
 16    AppContext as _, Context, Entity, EntityId, Font, FontFeatures, FontStyle, FontWeight, Pixels,
 17    VisualTestContext, Window, font, size,
 18};
 19use multi_buffer::ToPoint;
 20use pretty_assertions::assert_eq;
 21use project::{Project, project_settings::DiagnosticSeverity};
 22use ui::{App, BorrowAppContext, px};
 23use util::test::{generate_marked_text, marked_text_offsets, marked_text_ranges};
 24
 25#[cfg(test)]
 26#[ctor::ctor]
 27fn init_logger() {
 28    zlog::init_test();
 29}
 30
 31pub fn test_font() -> Font {
 32    static TEST_FONT: LazyLock<Font> = LazyLock::new(|| {
 33        #[cfg(not(target_os = "windows"))]
 34        {
 35            font("Helvetica")
 36        }
 37
 38        #[cfg(target_os = "windows")]
 39        {
 40            font("Courier New")
 41        }
 42    });
 43
 44    TEST_FONT.clone()
 45}
 46
 47// Returns a snapshot from text containing '|' character markers with the markers removed, and DisplayPoints for each one.
 48#[track_caller]
 49pub fn marked_display_snapshot(
 50    text: &str,
 51    cx: &mut gpui::App,
 52) -> (DisplaySnapshot, Vec<DisplayPoint>) {
 53    let (unmarked_text, markers) = marked_text_offsets(text);
 54
 55    let font = Font {
 56        family: ".ZedMono".into(),
 57        features: FontFeatures::default(),
 58        fallbacks: None,
 59        weight: FontWeight::default(),
 60        style: FontStyle::default(),
 61    };
 62    let font_size: Pixels = 14usize.into();
 63
 64    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
 65    let display_map = cx.new(|cx| {
 66        DisplayMap::new(
 67            buffer,
 68            font,
 69            font_size,
 70            None,
 71            1,
 72            1,
 73            FoldPlaceholder::test(),
 74            DiagnosticSeverity::Warning,
 75            None,
 76            cx,
 77        )
 78    });
 79    let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 80    let markers = markers
 81        .into_iter()
 82        .map(|offset| offset.to_display_point(&snapshot))
 83        .collect();
 84
 85    (snapshot, markers)
 86}
 87
 88#[track_caller]
 89pub fn select_ranges(
 90    editor: &mut Editor,
 91    marked_text: &str,
 92    window: &mut Window,
 93    cx: &mut Context<Editor>,
 94) {
 95    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
 96    assert_eq!(editor.text(cx), unmarked_text);
 97    editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 98        s.select_ranges(text_ranges)
 99    });
100}
101
102#[track_caller]
103pub fn assert_text_with_selections(
104    editor: &mut Editor,
105    marked_text: &str,
106    cx: &mut Context<Editor>,
107) {
108    let (unmarked_text, _text_ranges) = marked_text_ranges(marked_text, true);
109    assert_eq!(editor.text(cx), unmarked_text, "text doesn't match");
110    let actual = generate_marked_text(
111        &editor.text(cx),
112        &editor.selections.ranges(&editor.display_snapshot(cx)),
113        marked_text.contains("«"),
114    );
115    assert_eq!(actual, marked_text, "Selections don't match");
116}
117
118// RA thinks this is dead code even though it is used in a whole lot of tests
119#[allow(dead_code)]
120#[cfg(any(test, feature = "test-support"))]
121pub(crate) fn build_editor(
122    buffer: Entity<MultiBuffer>,
123    window: &mut Window,
124    cx: &mut Context<Editor>,
125) -> Editor {
126    Editor::new(EditorMode::full(), buffer, None, window, cx)
127}
128
129pub(crate) fn build_editor_with_project(
130    project: Entity<Project>,
131    buffer: Entity<MultiBuffer>,
132    window: &mut Window,
133    cx: &mut Context<Editor>,
134) -> Editor {
135    Editor::new(EditorMode::full(), buffer, Some(project), window, cx)
136}
137
138#[derive(Default)]
139struct TestBlockContent(
140    HashMap<(EntityId, CustomBlockId), Rc<dyn Fn(&mut VisualTestContext) -> String>>,
141);
142
143impl gpui::Global for TestBlockContent {}
144
145pub fn set_block_content_for_tests(
146    editor: &Entity<Editor>,
147    id: CustomBlockId,
148    cx: &mut App,
149    f: impl Fn(&mut VisualTestContext) -> String + 'static,
150) {
151    cx.update_default_global::<TestBlockContent, _>(|bc, _| {
152        bc.0.insert((editor.entity_id(), id), Rc::new(f))
153    });
154}
155
156pub fn block_content_for_tests(
157    editor: &Entity<Editor>,
158    id: CustomBlockId,
159    cx: &mut VisualTestContext,
160) -> Option<String> {
161    let f = cx.update(|_, cx| {
162        cx.default_global::<TestBlockContent>()
163            .0
164            .get(&(editor.entity_id(), id))
165            .cloned()
166    })?;
167    Some(f(cx))
168}
169
170pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String {
171    cx.draw(
172        gpui::Point::default(),
173        size(px(3000.0), px(3000.0)),
174        |_, _| editor.clone(),
175    );
176    let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| {
177        let snapshot = editor.snapshot(window, cx);
178        let text = editor.display_text(cx);
179        let lines = text.lines().map(|s| s.to_string()).collect::<Vec<String>>();
180        let blocks = snapshot
181            .blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
182            .map(|(row, block)| (row, block.clone()))
183            .collect::<Vec<_>>();
184        (snapshot, lines, blocks)
185    });
186    for (row, block) in blocks {
187        match block {
188            Block::Custom(custom_block) => {
189                if let BlockPlacement::Near(x) = &custom_block.placement
190                    && snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot()))
191                {
192                    continue;
193                };
194                let content = block_content_for_tests(editor, custom_block.id, cx)
195                    .expect("block content not found");
196                // 2: "related info 1 for diagnostic 0"
197                if let Some(height) = custom_block.height {
198                    if height == 0 {
199                        lines[row.0 as usize - 1].push_str(" § ");
200                        lines[row.0 as usize - 1].push_str(&content);
201                    } else {
202                        let block_lines = content.lines().collect::<Vec<_>>();
203                        assert_eq!(block_lines.len(), height as usize);
204                        lines[row.0 as usize].push_str("§ ");
205                        lines[row.0 as usize].push_str(block_lines[0].trim_end());
206                        for i in 1..height as usize {
207                            if row.0 as usize + i >= lines.len() {
208                                lines.push("".to_string());
209                            };
210                            lines[row.0 as usize + i].push_str("§ ");
211                            lines[row.0 as usize + i].push_str(block_lines[i].trim_end());
212                        }
213                    }
214                }
215            }
216            Block::FoldedBuffer {
217                first_excerpt,
218                height,
219            } => {
220                lines[row.0 as usize].push_str(&cx.update(|_, cx| {
221                    format!("§ {}", first_excerpt.buffer.file().unwrap().file_name(cx))
222                }));
223                for row in row.0 + 1..row.0 + height {
224                    lines[row as usize].push_str("§ -----");
225                }
226            }
227            Block::ExcerptBoundary { height, .. } => {
228                for row in row.0..row.0 + height {
229                    lines[row as usize].push_str("§ -----");
230                }
231            }
232            Block::BufferHeader { excerpt, height } => {
233                lines[row.0 as usize].push_str(
234                    &cx.update(|_, cx| {
235                        format!("§ {}", excerpt.buffer.file().unwrap().file_name(cx))
236                    }),
237                );
238                for row in row.0 + 1..row.0 + height {
239                    lines[row as usize].push_str("§ -----");
240                }
241            }
242            Block::Spacer { height, .. } => {
243                for row in row.0..row.0 + height {
244                    lines[row as usize].push_str("@@@@@@@");
245                }
246            }
247        }
248    }
249    lines.join("\n")
250}