915 lines · 29.5 KB · Rust
RawDownload
use std::sync::{Arc, Mutex}; use remote_server::manager::RemoteServerManagerEvent;use warp_util::remote_path::RemotePath; use super::InternalRemoteDiffState;use crate::auth::AuthStateProvider;use crate::code_review::diff_size_limits::DiffSize;use crate::code_review::diff_state::{    DiffHunk, DiffLine, DiffLineType, DiffMetadata, DiffMetadataAgainstBase, DiffMode, DiffState,    DiffStateModelEvent, DiffStats, FileDiff, FileDiffAndContent, GitDiffData,    GitDiffWithBaseContent, GitFileStatus, RemoteDiffStateModel,};use crate::server::telemetry::context_provider::AppTelemetryContextProvider;use crate::util::git::Commit; impl RemoteDiffStateModel {    fn new_for_test(        mode: DiffMode,        state: InternalRemoteDiffState,        metadata: Option<DiffMetadata>,    ) -> Self {        Self {            remote_path: RemotePath::new(                remote_server::HostId::new("test-host".to_string()),                warp_util::standardized_path::StandardizedPath::try_new("/test/repo")                    .expect("test repo path should be valid and absolute"),            ),            mode,            state,            metadata,            tracked_diff_load_start_time: None,        }    }} fn empty_metadata(branch: &str) -> DiffMetadata {    DiffMetadata {        main_branch_name: "main".to_string(),        current_branch_name: branch.to_string(),        against_head: DiffMetadataAgainstBase {            aggregate_stats: DiffStats::default(),            files: vec![],        },        against_base_branch: None,        has_head_commit: true,        unpushed_commits: vec![],        upstream_ref: None,    }} /// Snapshot inputs grouped for ergonomic test construction. Mirrors the/// argument list of `RemoteDiffStateModel::apply_snapshot`.struct SnapshotInputs {    metadata: Option<DiffMetadata>,    state: DiffState,    diffs: Option<GitDiffWithBaseContent>,} fn loaded_snapshot_with_files(files: Vec<FileDiffAndContent>) -> SnapshotInputs {    let files_changed = files.len();    SnapshotInputs {        metadata: Some(empty_metadata("feature")),        state: DiffState::Loaded,        diffs: Some(GitDiffWithBaseContent {            files,            total_additions: 1,            total_deletions: 0,            files_changed,        }),    }} fn simple_file(path: &str) -> FileDiffAndContent {    simple_file_with_content(path, None)} fn simple_file_with_content(path: &str, content_at_base: Option<&str>) -> FileDiffAndContent {    FileDiffAndContent {        file_diff: FileDiff {            file_path: path.to_string(),            status: GitFileStatus::Modified,            hunks: Arc::new(vec![DiffHunk {                old_start_line: 1,                old_line_count: 1,                new_start_line: 1,                new_line_count: 2,                lines: vec![DiffLine {                    line_type: DiffLineType::Add,                    old_line_number: None,                    new_line_number: Some(2),                    text: "+new line".to_string(),                    no_trailing_newline: false,                }],                unified_diff_start: 0,                unified_diff_end: 1,            }]),            is_binary: false,            is_autogenerated: false,            max_line_number: 10,            has_hidden_bidi_chars: false,            size: DiffSize::Normal,        },        content_at_head: content_at_base.map(str::to_string),    }} fn test_metadata(branch: &str) -> DiffMetadata {    DiffMetadata {        main_branch_name: "main".to_string(),        current_branch_name: branch.to_string(),        against_head: DiffMetadataAgainstBase {            aggregate_stats: DiffStats {                files_changed: 1,                total_additions: 5,                total_deletions: 2,            },            files: vec![],        },        against_base_branch: None,        has_head_commit: true,        unpushed_commits: vec![Commit {            hash: "abc123".to_string(),            subject: "test commit".to_string(),            files_changed: 1,            additions: 5,            deletions: 2,            files: vec![],        }],        upstream_ref: Some("origin/feature".to_string()),    }} fn initialize_test_app(app: &mut warpui::App) {    app.add_singleton_model(|_| AuthStateProvider::new_for_test());    app.add_singleton_model(AppTelemetryContextProvider::new_context_provider);}#[test]fn apply_snapshot_loaded_with_diffs() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let SnapshotInputs {            metadata,            state,            diffs,        } = loaded_snapshot_with_files(vec![simple_file("src/main.rs")]);        handle.update(&mut app, |m, ctx| {            m.apply_snapshot(metadata, state, diffs, ctx)        });        handle.read(&app, |m, _| {            assert!(matches!(m.get(), DiffState::Loaded));            assert!(m.metadata.is_some());        });    });} #[test]fn get_committed_branch_files_response_emits_domain_files() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let emitted = Arc::new(Mutex::new(Vec::new()));        {            let emitted = emitted.clone();            app.update(|ctx| {                ctx.subscribe_to_model(&handle, move |_, event, _| {                    if let DiffStateModelEvent::BranchCommittedFilesReceived(files) = event {                        emitted                            .lock()                            .expect("emitted mutex should not be poisoned")                            .push(files.clone());                    }                });            });        }         // Success: proto entries are converted to domain entries and emitted.        let proto_files = vec![remote_server::proto::FileChangeEntry {            path: "src/main.rs".to_string(),            additions: 3,            deletions: 1,        }];        handle.update(&mut app, |m, ctx| {            m.handle_get_committed_branch_files_response(&Ok(proto_files), ctx);        });         // Error: an empty list is emitted so the dialog shows an empty box        // rather than stale data.        handle.update(&mut app, |m, ctx| {            m.handle_get_committed_branch_files_response(&Err("boom".to_string()), ctx);        });         let emitted = emitted            .lock()            .expect("emitted mutex should not be poisoned");        assert_eq!(emitted.len(), 2);        assert_eq!(emitted[0].len(), 1);        assert_eq!(emitted[0][0].path, "src/main.rs");        assert_eq!(emitted[0][0].additions, 3);        assert_eq!(emitted[0][0].deletions, 1);        assert!(emitted[1].is_empty());    });} #[test]fn apply_snapshot_loaded_preserves_content_at_base_in_event() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let emitted_content = Arc::new(Mutex::new(Vec::new()));        {            let emitted_content = emitted_content.clone();            app.update(|ctx| {                ctx.subscribe_to_model(&handle, move |_, event, _| {                    if let DiffStateModelEvent::NewDiffsComputed {                        diffs: Some(diffs), ..                    } = event                    {                        emitted_content                            .lock()                            .expect("emitted content mutex should not be poisoned")                            .push(                                diffs                                    .files                                    .first()                                    .and_then(|file| file.content_at_head.clone()),                            );                    }                });            });        }         let SnapshotInputs {            metadata,            state,            diffs,        } = loaded_snapshot_with_files(vec![simple_file_with_content(            "src/main.rs",            Some("base content"),        )]);        handle.update(&mut app, |m, ctx| {            m.apply_snapshot(metadata, state, diffs, ctx)        });         assert_eq!(            emitted_content                .lock()                .expect("emitted content mutex should not be poisoned")                .as_slice(),            &[Some("base content".to_string())]        );    });} #[test]fn apply_snapshot_loaded_without_diffs_becomes_error() {    warpui::App::test((), |mut app| async move {        initialize_test_app(&mut app);        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        handle.update(&mut app, |m, ctx| {            m.apply_snapshot(None, DiffState::Loaded, None, ctx)        });        assert!(matches!(            handle.read(&app, |m, _| m.get()),            DiffState::Error(_)        ));    });} #[test]fn apply_snapshot_not_in_repository() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        handle.update(&mut app, |m, ctx| {            m.apply_snapshot(None, DiffState::NotInRepository, None, ctx)        });        assert!(matches!(            handle.read(&app, |m, _| m.get()),            DiffState::NotInRepository        ));    });} #[test]fn apply_snapshot_error_stores_message() {    warpui::App::test((), |mut app| async move {        initialize_test_app(&mut app);        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        handle.update(&mut app, |m, ctx| {            m.apply_snapshot(None, DiffState::Error("git failed".to_string()), None, ctx)        });        assert!(            matches!(handle.read(&app, |m, _| m.get()), DiffState::Error(ref msg) if msg == "git failed")        );    });} #[test]fn mark_disconnected_transitions_state_and_emits_connection_lost() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let connection_lost_count = Arc::new(Mutex::new(0));        {            let connection_lost_count = connection_lost_count.clone();            app.update(|ctx| {                ctx.subscribe_to_model(&handle, move |_, event, _| {                    if matches!(event, DiffStateModelEvent::ConnectionLost) {                        *connection_lost_count                            .lock()                            .expect("connection lost count mutex should not be poisoned") += 1;                    }                });            });        }         handle.update(&mut app, |m, ctx| m.mark_disconnected(ctx));         handle.read(&app, |m, _| {            assert!(matches!(m.get(), DiffState::Disconnected));        });        assert_eq!(            *connection_lost_count                .lock()                .expect("connection lost count mutex should not be poisoned"),            1        );         // Idempotent: a second call should not re-emit ConnectionLost.        handle.update(&mut app, |m, ctx| m.mark_disconnected(ctx));        assert_eq!(            *connection_lost_count                .lock()                .expect("connection lost count mutex should not be poisoned"),            1        );    });} #[test]fn apply_metadata_first_time_sets_branch() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let meta = empty_metadata("feature");        handle.update(&mut app, |m, ctx| m.apply_metadata_update(&meta, ctx));        assert_eq!(            handle                .read(&app, |m, _| m.get_current_branch_name())                .as_deref(),            Some("feature")        );    });} #[test]fn apply_metadata_branch_change_updates_branch() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                Some(test_metadata("feature-a")),            )        });        let meta = empty_metadata("feature-b");        handle.update(&mut app, |m, ctx| m.apply_metadata_update(&meta, ctx));        assert_eq!(            handle                .read(&app, |m, _| m.get_current_branch_name())                .as_deref(),            Some("feature-b")        );    });} #[test]fn apply_file_delta_ignored_when_not_loaded() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let file_path = "src/main.rs".to_string();        let diff = Some(simple_file("src/main.rs"));        handle.update(&mut app, |m, ctx| {            m.apply_file_delta(file_path, diff, None, ctx)        });        assert!(matches!(            handle.read(&app, |m, _| m.get()),            DiffState::Loading        ));    });} #[test]fn apply_file_delta_adds_file() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loaded(GitDiffData {                    files: vec![],                    total_additions: 0,                    total_deletions: 0,                    files_changed: 0,                }),                None,            )        });        let file_path = "src/new.rs".to_string();        let diff = Some(simple_file("src/new.rs"));        handle.update(&mut app, |m, ctx| {            m.apply_file_delta(file_path, diff, None, ctx)        });        assert!(matches!(            handle.read(&app, |m, _| m.get()),            DiffState::Loaded        ));    });} #[test]fn apply_snapshot_preserves_repo_relative_file_paths() {    warpui::App::test((), |mut app| async move {        let handle = app.add_model(|_ctx| {            RemoteDiffStateModel::new_for_test(                DiffMode::Head,                InternalRemoteDiffState::Loading,                None,            )        });        let SnapshotInputs {            metadata,            state,            diffs,        } = loaded_snapshot_with_files(vec![simple_file("src/main.rs")]);        handle.update(&mut app, |m, ctx| {            m.apply_snapshot(metadata, state, diffs, ctx)        });        handle.read(&app, |m, _| {            let InternalRemoteDiffState::Loaded(diffs) = &m.state else {                panic!("state should be Loaded");            };            assert_eq!(diffs.files.len(), 1);            assert_eq!(diffs.files[0].file_path, "src/main.rs");        });    });} #[test]fn apply_snapshot_emits_event_with_repo_relative_paths() {    // Subscribers to NewDiffsComputed should see repo-relative paths so they

Lines 1–500 of 915.