use warpui::integration::{AssertionCallback, AssertionOutcome, TestStep};use warpui::{App, ViewHandle, WindowId, async_assert}; use crate::code_review::code_review_view::{CodeReviewView, CodeReviewVisibleAnchorForTest}; /// Expected scroll region type for assertions.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum ScrollRegion { Header, CurrentLine, RemovedLine, Footer,} fn try_single_code_review_view( app: &App, window_id: WindowId,) -> Option<ViewHandle<CodeReviewView>> { let views = app.views_of_type::<CodeReviewView>(window_id)?; if views.len() == 1 { Some(views[0].clone()) } else { None }} fn single_code_review_view(app: &App, window_id: WindowId) -> ViewHandle<CodeReviewView> { try_single_code_review_view(app, window_id) .expect("expected exactly one code review view in the window")} pub fn assert_code_review_loaded() -> AssertionCallback { Box::new(|app, window_id| { let Some(code_review_view) = try_single_code_review_view(app, window_id) else { return AssertionOutcome::failure( "code review view not yet available in the window".to_string(), ); }; code_review_view.read(app, |code_review_view, _| { async_assert!( code_review_view.has_file_states() && code_review_view.all_editors_loaded_for_test(), "expected code review to have loaded file states and editor buffers" ) }) })} pub fn assert_code_review_anchor( expected_file_path: impl Into<String>, expected_text: impl Into<String>, expected_line_number: Option<usize>,) -> AssertionCallback { let expected_file_path = expected_file_path.into(); let expected_text = expected_text.into(); Box::new(move |app, window_id| { let Some(code_review_view) = try_single_code_review_view(app, window_id) else { return AssertionOutcome::failure( "code review view not yet available in the window".to_string(), ); }; code_review_view.read(app, |code_review_view, ctx| { let Some(anchor) = code_review_view.visible_anchor_for_test(ctx) else { return AssertionOutcome::failure( "expected a visible code review anchor but none was available".to_string(), ); }; assert_anchor( &anchor, &expected_file_path, &expected_text, expected_line_number, ) }) })} pub fn scroll_code_review_to_line(file_path: impl Into<String>, line_number: usize) -> TestStep { let file_path = file_path.into(); TestStep::new("Scroll code review to a file line").with_action(move |app, window_id, _| { let code_review_view = single_code_review_view(app, window_id); code_review_view.update(app, |code_review_view, ctx| { let _ = code_review_view.scroll_to_line_for_test(&file_path, line_number, ctx); }); })} pub fn assert_code_review_line_text( expected_file_path: impl Into<String>, line_number: usize, expected_text: impl Into<String>,) -> AssertionCallback { let expected_file_path = expected_file_path.into(); let expected_text = expected_text.into(); Box::new(move |app, window_id| { let Some(code_review_view) = try_single_code_review_view(app, window_id) else { return AssertionOutcome::failure( "code review view not yet available in the window".to_string(), ); }; code_review_view.read(app, |code_review_view, ctx| { let Some(line_text) = code_review_view.line_text_for_test(&expected_file_path, line_number, ctx) else { return AssertionOutcome::failure(format!( "expected code review line {line_number} for {expected_file_path:?} to be available", )); }; if line_text != expected_text { return AssertionOutcome::failure(format!( "expected line {line_number} in {expected_file_path:?} to be {expected_text:?}, got {line_text:?}", )); } AssertionOutcome::Success }) })} fn assert_anchor( anchor: &CodeReviewVisibleAnchorForTest, expected_file_path: &str, expected_text: &str, expected_line_number: Option<usize>,) -> AssertionOutcome { if anchor.file_path != expected_file_path { return AssertionOutcome::failure(format!( "expected anchor file to be {expected_file_path:?}, got {:?}", anchor.file_path )); } if anchor.line_text != expected_text { return AssertionOutcome::failure(format!( "expected anchor text to be {expected_text:?}, got {:?}", anchor.line_text )); } if let Some(expected_line_number) = expected_line_number && anchor.line_number != expected_line_number { return AssertionOutcome::failure(format!( "expected anchor line number to be {expected_line_number}, got {}", anchor.line_number )); } AssertionOutcome::Success} pub fn scroll_code_review_to_header(file_path: impl Into<String>) -> TestStep { let file_path = file_path.into(); TestStep::new("Scroll code review to header region").with_action(move |app, window_id, _| { let code_review_view = single_code_review_view(app, window_id); code_review_view.update(app, |code_review_view, ctx| { code_review_view.scroll_to_header_for_test(&file_path, ctx); }); })} pub fn scroll_code_review_to_footer(file_path: impl Into<String>) -> TestStep { let file_path = file_path.into(); TestStep::new("Scroll code review to footer region").with_action(move |app, window_id, _| { let code_review_view = single_code_review_view(app, window_id); code_review_view.update(app, |code_review_view, ctx| { code_review_view.scroll_to_footer_for_test(&file_path, ctx); }); })} pub fn scroll_code_review_to_deleted_range( file_path: impl Into<String>, near_line: usize,) -> TestStep { let file_path = file_path.into(); TestStep::new("Scroll code review to deleted range").with_action(move |app, window_id, _| { let code_review_view = single_code_review_view(app, window_id); code_review_view.update(app, |code_review_view, ctx| { code_review_view.scroll_to_deleted_range_for_test(&file_path, near_line, ctx); }); })} pub fn assert_code_review_scroll_region(expected_region: ScrollRegion) -> AssertionCallback { let expected_str = match expected_region { ScrollRegion::Header => "header", ScrollRegion::CurrentLine => "current_line", ScrollRegion::RemovedLine => "removed_line", ScrollRegion::Footer => "footer", }; Box::new(move |app, window_id| { let Some(code_review_view) = try_single_code_review_view(app, window_id) else { return AssertionOutcome::failure( "code review view not yet available in the window".to_string(), ); }; code_review_view.read(app, |code_review_view, ctx| { let actual = code_review_view.scroll_region_for_test(ctx); if actual != expected_str { return AssertionOutcome::failure(format!( "expected scroll region to be {expected_str:?}, got {actual:?}" )); } AssertionOutcome::Success }) })} /// Polls until the given file's diff editor has at least `min` collapsed hidden/// sections. Use this to wait for the diff to lay out before interacting.pub fn assert_min_hidden_sections( expected_file_path: impl Into<String>, min: usize,) -> AssertionCallback { let expected_file_path = expected_file_path.into(); Box::new(move |app, window_id| { let Some(code_review_view) = try_single_code_review_view(app, window_id) else { return AssertionOutcome::failure( "code review view not yet available in the window".to_string(), ); }; code_review_view.read(app, |code_review_view, ctx| { let Some(actual) = code_review_view.hidden_section_count_for_test(&expected_file_path, ctx) else { return AssertionOutcome::failure(format!( "expected hidden-section count for {expected_file_path:?} to be available" )); }; async_assert!( actual >= min, "expected at least {min} hidden sections in {expected_file_path:?}, got {actual}" ) }) })} /// Fully expand the first hidden section (the action a bar double-click/// performs) and assert it removed exactly one hidden section. A chunked/// reveal would only shrink a range, leaving the count unchanged, so the/// decrement is what distinguishes a full expansion.pub fn expand_first_hidden_section_and_assert_full_reveal( file_path: impl Into<String>,) -> TestStep { let file_path = file_path.into(); TestStep::new("Fully expand the first hidden section").with_action(move |app, window_id, _| { let code_review_view = single_code_review_view(app, window_id); let before = code_review_view .read(app, |code_review_view, ctx| { code_review_view.hidden_section_count_for_test(&file_path, ctx) }) .expect("hidden-section count should be available before expanding"); assert!( before > 0, "expected at least one hidden section before expanding {file_path:?}, got {before}" ); let expanded = code_review_view.update(app, |code_review_view, ctx| { code_review_view.fully_expand_first_hidden_section_for_test(&file_path, ctx) }); assert!( expanded, "expected a hidden section to expand for {file_path:?}" ); let after = code_review_view .read(app, |code_review_view, ctx| { code_review_view.hidden_section_count_for_test(&file_path, ctx) }) .expect("hidden-section count should be available after expanding"); assert_eq!( after, before - 1, "expected a full expansion to remove exactly one hidden section in {file_path:?} \ (a chunked reveal would leave the count unchanged)" ); })}
use warpui::integration::{AssertionCallback, AssertionOutcome, TestStep};
use warpui::{App, ViewHandle, WindowId, async_assert};
use crate::code_review::code_review_view::{CodeReviewView, CodeReviewVisibleAnchorForTest};
/// Expected scroll region type for assertions.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollRegion {
Header,
CurrentLine,
RemovedLine,
Footer,
}
fn try_single_code_review_view(
app: &App,
window_id: WindowId,
) -> Option<ViewHandle<CodeReviewView>> {
let views = app.views_of_type::<CodeReviewView>(window_id)?;
if views.len() == 1 {
Some(views[0].clone())
} else {
None
}
}
fn single_code_review_view(app: &App, window_id: WindowId) -> ViewHandle<CodeReviewView> {
try_single_code_review_view(app, window_id)
.expect("expected exactly one code review view in the window")
}
pub fn assert_code_review_loaded() -> AssertionCallback {
Box::new(|app, window_id| {
let Some(code_review_view) = try_single_code_review_view(app, window_id) else {
return AssertionOutcome::failure(
"code review view not yet available in the window".to_string(),
);
};
code_review_view.read(app, |code_review_view, _| {
async_assert!(
code_review_view.has_file_states()
&& code_review_view.all_editors_loaded_for_test(),
"expected code review to have loaded file states and editor buffers"
)
})
})
}
pub fn assert_code_review_anchor(
expected_file_path: impl Into<String>,
expected_text: impl Into<String>,
expected_line_number: Option<usize>,
) -> AssertionCallback {
let expected_file_path = expected_file_path.into();
let expected_text = expected_text.into();
Box::new(move |app, window_id| {
let Some(code_review_view) = try_single_code_review_view(app, window_id) else {
return AssertionOutcome::failure(
"code review view not yet available in the window".to_string(),
);
};
code_review_view.read(app, |code_review_view, ctx| {
let Some(anchor) = code_review_view.visible_anchor_for_test(ctx) else {
return AssertionOutcome::failure(
"expected a visible code review anchor but none was available".to_string(),
);
};
assert_anchor(
&anchor,
&expected_file_path,
&expected_text,
expected_line_number,
)
})
})
}
pub fn scroll_code_review_to_line(file_path: impl Into<String>, line_number: usize) -> TestStep {
let file_path = file_path.into();
TestStep::new("Scroll code review to a file line").with_action(move |app, window_id, _| {
let code_review_view = single_code_review_view(app, window_id);
code_review_view.update(app, |code_review_view, ctx| {
let _ = code_review_view.scroll_to_line_for_test(&file_path, line_number, ctx);
});
})
}
pub fn assert_code_review_line_text(
expected_file_path: impl Into<String>,
line_number: usize,
expected_text: impl Into<String>,
) -> AssertionCallback {
let expected_file_path = expected_file_path.into();
let expected_text = expected_text.into();
Box::new(move |app, window_id| {
let Some(code_review_view) = try_single_code_review_view(app, window_id) else {
return AssertionOutcome::failure(
"code review view not yet available in the window".to_string(),
);
};
code_review_view.read(app, |code_review_view, ctx| {
let Some(line_text) =
code_review_view.line_text_for_test(&expected_file_path, line_number, ctx)
else {
return AssertionOutcome::failure(format!(
"expected code review line {line_number} for {expected_file_path:?} to be available",
));
};
if line_text != expected_text {
return AssertionOutcome::failure(format!(
"expected line {line_number} in {expected_file_path:?} to be {expected_text:?}, got {line_text:?}",
));
}
AssertionOutcome::Success
})
})
}
fn assert_anchor(
anchor: &CodeReviewVisibleAnchorForTest,
expected_file_path: &str,
expected_text: &str,
expected_line_number: Option<usize>,
) -> AssertionOutcome {
if anchor.file_path != expected_file_path {
return AssertionOutcome::failure(format!(
"expected anchor file to be {expected_file_path:?}, got {:?}",
anchor.file_path
));
}
if anchor.line_text != expected_text {
return AssertionOutcome::failure(format!(
"expected anchor text to be {expected_text:?}, got {:?}",
anchor.line_text
));
}
if let Some(expected_line_number) = expected_line_number
&& anchor.line_number != expected_line_number
{
return AssertionOutcome::failure(format!(
"expected anchor line number to be {expected_line_number}, got {}",
anchor.line_number
));
}
AssertionOutcome::Success
}
pub fn scroll_code_review_to_header(file_path: impl Into<String>) -> TestStep {
let file_path = file_path.into();
TestStep::new("Scroll code review to header region").with_action(move |app, window_id, _| {
let code_review_view = single_code_review_view(app, window_id);
code_review_view.update(app, |code_review_view, ctx| {
code_review_view.scroll_to_header_for_test(&file_path, ctx);
});
})
}
pub fn scroll_code_review_to_footer(file_path: impl Into<String>) -> TestStep {
let file_path = file_path.into();
TestStep::new("Scroll code review to footer region").with_action(move |app, window_id, _| {
let code_review_view = single_code_review_view(app, window_id);
code_review_view.update(app, |code_review_view, ctx| {
code_review_view.scroll_to_footer_for_test(&file_path, ctx);
});
})
}
pub fn scroll_code_review_to_deleted_range(
file_path: impl Into<String>,
near_line: usize,
) -> TestStep {
let file_path = file_path.into();
TestStep::new("Scroll code review to deleted range").with_action(move |app, window_id, _| {
let code_review_view = single_code_review_view(app, window_id);
code_review_view.update(app, |code_review_view, ctx| {
code_review_view.scroll_to_deleted_range_for_test(&file_path, near_line, ctx);
});
})
}
pub fn assert_code_review_scroll_region(expected_region: ScrollRegion) -> AssertionCallback {
let expected_str = match expected_region {
ScrollRegion::Header => "header",
ScrollRegion::CurrentLine => "current_line",
ScrollRegion::RemovedLine => "removed_line",
ScrollRegion::Footer => "footer",
};
Box::new(move |app, window_id| {
let Some(code_review_view) = try_single_code_review_view(app, window_id) else {
return AssertionOutcome::failure(
"code review view not yet available in the window".to_string(),
);
};
code_review_view.read(app, |code_review_view, ctx| {
let actual = code_review_view.scroll_region_for_test(ctx);
if actual != expected_str {
return AssertionOutcome::failure(format!(
"expected scroll region to be {expected_str:?}, got {actual:?}"
));
}
AssertionOutcome::Success
})
})
}
/// Polls until the given file's diff editor has at least `min` collapsed hidden
/// sections. Use this to wait for the diff to lay out before interacting.
pub fn assert_min_hidden_sections(
expected_file_path: impl Into<String>,
min: usize,
) -> AssertionCallback {
let expected_file_path = expected_file_path.into();
Box::new(move |app, window_id| {
let Some(code_review_view) = try_single_code_review_view(app, window_id) else {
return AssertionOutcome::failure(
"code review view not yet available in the window".to_string(),
);
};
code_review_view.read(app, |code_review_view, ctx| {
let Some(actual) =
code_review_view.hidden_section_count_for_test(&expected_file_path, ctx)
else {
return AssertionOutcome::failure(format!(
"expected hidden-section count for {expected_file_path:?} to be available"
));
};
async_assert!(
actual >= min,
"expected at least {min} hidden sections in {expected_file_path:?}, got {actual}"
)
})
})
}
/// Fully expand the first hidden section (the action a bar double-click
/// performs) and assert it removed exactly one hidden section. A chunked
/// reveal would only shrink a range, leaving the count unchanged, so the
/// decrement is what distinguishes a full expansion.
pub fn expand_first_hidden_section_and_assert_full_reveal(
file_path: impl Into<String>,
) -> TestStep {
let file_path = file_path.into();
TestStep::new("Fully expand the first hidden section").with_action(move |app, window_id, _| {
let code_review_view = single_code_review_view(app, window_id);
let before = code_review_view
.read(app, |code_review_view, ctx| {
code_review_view.hidden_section_count_for_test(&file_path, ctx)
})
.expect("hidden-section count should be available before expanding");
assert!(
before > 0,
"expected at least one hidden section before expanding {file_path:?}, got {before}"
);
let expanded = code_review_view.update(app, |code_review_view, ctx| {
code_review_view.fully_expand_first_hidden_section_for_test(&file_path, ctx)
});
assert!(
expanded,
"expected a hidden section to expand for {file_path:?}"
);
let after = code_review_view
.read(app, |code_review_view, ctx| {
code_review_view.hidden_section_count_for_test(&file_path, ctx)
})
.expect("hidden-section count should be available after expanding");
assert_eq!(
after,
before - 1,
"expected a full expansion to remove exactly one hidden section in {file_path:?} \
(a chunked reveal would leave the count unchanged)"
);
})
}