mirror of
https://github.com/storytold/craft-launcher.git
synced 2026-11-09 20:04:34 +00:00
test(ui): cover the dialog, the note's tooltip, Reinstall and stacked rows strictly
- The dialog's Install is disabled with a reason for a refused version. - The out-of-reach tooltip in card and table; no note for a repository copy; the note for a release without a download here. - Clicking Reinstall installs; clicking a greyed-out Install version X sends nothing. - German rows: a single main button must sit on the menu's line, a stacked pair must be centred on it and stay out of the next row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0fba50e46b
commit
7c09fb79f4
2 files changed
+102
-23
No files matched your search
@@ -194,18 +194,9 @@ fn german_switches_persists_and_keeps_install_actions() {
|
||||
let slot = retry.query_all_by_label("Installieren").next().unwrap().rect().width();
|
||||
for app in ["PhotoCraft", "VectorCraft"] {
|
||||
let menu = retry.get_by_label(&format!("Weitere Aktionen für {app}")).rect();
|
||||
// The main button is the one ending just left of the menu (or overlapping it); a stacked
|
||||
// Open and Update sit half a button above and below the menu's middle.
|
||||
let main = retry
|
||||
.query_all_by_role(egui::accesskit::Role::Button)
|
||||
.filter(|n| {
|
||||
(n.rect().center().y - menu.center().y).abs() < STACKED_OFFSET
|
||||
&& n.rect().left() < menu.left()
|
||||
&& n.rect().right() > menu.left() - 20.0
|
||||
})
|
||||
.find(|n| n.rect().height() < 40.0)
|
||||
.unwrap();
|
||||
assert!(main.rect().width() <= slot + 0.5, "{app}: {:?} wider than Installieren ({slot})", main.rect());
|
||||
let main = main_button_in_line(&retry, menu, app);
|
||||
assert!(main.right() > menu.left() - 20.0, "{app}: {main:?} isn't beside the menu {menu:?}");
|
||||
assert!(main.width() <= slot + 0.5, "{app}: {main:?} wider than Installieren ({slot})");
|
||||
}
|
||||
let (mut web, _) = harness_language(ViewMode::Grid, egui::Theme::Dark, false, &restored.language, Vec::new());
|
||||
web.run_steps(6);
|
||||
@@ -244,6 +235,14 @@ fn a_table_update_too_narrow_to_sit_side_by_side_stacks() {
|
||||
// Up-to-date LightCraft keeps a one-line row.
|
||||
let light = h.get_by_label("Weitere Aktionen für LightCraft").rect();
|
||||
assert!(update.bottom() - open.top() > light.height() * 1.5, "PhotoCraft's row grew for two buttons");
|
||||
// The pair stays inside its own row: the next row's controls start below it.
|
||||
let next = h
|
||||
.query_all_by_label_contains("Weitere Aktionen für")
|
||||
.map(|n| n.rect())
|
||||
.filter(|r| r.top() > menu.top())
|
||||
.min_by(|a, b| a.top().total_cmp(&b.top()))
|
||||
.expect("a row below PhotoCraft's");
|
||||
assert!(update.bottom() < next.top(), "Update {update:?} runs into the next row {next:?}");
|
||||
|
||||
sent.borrow_mut().clear();
|
||||
h.get_by_label(keep).click();
|
||||
@@ -257,6 +256,24 @@ fn a_table_update_too_narrow_to_sit_side_by_side_stacks() {
|
||||
/// half a button (and half the seam) above and below it. Rows are further apart than this.
|
||||
const STACKED_OFFSET: f32 = 20.0;
|
||||
|
||||
/// The main button of the row whose `⋯` menu is `menu` (the one ending nearest the menu), after
|
||||
/// checking it is in line with the menu: centred on it, or, stacked over its twin (the button of
|
||||
/// the same width ending at the same place), the pair centred on it. Only a stacked pair may sit
|
||||
/// off the menu's middle.
|
||||
fn main_button_in_line(h: &Harness<'static, Option<LauncherUi>>, menu: egui::Rect, what: &str) -> egui::Rect {
|
||||
let near: Vec<egui::Rect> = h
|
||||
.query_all_by_role(egui::accesskit::Role::Button)
|
||||
.map(|n| n.rect())
|
||||
.filter(|r| (r.center().y - menu.center().y).abs() < STACKED_OFFSET && r.left() < menu.left() && r.height() < 40.0)
|
||||
.collect();
|
||||
let main = *near.iter().max_by(|a, b| a.right().total_cmp(&b.right())).unwrap_or_else(|| panic!("{what}: no main button by {menu:?}"));
|
||||
let column: Vec<&egui::Rect> = near.iter().filter(|r| (r.right() - main.right()).abs() < 0.5 && (r.left() - main.left()).abs() < 0.5).collect();
|
||||
let (top, bottom) = column.iter().fold((f32::INFINITY, f32::NEG_INFINITY), |(t, b), r| (t.min(r.top()), b.max(r.bottom())));
|
||||
let middle = (top + bottom) / 2.0;
|
||||
assert!((middle - menu.center().y).abs() < 1.0, "{what}: {column:?} not in line with the menu {menu:?}");
|
||||
main
|
||||
}
|
||||
|
||||
/// At the launcher's narrowest, no main button outgrows its slot or crowds out the `⋯` menu (#956):
|
||||
/// a kept update, a kept install and a plain update, in both views, whichever label `language` has.
|
||||
fn main_buttons_fit_the_narrowest_window(language: &str) {
|
||||
@@ -269,12 +286,7 @@ fn main_buttons_fit_the_narrowest_window(language: &str) {
|
||||
let slot = h.query_all_by_label(t("Install")).next().unwrap().rect().width();
|
||||
for app in ["PhotoCraft", "VectorCraft", "EffectCraft"] {
|
||||
let menu = h.get_by_label(&fmt(t("More actions for {name}"), &[("name", app)])).rect();
|
||||
let main = h
|
||||
.query_all_by_role(egui::accesskit::Role::Button)
|
||||
.filter(|n| (n.rect().center().y - menu.center().y).abs() < STACKED_OFFSET && n.rect().left() < menu.left())
|
||||
.max_by(|a, b| a.rect().right().total_cmp(&b.rect().right()))
|
||||
.unwrap()
|
||||
.rect();
|
||||
let main = main_button_in_line(&h, menu, &format!("{language} {view:?} {app}"));
|
||||
assert!(main.width() <= slot + 0.5, "{language} {view:?} {app}: {main:?} wider than Install ({slot})");
|
||||
assert!(main.right() <= menu.left(), "{language} {view:?} {app}: {main:?} overlaps the menu {menu:?}");
|
||||
// The table scrolls sideways, with a fixed slot; cards have to fit the window.
|
||||
|
||||
@@ -571,6 +571,31 @@ fn choosing_an_app_version_turns_its_main_action_into_install() {
|
||||
assert_eq!(h.query_all_by_label("Open").count(), opens + 1, "PhotoCraft shows Open");
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The version dialog's Install follows the same answer as the card's "Install version X": for a
|
||||
/// copy the backend can't replace with the chosen version (installed outside the launcher, say),
|
||||
/// it is greyed out, says why on hover, and clicking it sends no install.
|
||||
fn the_version_dialog_wont_install_a_version_the_install_would_refuse() {
|
||||
let pin = signed_photocraft("0.4.0");
|
||||
let (mut h, sent, queue) = version_menu(pin.clone(), vec![signed_photocraft("0.5.0"), pin]);
|
||||
queue.borrow_mut().push(Event::Installable { app: "photocraft".into(), installable: false });
|
||||
h.query_all_by_role_and_label(egui::accesskit::Role::Button, "v0.4.0").next().unwrap().click();
|
||||
h.run_steps(8);
|
||||
sent.borrow_mut().clear();
|
||||
let row = h.get_by_label("Close").rect().center().y;
|
||||
let install = h
|
||||
.get_all_by_label("Install version v0.4.0")
|
||||
.find(|n| (n.rect().center().y - row).abs() < 4.0)
|
||||
.expect("the dialog shows Install for the chosen version");
|
||||
assert!(install.accesskit_node().is_disabled(), "the install would be refused");
|
||||
install.hover();
|
||||
h.run_steps(4);
|
||||
assert!(h.query_all_by_label_contains("can't install version v0.4.0 over this copy").next().is_some(), "it says why");
|
||||
h.get_all_by_label("Install version v0.4.0").find(|n| (n.rect().center().y - row).abs() < 4.0).unwrap().click();
|
||||
h.run_steps(4);
|
||||
assert!(!sent.borrow().iter().any(|c| matches!(c, Command::Install { .. })), "{:?}", sent.borrow());
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// A custom-folder install opens a confirmation dialog with no command until Install; the
|
||||
/// chosen root and per-app folder are persisted, Configure precedes Install, and Cancel keeps
|
||||
@@ -1128,6 +1153,7 @@ fn a_newer_release_without_a_download_here_is_not_offered_as_an_update() {
|
||||
assert!(h.query_by_label(KEEP_PHOTOCRAFT).is_none(), "no split without an update");
|
||||
h.get_by_label("Update to 0.6.0");
|
||||
h.get_by_label("Update 1 app");
|
||||
h.get_by_label_contains("0.4.1 · latest 0.5.0");
|
||||
// PhotoCraft's main button is a plain Open, which launches it.
|
||||
// Cards share a row, so PhotoCraft's Open is the one right before its own menu.
|
||||
let menu = h.get_by_label("More actions for PhotoCraft").rect();
|
||||
@@ -1152,10 +1178,15 @@ fn the_backends_installability_answer_decides_an_update() {
|
||||
assert!(h.query_by_label("Update to 0.5.0").is_none(), "the backend can't install it");
|
||||
assert!(h.query_by_label(KEEP_PHOTOCRAFT).is_none());
|
||||
h.get_by_label("Update 1 app");
|
||||
// Still a sign the release is out: a quiet note beside the installed version.
|
||||
h.get_by_label_contains("0.4.1 · latest 0.5.0");
|
||||
// Still a sign the release is out: a quiet note beside the installed version, saying why.
|
||||
h.get_by_label_contains("0.4.1 · latest 0.5.0").hover();
|
||||
h.run_steps(4);
|
||||
h.get_by_label_contains(OUT_OF_REACH_TIP);
|
||||
}
|
||||
|
||||
/// The tooltip on the note beside a copy behind a release it can't take.
|
||||
const OUT_OF_REACH_TIP: &str = "0.5.0 is out, but ArtCraft Launcher can't install it over this copy.";
|
||||
|
||||
#[test]
|
||||
/// The note beside a copy behind a release it can't take shows in the table too.
|
||||
fn the_table_notes_a_newer_release_that_cant_install_here() {
|
||||
@@ -1163,7 +1194,33 @@ fn the_table_notes_a_newer_release_that_cant_install_here() {
|
||||
ev.push(Event::Installable { app: "photocraft".into(), installable: false });
|
||||
let (mut h, _) = harness_with(ev, ViewMode::Table, egui::Theme::Dark, true, 1.0);
|
||||
h.run_steps(8);
|
||||
h.get_by_label("0.4.1 · latest 0.5.0");
|
||||
h.get_by_label("0.4.1 · latest 0.5.0").hover();
|
||||
h.run_steps(4);
|
||||
h.get_by_label_contains(OUT_OF_REACH_TIP);
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// A copy its repository updates (a Flatpak from FlatPark) knows nothing of GitHub's latest, so a
|
||||
/// newer release there gets no "can't install" note: its repository may well have it.
|
||||
fn a_repository_copy_gets_no_out_of_reach_note() {
|
||||
let mut ev = events();
|
||||
ev.push(Event::Detected {
|
||||
app: "lightcraft".into(),
|
||||
installed: Some(Installed {
|
||||
version: version::parse("0.3.0"),
|
||||
source: InstallSource::Flatpak,
|
||||
location: "ai.storyteller.lightcraft".into(),
|
||||
package: None,
|
||||
externally_managed: true,
|
||||
origin: Some("flatpark".into()),
|
||||
installation: Some("user".into()),
|
||||
}),
|
||||
});
|
||||
ev.push(Event::Installable { app: "lightcraft".into(), installable: false });
|
||||
let (mut h, _) = harness_with(ev, ViewMode::Table, egui::Theme::Dark, true, 1.0);
|
||||
h.run_steps(8);
|
||||
assert!(h.query_all_by_label("0.3.0").next().is_some(), "LightCraft's version shows");
|
||||
assert!(h.query_by_label_contains("0.3.0 · latest").is_none(), "no note for a repository copy");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1173,7 +1230,7 @@ fn the_table_notes_a_newer_release_that_cant_install_here() {
|
||||
fn a_kept_version_gets_no_out_of_reach_note_and_says_why_it_cant_install() {
|
||||
let mut ev = events();
|
||||
ev.push(Event::Installable { app: "photocraft".into(), installable: false });
|
||||
let (mut h, _) = harness_with(ev, ViewMode::Grid, egui::Theme::Dark, true, 1.0);
|
||||
let (mut h, sent) = harness_with(ev, ViewMode::Grid, egui::Theme::Dark, true, 1.0);
|
||||
h.run_steps(8);
|
||||
h.get_by_label_contains("0.4.1 · latest 0.5.0");
|
||||
h.state_mut()
|
||||
@@ -1189,6 +1246,10 @@ fn a_kept_version_gets_no_out_of_reach_note_and_says_why_it_cant_install() {
|
||||
install.hover();
|
||||
h.run_steps(4);
|
||||
h.get_by_label_contains("can't install version v0.5.0 over this copy");
|
||||
sent.borrow_mut().clear();
|
||||
h.get_by_label("Install version v0.5.0").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(launches_and_installs(&sent, "photocraft"), (0, 0), "a greyed-out Install sends nothing: {:?}", sent.borrow());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1223,11 +1284,17 @@ fn reinstall_needs_an_install_that_would_go_ahead() {
|
||||
for installable in [true, false] {
|
||||
let mut ev = events();
|
||||
ev.push(Event::Installable { app: "lightcraft".into(), installable });
|
||||
let (mut h, _) = harness_with(ev, ViewMode::Grid, egui::Theme::Dark, true, 1.0);
|
||||
let (mut h, sent) = harness_with(ev, ViewMode::Grid, egui::Theme::Dark, true, 1.0);
|
||||
h.run_steps(8);
|
||||
h.get_by_label("More actions for LightCraft").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.query_by_label("Reinstall").is_some(), installable, "installable: {installable}");
|
||||
if installable {
|
||||
sent.borrow_mut().clear();
|
||||
h.get_by_label("Reinstall").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(launches_and_installs(&sent, "lightcraft"), (0, 1), "Reinstall installs: {:?}", sent.borrow());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user