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