use std::path::Path; use futures::channel::oneshot;use vec1::vec1;use warp_editor::content::buffer::{InitialBufferState, SelectionOffsets};use warp_editor::multiline::MultilineString;use warp_util::content_version::ContentVersion;use warpui::App; use super::*;use crate::code::editor::line::EditorLineLocation;use crate::code::editor::view::code_text_styles;use crate::settings::FontSettings;use crate::test_util::settings::initialize_settings_for_tests; fn initialize_deps(app: &mut App) { app.add_singleton_model(|_| Appearance::mock()); initialize_settings_for_tests(app);} fn mock_model(app: &mut App, text: &str, version: ContentVersion) -> ModelHandle<CodeEditorModel> { app.add_model(|ctx| { let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None); let mut model = CodeEditorModel::new(styles, None, false, None, ctx); let state = InitialBufferState::plain_text(text).with_version(version); model.reset_content(state, ctx); model.set_language_with_local_path(Path::new("/test.rs"), ctx); model })} /// Builds a `CodeEditorModel` that shares an existing `Buffer` rather than creating its own./// Used to reproduce configurations where multiple editors point at the same underlying content/// (e.g. two views of the same open file), such as via `CodeEditorView::new`'s optional shared/// `Buffer` parameter.fn mock_model_with_buffer( app: &mut App, buffer: ModelHandle<Buffer>,) -> ModelHandle<CodeEditorModel> { app.add_model(|ctx| { let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None); let mut model = CodeEditorModel::new(styles, None, false, Some(buffer), ctx); model.set_language_with_local_path(Path::new("/test.rs"), ctx); model })} fn mock_model_with_diff( app: &mut App, base_text: &str, current_text: &str, version: ContentVersion,) -> ModelHandle<CodeEditorModel> { app.add_model(|ctx| { let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None); let mut model = CodeEditorModel::new(styles, None, false, None, ctx); let state = InitialBufferState::plain_text(current_text).with_version(version); model.reset_content(state, ctx); model.set_language_with_local_path(Path::new("/test.rs"), ctx); // Set up diff model with base text model.diff().update(ctx, |diff, _| { diff.set_base(MultilineString::apply(base_text)); }); model })} async fn layout_model(app: &mut App, model: &ModelHandle<CodeEditorModel>) { app.read(|ctx| model.as_ref(ctx).render_state.as_ref(ctx).layout_complete()) .await;} #[test]fn test_two_editors_sharing_a_buffer_both_lay_out_a_large_content_replace() { // Regression test for APP-4844: `CodeEditorView::new` can pass an existing shared `Buffer` // into `CodeEditorModel::new`, so two editors can point at the same underlying content (for // example, two views of the same open file). Both editors independently subscribe to and // clone the `ContentChanged` event's `EditDelta` emitted by a full content replace, so more // than one clone of `new_lines` can be alive when either editor lays out. This exercises that // configuration end to end: it must not panic, and both editors must lay out the new content // correctly regardless of how many clones of the delta are alive when their layout runs. App::test((), |mut app| async move { initialize_deps(&mut app); let shared_buffer = app.add_model(|_| Buffer::default()); let editor_a = mock_model_with_buffer(&mut app, shared_buffer.clone()); let editor_b = mock_model_with_buffer(&mut app, shared_buffer.clone()); let content = "line one\nline two\nline three\n".repeat(50); shared_buffer.update(&mut app, |buffer, ctx| { buffer.replace_all(content.clone(), ctx); }); layout_model(&mut app, &editor_a).await; layout_model(&mut app, &editor_b).await; for editor in [&editor_a, &editor_b] { editor.read(&app, |editor, ctx| { let text = editor.content.as_ref(ctx).text().as_str().to_string(); assert!( text.contains("line three"), "expected replaced content, got: {text}" ); assert_eq!(text.matches("line one").count(), 50); assert!(editor.render_state.as_ref(ctx).blocks() > 1); }); } })} #[test]fn replace_first_n_characters_handles_incremental_unicode_prefix() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, "suffix", ContentVersion::new()); editor.update(&mut app, |editor, ctx| { editor.replace_first_n_characters(CharOffset::from(0), "é", ctx); editor.replace_first_n_characters(CharOffset::from(1), "élan", ctx); }); editor.read(&app, |editor, ctx| { assert_eq!(editor.content().as_ref(ctx).text().as_str(), "élansuffix"); }); });} #[test]fn test_no_trailing_newline() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, "", ContentVersion::new()); // We need to layout the model to be able to select by line boundaries. layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(1), ctx); }); // At the start we should have a trailing newline (the same height as an empty line). let old_height = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height()); editor.update(&mut app, |editor, ctx| { editor.insert("a", EditOrigin::UserTyped, ctx); }); layout_model(&mut app, &editor).await; // After insertion, we should replace the trailing newline with an actual text line. let height_after_insertion = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height()); assert_eq!(old_height, height_after_insertion); editor.update(&mut app, |editor, ctx| { editor.enter(ctx); }); layout_model(&mut app, &editor).await; // Enter should create a newline, which adds to the height. let height_after_newline = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height()); assert_ne!(old_height, height_after_newline); })} #[test]fn test_toggle_comment() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, " First\n Second", ContentVersion::new()); // We need to layout the model to be able to select by line boundaries. layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(5), ctx); editor.select_to_line_end(ctx); }); editor.update(&mut app, |editor, ctx| { assert_eq!( editor.selections(ctx), vec1![SelectionOffsets { head: CharOffset::from(10), tail: CharOffset::from(5) }] ); editor.toggle_comments(ctx); }); // Toggling comment for the first line. editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " // First\n Second" ); }); editor.update(&mut app, |editor, ctx| { editor.select_all(ctx); }); // Not all selected lines have comment prefix. This should add the prefix again. editor.update(&mut app, |editor, ctx| { editor.toggle_comments(ctx); }); editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " // // First\n // Second" ); }); editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(20), ctx); editor.select_to_line_end(ctx); }); editor.update(&mut app, |editor, ctx| { editor.toggle_comments(ctx); }); // Toggling comment on a line with comment should uncomment it. editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " // // First\n Second" ); }); })} #[test]fn test_apply_diffs() { App::test((), |mut app| async move { initialize_deps(&mut app); // Historically, applying diffs at the very start of an empty buffer (line range 0..0) // could preserve the buffer's internal trailing-newline/block marker, resulting in an // extra newline in the final content. This guards against that by ensuring we replace the // implicit initial block correctly and do not end up with an extra trailing newline. let editor_zero = mock_model(&mut app, "", ContentVersion::new()); layout_model(&mut app, &editor_zero).await; editor_zero.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 0..0, insertion: "First\nSecond\n".to_string(), }], ctx, ); }); editor_zero.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), "First\nSecond\n" ); }); let editor = mock_model( &mut app, " First\n Second\n Third", ContentVersion::new(), ); // We need to layout the model to be able to select by line boundaries. layout_model(&mut app, &editor).await; // Put cursor on the second line between S and e. editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(16), ctx); }); editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 2..3, insertion: " Fourth\n Fifth\n".to_string(), }], ctx, ); }); // Inserted content should have the same indentation level. // The selections should not change after the insertion. editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " First\n Fourth\n Fifth\n Third" ); assert_eq!( editor.selections(ctx), vec1![SelectionOffsets { head: CharOffset::from(16), tail: CharOffset::from(16) }] ); }); })} #[test]fn test_apply_diff_on_empty_buffer() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, "", ContentVersion::new()); layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 0..0, insertion: "First line\nSecond line\n".to_string(), }], ctx, ); }); editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), "First line\nSecond line\n" ); assert_eq!( editor.selections(ctx), vec1![SelectionOffsets { head: CharOffset::from(24), tail: CharOffset::from(24) }] ); }); })} #[test]fn test_apply_single_deletion() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model( &mut app, " First\n Second\n Third", ContentVersion::new(), ); layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 1..2, insertion: "".to_string(), }], ctx, ); }); editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " Second\n Third" ); }); editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 2..3, insertion: "".to_string(), }], ctx, ); }); editor.read(&app, |editor, ctx| { assert_eq!(editor.content.as_ref(ctx).text().as_str(), " Second\n"); }); })} // #[test]// fn test_indent() {// App::test((), |mut app| async move {// initialize_deps(&mut app);// let editor = mock_model(&mut app, "fn test() {\ns\n{\ntest\n}\n}");// layout_model(&mut app, &editor).await; // editor.update(&mut app, |editor, ctx| {// editor.cursor_at(CharOffset::from(13), ctx);// }); // editor.update(&mut app, |editor, ctx| {// editor.indent(false, ctx);// }); // // Indent with one expected indent level.// editor.read(&app, |editor, ctx| {// assert_eq!(// editor.content.as_ref(ctx).text(),// "fn test() {\n s\n{\ntest\n}\n}"// );// }); // editor.update(&mut app, |editor, ctx| {// editor.cursor_at(CharOffset::from(21), ctx);// }); // editor.update(&mut app, |editor, ctx| {// editor.indent(false, ctx);// }); // // Indent with two expected indent level.// editor.read(&app, |editor, ctx| {// assert_eq!(// editor.content.as_ref(ctx).text(),// "fn test() {\n s\n{\n test\n}\n}"// );// }); // editor.update(&mut app, |editor, ctx| {// editor.undo(ctx);// }); // editor.update(&mut app, |editor, ctx| {// editor.cursor_at(CharOffset::from(22), ctx);// }); // editor.update(&mut app, |editor, ctx| {// editor.indent(false, ctx);// }); // // Indent in the middle of the text should only advance one indent level.// editor.read(&app, |editor, ctx| {// // TODO(kevin): Looks like there is a bug here where the indentation should be 4,// // it is currently 3.// assert_eq!(// editor.content.as_ref(ctx).text(),// "fn test() {\n s\n{\nt est\n}\n}"// );// });// })// } // This test flakes occasionally. I think it could be due to how we are initializing tree-sitter// in unit tests.// TODO(kevin): Re-enable this// #[test]// fn test_bracket_expansion() {// App::test((), |mut app| async move {// initialize_deps(&mut app);// let editor = mock_model(&mut app, "fn test() {}");// layout_model(&mut app, &editor).await; // editor.update(&mut app, |editor, ctx| {// editor.cursor_at(CharOffset::from(9), ctx);// }); // // Bracket expansion in parentheses.// editor.update(&mut app, |editor, ctx| {// editor.enter(ctx);// }); // editor.read(&app, |editor, ctx| {// assert_eq!(// editor.selections(ctx),// vec1![SelectionOffsets {// head: CharOffset::from(14),// tail: CharOffset::from(14)// }]// );// assert_eq!(editor.content.as_ref(ctx).text(), "fn test(\n \n) {}");// }); // editor.update(&mut app, |editor, ctx| {// editor.cursor_at(CharOffset::from(18), ctx);// }); // editor.update(&mut app, |editor, ctx| {// editor.enter(ctx);// }); // // Bracket expansion in brackets.// editor.read(&app, |editor, ctx| {// assert_eq!(// editor.selections(ctx),// vec1![SelectionOffsets {// head: CharOffset::from(23),// tail: CharOffset::from(23)// }]// );// assert_eq!(// editor.content.as_ref(ctx).text(),// "fn test(\n \n) {\n \n}"// );// });// })// } #[test]fn test_move_by_word() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model( &mut app, "fn test() {}\nfn test2() {}",Lines 1–500 of 1241. Load more
use std::path::Path; use futures::channel::oneshot; use vec1::vec1; use warp_editor::content::buffer::{InitialBufferState, SelectionOffsets}; use warp_editor::multiline::MultilineString; use warp_util::content_version::ContentVersion; use warpui::App; use super::*; use crate::code::editor::line::EditorLineLocation; use crate::code::editor::view::code_text_styles; use crate::settings::FontSettings; use crate::test_util::settings::initialize_settings_for_tests; fn initialize_deps(app: &mut App) { app.add_singleton_model(|_| Appearance::mock()); initialize_settings_for_tests(app); } fn mock_model(app: &mut App, text: &str, version: ContentVersion) -> ModelHandle<CodeEditorModel> { app.add_model(|ctx| { let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None); let mut model = CodeEditorModel::new(styles, None, false, None, ctx); let state = InitialBufferState::plain_text(text).with_version(version); model.reset_content(state, ctx); model.set_language_with_local_path(Path::new("/test.rs"), ctx); model }) } /// Builds a `CodeEditorModel` that shares an existing `Buffer` rather than creating its own. /// Used to reproduce configurations where multiple editors point at the same underlying content /// (e.g. two views of the same open file), such as via `CodeEditorView::new`'s optional shared /// `Buffer` parameter. fn mock_model_with_buffer( app: &mut App, buffer: ModelHandle<Buffer>, ) -> ModelHandle<CodeEditorModel> { app.add_model(|ctx| { let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None); let mut model = CodeEditorModel::new(styles, None, false, Some(buffer), ctx); model.set_language_with_local_path(Path::new("/test.rs"), ctx); model }) } fn mock_model_with_diff( app: &mut App, base_text: &str, current_text: &str, version: ContentVersion, ) -> ModelHandle<CodeEditorModel> { app.add_model(|ctx| { let styles = code_text_styles(Appearance::as_ref(ctx), FontSettings::as_ref(ctx), None); let mut model = CodeEditorModel::new(styles, None, false, None, ctx); let state = InitialBufferState::plain_text(current_text).with_version(version); model.reset_content(state, ctx); model.set_language_with_local_path(Path::new("/test.rs"), ctx); // Set up diff model with base text model.diff().update(ctx, |diff, _| { diff.set_base(MultilineString::apply(base_text)); }); model }) } async fn layout_model(app: &mut App, model: &ModelHandle<CodeEditorModel>) { app.read(|ctx| model.as_ref(ctx).render_state.as_ref(ctx).layout_complete()) .await; } #[test] fn test_two_editors_sharing_a_buffer_both_lay_out_a_large_content_replace() { // Regression test for APP-4844: `CodeEditorView::new` can pass an existing shared `Buffer` // into `CodeEditorModel::new`, so two editors can point at the same underlying content (for // example, two views of the same open file). Both editors independently subscribe to and // clone the `ContentChanged` event's `EditDelta` emitted by a full content replace, so more // than one clone of `new_lines` can be alive when either editor lays out. This exercises that // configuration end to end: it must not panic, and both editors must lay out the new content // correctly regardless of how many clones of the delta are alive when their layout runs. App::test((), |mut app| async move { initialize_deps(&mut app); let shared_buffer = app.add_model(|_| Buffer::default()); let editor_a = mock_model_with_buffer(&mut app, shared_buffer.clone()); let editor_b = mock_model_with_buffer(&mut app, shared_buffer.clone()); let content = "line one\nline two\nline three\n".repeat(50); shared_buffer.update(&mut app, |buffer, ctx| { buffer.replace_all(content.clone(), ctx); }); layout_model(&mut app, &editor_a).await; layout_model(&mut app, &editor_b).await; for editor in [&editor_a, &editor_b] { editor.read(&app, |editor, ctx| { let text = editor.content.as_ref(ctx).text().as_str().to_string(); assert!( text.contains("line three"), "expected replaced content, got: {text}" ); assert_eq!(text.matches("line one").count(), 50); assert!(editor.render_state.as_ref(ctx).blocks() > 1); }); } }) } #[test] fn replace_first_n_characters_handles_incremental_unicode_prefix() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, "suffix", ContentVersion::new()); editor.update(&mut app, |editor, ctx| { editor.replace_first_n_characters(CharOffset::from(0), "é", ctx); editor.replace_first_n_characters(CharOffset::from(1), "élan", ctx); }); editor.read(&app, |editor, ctx| { assert_eq!(editor.content().as_ref(ctx).text().as_str(), "élansuffix"); }); }); } #[test] fn test_no_trailing_newline() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, "", ContentVersion::new()); // We need to layout the model to be able to select by line boundaries. layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(1), ctx); }); // At the start we should have a trailing newline (the same height as an empty line). let old_height = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height()); editor.update(&mut app, |editor, ctx| { editor.insert("a", EditOrigin::UserTyped, ctx); }); layout_model(&mut app, &editor).await; // After insertion, we should replace the trailing newline with an actual text line. let height_after_insertion = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height()); assert_eq!(old_height, height_after_insertion); editor.update(&mut app, |editor, ctx| { editor.enter(ctx); }); layout_model(&mut app, &editor).await; // Enter should create a newline, which adds to the height. let height_after_newline = app.read(|ctx| editor.as_ref(ctx).render_state().as_ref(ctx).height()); assert_ne!(old_height, height_after_newline); }) } #[test] fn test_toggle_comment() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, " First\n Second", ContentVersion::new()); // We need to layout the model to be able to select by line boundaries. layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(5), ctx); editor.select_to_line_end(ctx); }); editor.update(&mut app, |editor, ctx| { assert_eq!( editor.selections(ctx), vec1![SelectionOffsets { head: CharOffset::from(10), tail: CharOffset::from(5) }] ); editor.toggle_comments(ctx); }); // Toggling comment for the first line. editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " // First\n Second" ); }); editor.update(&mut app, |editor, ctx| { editor.select_all(ctx); }); // Not all selected lines have comment prefix. This should add the prefix again. editor.update(&mut app, |editor, ctx| { editor.toggle_comments(ctx); }); editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " // // First\n // Second" ); }); editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(20), ctx); editor.select_to_line_end(ctx); }); editor.update(&mut app, |editor, ctx| { editor.toggle_comments(ctx); }); // Toggling comment on a line with comment should uncomment it. editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " // // First\n Second" ); }); }) } #[test] fn test_apply_diffs() { App::test((), |mut app| async move { initialize_deps(&mut app); // Historically, applying diffs at the very start of an empty buffer (line range 0..0) // could preserve the buffer's internal trailing-newline/block marker, resulting in an // extra newline in the final content. This guards against that by ensuring we replace the // implicit initial block correctly and do not end up with an extra trailing newline. let editor_zero = mock_model(&mut app, "", ContentVersion::new()); layout_model(&mut app, &editor_zero).await; editor_zero.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 0..0, insertion: "First\nSecond\n".to_string(), }], ctx, ); }); editor_zero.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), "First\nSecond\n" ); }); let editor = mock_model( &mut app, " First\n Second\n Third", ContentVersion::new(), ); // We need to layout the model to be able to select by line boundaries. layout_model(&mut app, &editor).await; // Put cursor on the second line between S and e. editor.update(&mut app, |editor, ctx| { editor.cursor_at(CharOffset::from(16), ctx); }); editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 2..3, insertion: " Fourth\n Fifth\n".to_string(), }], ctx, ); }); // Inserted content should have the same indentation level. // The selections should not change after the insertion. editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " First\n Fourth\n Fifth\n Third" ); assert_eq!( editor.selections(ctx), vec1![SelectionOffsets { head: CharOffset::from(16), tail: CharOffset::from(16) }] ); }); }) } #[test] fn test_apply_diff_on_empty_buffer() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model(&mut app, "", ContentVersion::new()); layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 0..0, insertion: "First line\nSecond line\n".to_string(), }], ctx, ); }); editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), "First line\nSecond line\n" ); assert_eq!( editor.selections(ctx), vec1![SelectionOffsets { head: CharOffset::from(24), tail: CharOffset::from(24) }] ); }); }) } #[test] fn test_apply_single_deletion() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model( &mut app, " First\n Second\n Third", ContentVersion::new(), ); layout_model(&mut app, &editor).await; editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 1..2, insertion: "".to_string(), }], ctx, ); }); editor.read(&app, |editor, ctx| { assert_eq!( editor.content.as_ref(ctx).text().as_str(), " Second\n Third" ); }); editor.update(&mut app, |editor, ctx| { editor.apply_diffs( vec![DiffDelta { replacement_line_range: 2..3, insertion: "".to_string(), }], ctx, ); }); editor.read(&app, |editor, ctx| { assert_eq!(editor.content.as_ref(ctx).text().as_str(), " Second\n"); }); }) } // #[test] // fn test_indent() { // App::test((), |mut app| async move { // initialize_deps(&mut app); // let editor = mock_model(&mut app, "fn test() {\ns\n{\ntest\n}\n}"); // layout_model(&mut app, &editor).await; // editor.update(&mut app, |editor, ctx| { // editor.cursor_at(CharOffset::from(13), ctx); // }); // editor.update(&mut app, |editor, ctx| { // editor.indent(false, ctx); // }); // // Indent with one expected indent level. // editor.read(&app, |editor, ctx| { // assert_eq!( // editor.content.as_ref(ctx).text(), // "fn test() {\n s\n{\ntest\n}\n}" // ); // }); // editor.update(&mut app, |editor, ctx| { // editor.cursor_at(CharOffset::from(21), ctx); // }); // editor.update(&mut app, |editor, ctx| { // editor.indent(false, ctx); // }); // // Indent with two expected indent level. // editor.read(&app, |editor, ctx| { // assert_eq!( // editor.content.as_ref(ctx).text(), // "fn test() {\n s\n{\n test\n}\n}" // ); // }); // editor.update(&mut app, |editor, ctx| { // editor.undo(ctx); // }); // editor.update(&mut app, |editor, ctx| { // editor.cursor_at(CharOffset::from(22), ctx); // }); // editor.update(&mut app, |editor, ctx| { // editor.indent(false, ctx); // }); // // Indent in the middle of the text should only advance one indent level. // editor.read(&app, |editor, ctx| { // // TODO(kevin): Looks like there is a bug here where the indentation should be 4, // // it is currently 3. // assert_eq!( // editor.content.as_ref(ctx).text(), // "fn test() {\n s\n{\nt est\n}\n}" // ); // }); // }) // } // This test flakes occasionally. I think it could be due to how we are initializing tree-sitter // in unit tests. // TODO(kevin): Re-enable this // #[test] // fn test_bracket_expansion() { // App::test((), |mut app| async move { // initialize_deps(&mut app); // let editor = mock_model(&mut app, "fn test() {}"); // layout_model(&mut app, &editor).await; // editor.update(&mut app, |editor, ctx| { // editor.cursor_at(CharOffset::from(9), ctx); // }); // // Bracket expansion in parentheses. // editor.update(&mut app, |editor, ctx| { // editor.enter(ctx); // }); // editor.read(&app, |editor, ctx| { // assert_eq!( // editor.selections(ctx), // vec1![SelectionOffsets { // head: CharOffset::from(14), // tail: CharOffset::from(14) // }] // ); // assert_eq!(editor.content.as_ref(ctx).text(), "fn test(\n \n) {}"); // }); // editor.update(&mut app, |editor, ctx| { // editor.cursor_at(CharOffset::from(18), ctx); // }); // editor.update(&mut app, |editor, ctx| { // editor.enter(ctx); // }); // // Bracket expansion in brackets. // editor.read(&app, |editor, ctx| { // assert_eq!( // editor.selections(ctx), // vec1![SelectionOffsets { // head: CharOffset::from(23), // tail: CharOffset::from(23) // }] // ); // assert_eq!( // editor.content.as_ref(ctx).text(), // "fn test(\n \n) {\n \n}" // ); // }); // }) // } #[test] fn test_move_by_word() { App::test((), |mut app| async move { initialize_deps(&mut app); let editor = mock_model( &mut app, "fn test() {}\nfn test2() {}",