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,
  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::{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.
 48pub fn marked_display_snapshot(
 49    text: &str,
 50    cx: &mut gpui::App,
 51) -> (DisplaySnapshot, Vec<DisplayPoint>) {
 52    let (unmarked_text, markers) = marked_text_offsets(text);
 53
 54    let font = Font {
 55        family: "Zed Plex Mono".into(),
 56        features: FontFeatures::default(),
 57        fallbacks: None,
 58        weight: FontWeight::default(),
 59        style: FontStyle::default(),
 60    };
 61    let font_size: Pixels = 14usize.into();
 62
 63    let buffer = MultiBuffer::build_simple(&unmarked_text, cx);
 64    let display_map = cx.new(|cx| {
 65        DisplayMap::new(
 66            buffer,
 67            font,
 68            font_size,
 69            None,
 70            1,
 71            1,
 72            FoldPlaceholder::test(),
 73            DiagnosticSeverity::Warning,
 74            cx,
 75        )
 76    });
 77    let snapshot = display_map.update(cx, |map, cx| map.snapshot(cx));
 78    let markers = markers
 79        .into_iter()
 80        .map(|offset| offset.to_display_point(&snapshot))
 81        .collect();
 82
 83    (snapshot, markers)
 84}
 85
 86pub fn select_ranges(
 87    editor: &mut Editor,
 88    marked_text: &str,
 89    window: &mut Window,
 90    cx: &mut Context<Editor>,
 91) {
 92    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
 93    assert_eq!(editor.text(cx), unmarked_text);
 94    editor.change_selections(None, window, cx, |s| s.select_ranges(text_ranges));
 95}
 96
 97#[track_caller]
 98pub fn assert_text_with_selections(
 99    editor: &mut Editor,
100    marked_text: &str,
101    cx: &mut Context<Editor>,
102) {
103    let (unmarked_text, text_ranges) = marked_text_ranges(marked_text, true);
104    assert_eq!(editor.text(cx), unmarked_text, "text doesn't match");
105    assert_eq!(
106        editor.selections.ranges(cx),
107        text_ranges,
108        "selections don't match",
109    );
110}
111
112// RA thinks this is dead code even though it is used in a whole lot of tests
113#[allow(dead_code)]
114#[cfg(any(test, feature = "test-support"))]
115pub(crate) fn build_editor(
116    buffer: Entity<MultiBuffer>,
117    window: &mut Window,
118    cx: &mut Context<Editor>,
119) -> Editor {
120    Editor::new(EditorMode::full(), buffer, None, window, cx)
121}
122
123pub(crate) fn build_editor_with_project(
124    project: Entity<Project>,
125    buffer: Entity<MultiBuffer>,
126    window: &mut Window,
127    cx: &mut Context<Editor>,
128) -> Editor {
129    Editor::new(EditorMode::full(), buffer, Some(project), window, cx)
130}
131
132#[derive(Default)]
133struct TestBlockContent(
134    HashMap<(EntityId, CustomBlockId), Rc<dyn Fn(&mut VisualTestContext) -> String>>,
135);
136
137impl gpui::Global for TestBlockContent {}
138
139pub fn set_block_content_for_tests(
140    editor: &Entity<Editor>,
141    id: CustomBlockId,
142    cx: &mut App,
143    f: impl Fn(&mut VisualTestContext) -> String + 'static,
144) {
145    cx.update_default_global::<TestBlockContent, _>(|bc, _| {
146        bc.0.insert((editor.entity_id(), id), Rc::new(f))
147    });
148}
149
150pub fn block_content_for_tests(
151    editor: &Entity<Editor>,
152    id: CustomBlockId,
153    cx: &mut VisualTestContext,
154) -> Option<String> {
155    let f = cx.update(|_, cx| {
156        cx.default_global::<TestBlockContent>()
157            .0
158            .get(&(editor.entity_id(), id))
159            .cloned()
160    })?;
161    Some(f(cx))
162}
163
164pub fn editor_content_with_blocks(editor: &Entity<Editor>, cx: &mut VisualTestContext) -> String {
165    cx.draw(
166        gpui::Point::default(),
167        size(px(3000.0), px(3000.0)),
168        |_, _| editor.clone(),
169    );
170    let (snapshot, mut lines, blocks) = editor.update_in(cx, |editor, window, cx| {
171        let snapshot = editor.snapshot(window, cx);
172        let text = editor.display_text(cx);
173        let lines = text.lines().map(|s| s.to_string()).collect::<Vec<String>>();
174        let blocks = snapshot
175            .blocks_in_range(DisplayRow(0)..snapshot.max_point().row())
176            .map(|(row, block)| (row, block.clone()))
177            .collect::<Vec<_>>();
178        (snapshot, lines, blocks)
179    });
180    for (row, block) in blocks {
181        match block {
182            Block::Custom(custom_block) => {
183                if let BlockPlacement::Near(x) = &custom_block.placement {
184                    if snapshot.intersects_fold(x.to_point(&snapshot.buffer_snapshot)) {
185                        continue;
186                    }
187                };
188                let content = block_content_for_tests(&editor, custom_block.id, cx)
189                    .expect("block content not found");
190                // 2: "related info 1 for diagnostic 0"
191                if let Some(height) = custom_block.height {
192                    if height == 0 {
193                        lines[row.0 as usize - 1].push_str(" § ");
194                        lines[row.0 as usize - 1].push_str(&content);
195                    } else {
196                        let block_lines = content.lines().collect::<Vec<_>>();
197                        assert_eq!(block_lines.len(), height as usize);
198                        lines[row.0 as usize].push_str("§ ");
199                        lines[row.0 as usize].push_str(block_lines[0].trim_end());
200                        for i in 1..height as usize {
201                            if row.0 as usize + i >= lines.len() {
202                                lines.push("".to_string());
203                            };
204                            lines[row.0 as usize + i].push_str("§ ");
205                            lines[row.0 as usize + i].push_str(block_lines[i].trim_end());
206                        }
207                    }
208                }
209            }
210            Block::FoldedBuffer {
211                first_excerpt,
212                height,
213            } => {
214                lines[row.0 as usize].push_str(&cx.update(|_, cx| {
215                    format!(
216                        "§ {}",
217                        first_excerpt
218                            .buffer
219                            .file()
220                            .unwrap()
221                            .file_name(cx)
222                            .to_string_lossy()
223                    )
224                }));
225                for row in row.0 + 1..row.0 + height {
226                    lines[row as usize].push_str("§ -----");
227                }
228            }
229            Block::ExcerptBoundary {
230                excerpt,
231                height,
232                starts_new_buffer,
233            } => {
234                if starts_new_buffer {
235                    lines[row.0 as usize].push_str(&cx.update(|_, cx| {
236                        format!(
237                            "§ {}",
238                            excerpt
239                                .buffer
240                                .file()
241                                .unwrap()
242                                .file_name(cx)
243                                .to_string_lossy()
244                        )
245                    }));
246                } else {
247                    lines[row.0 as usize].push_str("§ -----")
248                }
249                for row in row.0 + 1..row.0 + height {
250                    lines[row as usize].push_str("§ -----");
251                }
252            }
253        }
254    }
255    lines.join("\n")
256}