From b2dd8d32c3a091b0ee2e5e457800a12dbfdfe87a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 31 Mar 2026 10:42:50 -0700 Subject: [PATCH] Add failing test for unwanted reload during format-on-save --- .../tests/integration/project_tests.rs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index 3230df665557077ed2f50142815242e7caef85a4..b3b719fddb1b460b1add10bc024b5685986a746e 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -6094,6 +6094,107 @@ async fn test_dirty_buffer_reloads_after_undo(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_save_does_not_reload_when_format_removes_user_edits(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/dir"), + json!({ + "file.txt": "hello\nworld\n", + }), + ) + .await; + + let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await; + let buffer = project + .update(cx, |p, cx| p.open_local_buffer(path!("/dir/file.txt"), cx)) + .await + .unwrap(); + + buffer.read_with(cx, |buffer, _| { + assert_eq!(buffer.text(), "hello\nworld\n"); + assert!(!buffer.is_dirty()); + }); + + // User adds trailing whitespace — the only edit. + buffer.update(cx, |buffer, cx| { + buffer.edit([(5..5, " ")], None, cx); + }); + + buffer.read_with(cx, |buffer, _| { + assert_eq!(buffer.text(), "hello \nworld\n"); + assert!(buffer.is_dirty()); + }); + + // An external tool writes different content to the file while the buffer is dirty. + fs.save( + path!("/dir/file.txt").as_ref(), + &"EXTERNAL CONTENT\n".into(), + Default::default(), + ) + .await + .unwrap(); + cx.executor().run_until_parked(); + + // The buffer has a conflict: file mtime changed and buffer has unsaved edits. + buffer.read_with(cx, |buffer, _| { + assert!(buffer.is_dirty()); + assert!(buffer.has_conflict()); + assert_eq!(buffer.text(), "hello \nworld\n"); + assert_ne!( + buffer.file().unwrap().disk_state().mtime(), + buffer.saved_mtime(), + "disk mtime should differ from saved mtime after external write" + ); + }); + + // The user triggers a save, which formats the buffer then saves it. + let buffers = [buffer.clone()].into_iter().collect::>(); + project + .update(cx, |project, cx| { + project.format( + buffers, + project::lsp_store::LspFormatTarget::Buffers, + true, + project::lsp_store::FormatTrigger::Save, + cx, + ) + }) + .await + .unwrap(); + + // After formatting, the trailing whitespace was removed. + // The buffer text should match what will be saved to disk. + let formatted_text = buffer.read_with(cx, |buffer, _| buffer.text()); + assert_eq!(formatted_text, "hello\nworld\n"); + cx.executor().run_until_parked(); + + // The buffer text must still be the formatted text — not reloaded from disk. + buffer.read_with(cx, |buffer, _| { + assert_eq!( + buffer.text(), + formatted_text, + "buffer should not have been reloaded from disk during format-on-save" + ); + }); + + // Now save to disk. + project + .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)) + .await + .unwrap(); + cx.executor().run_until_parked(); + + // After save, the buffer should be clean and match disk. + buffer.read_with(cx, |buffer, _| { + assert_eq!(buffer.text(), "hello\nworld\n"); + assert!(!buffer.is_dirty()); + assert!(!buffer.has_conflict()); + }); +} + #[gpui::test] async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) { init_test(cx);