Desktop: tray mini card on left click (store status at a glance next to the tray icon; Refresh runs the main window's Refresh all)
This commit is contained in:
parent
c03938f9dc
commit
60a72e6947
23 changed files with 1052 additions and 11 deletions
77
apps/desktop/src-tauri/tests/card.rs
Normal file
77
apps/desktop/src-tauri/tests/card.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
//! The tray card's placement next to the tray icon, always inside the monitor's work area.
|
||||
use storebound_desktop::card::*;
|
||||
|
||||
const W: f64 = 360.0;
|
||||
const H: f64 = 480.0;
|
||||
|
||||
fn r(x: f64, y: f64, w: f64, h: f64) -> Rect { Rect { x, y, w, h } }
|
||||
|
||||
fn inside(p: (f64, f64), work: Rect) {
|
||||
assert!(p.0 >= work.x && p.0 + W <= work.x + work.w, "x {p:?} outside {work:?}");
|
||||
assert!(p.1 >= work.y && p.1 + H <= work.y + work.h, "y {p:?} outside {work:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn windows_bottom_taskbar_opens_above_the_icon() {
|
||||
let work = r(0.0, 0.0, 1920.0, 1032.0);
|
||||
let p = place(r(1700.0, 1040.0, 24.0, 32.0), work, W, H, 8.0);
|
||||
assert_eq!(p, (1532.0, 1032.0 - H - 8.0));
|
||||
inside(p, work);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn icon_in_the_corner_is_clamped_into_the_work_area() {
|
||||
let work = r(0.0, 0.0, 1920.0, 1032.0);
|
||||
let p = place(r(1900.0, 1040.0, 24.0, 32.0), work, W, H, 8.0);
|
||||
assert_eq!(p.0, 1920.0 - W - 8.0);
|
||||
inside(p, work);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn macos_menu_bar_opens_below_the_icon() {
|
||||
let work = r(0.0, 25.0, 1440.0, 875.0);
|
||||
let p = place(r(1200.0, 0.0, 22.0, 24.0), work, W, H, 8.0);
|
||||
assert_eq!(p.1, 33.0);
|
||||
inside(p, work);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn side_taskbars_open_beside_the_icon() {
|
||||
let work = r(60.0, 0.0, 1860.0, 1080.0);
|
||||
let left = place(r(10.0, 900.0, 40.0, 40.0), work, W, H, 8.0);
|
||||
assert_eq!(left.0, 68.0);
|
||||
inside(left, work);
|
||||
let work = r(0.0, 0.0, 1860.0, 1080.0);
|
||||
let right = place(r(1870.0, 100.0, 40.0, 40.0), work, W, H, 8.0);
|
||||
assert_eq!(right.0, 1860.0 - W - 8.0);
|
||||
inside(right, work);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_monitor_with_negative_origin() {
|
||||
let work = r(-2560.0, 0.0, 2560.0, 1400.0);
|
||||
let p = place(r(-200.0, 1410.0, 24.0, 30.0), work, W, H, 8.0);
|
||||
inside(p, work);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn icon_inside_the_work_area_and_a_tiny_screen() {
|
||||
let work = r(0.0, 0.0, 1920.0, 1080.0);
|
||||
inside(place(r(900.0, 1000.0, 24.0, 24.0), work, W, H, 8.0), work);
|
||||
inside(place(r(900.0, 20.0, 24.0, 24.0), work, W, H, 8.0), work);
|
||||
// smaller than the card: top-left of the work area, no panic
|
||||
let tiny = r(0.0, 0.0, 300.0, 300.0);
|
||||
assert_eq!(place(r(100.0, 310.0, 20.0, 20.0), tiny, W, H, 8.0), (8.0, 8.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn actions_and_snapshots_are_checked() {
|
||||
for a in ["hide", "open", "route", "refresh", "mute", "quit", "external", "ready"] {
|
||||
assert!(valid_action(a));
|
||||
}
|
||||
assert!(!valid_action("sidecar_call"));
|
||||
assert!(!set_snapshot(serde_json::json!([1, 2])));
|
||||
assert!(!set_snapshot(serde_json::json!({ "x": "y".repeat(MAX_SNAPSHOT) })));
|
||||
assert!(set_snapshot(serde_json::json!({ "busy": false })));
|
||||
assert_eq!(snapshot().unwrap()["busy"], false);
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
|||
|
||||
use serde_json::{json, Value};
|
||||
use storebound_desktop::policy::*;
|
||||
use storebound_desktop::COMMANDS;
|
||||
use storebound_desktop::{CARD_COMMANDS, COMMANDS};
|
||||
use url::Url;
|
||||
|
||||
fn root() -> PathBuf {
|
||||
|
|
@ -24,9 +24,10 @@ fn u(s: &str) -> Url {
|
|||
#[test]
|
||||
fn one_capability_for_the_main_window_with_exactly_the_app_commands() {
|
||||
let dir = root().join("capabilities");
|
||||
let files: Vec<PathBuf> = fs::read_dir(&dir).unwrap().map(|e| e.unwrap().path()).collect();
|
||||
assert_eq!(files.len(), 1, "one capability file expected, found {files:?}");
|
||||
let cap = read_json(&files[0]);
|
||||
let mut files: Vec<PathBuf> = fs::read_dir(&dir).unwrap().map(|e| e.unwrap().path()).collect();
|
||||
files.sort();
|
||||
assert_eq!(files.len(), 2, "main and card capability files expected, found {files:?}");
|
||||
let cap = read_json(&dir.join("main.json"));
|
||||
assert_eq!(cap["identifier"], "main-window");
|
||||
assert_eq!(cap["windows"], json!(["main"]));
|
||||
assert!(cap.get("webviews").is_none(), "no webview-scoped grants");
|
||||
|
|
@ -46,6 +47,30 @@ fn one_capability_for_the_main_window_with_exactly_the_app_commands() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_card_window_gets_only_the_card_commands() {
|
||||
let cap = read_json(&root().join("capabilities/card.json"));
|
||||
assert_eq!(cap["identifier"], "card-window");
|
||||
assert_eq!(cap["windows"], json!(["card"]));
|
||||
assert!(cap.get("webviews").is_none() && cap.get("remote").is_none());
|
||||
assert_ne!(cap["local"], json!(false));
|
||||
let got: BTreeSet<String> = cap["permissions"].as_array().unwrap().iter().map(|p| p.as_str().unwrap().to_string()).collect();
|
||||
let mut want: BTreeSet<String> = CARD_COMMANDS.iter().map(|c| format!("allow-{}", c.replace('_', "-"))).collect();
|
||||
want.insert("core:event:allow-listen".into());
|
||||
want.insert("core:event:allow-unlisten".into());
|
||||
assert_eq!(got, want);
|
||||
let cmds = fs::read_to_string(root().join("src/commands.rs")).unwrap();
|
||||
for c in CARD_COMMANDS {
|
||||
let at = cmds.find(&format!("pub fn {c}(")).unwrap_or_else(|| panic!("{c} not in commands.rs"));
|
||||
let body = &cmds[at..];
|
||||
let open = body.find('{').unwrap();
|
||||
assert!(body[open + 1..].trim_start().starts_with("guard_card(&window)?;"), "{c} must start with guard_card(&window)?");
|
||||
}
|
||||
assert!(require_card("card", &u("tauri://localhost/card.html"), false).is_ok());
|
||||
assert!(require_card("main", &u("tauri://localhost/"), false).is_err());
|
||||
assert!(require_main("card", &u("tauri://localhost/card.html"), false).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_registered_command_is_in_the_list_and_nothing_else() {
|
||||
let lib = fs::read_to_string(root().join("src/lib.rs")).unwrap();
|
||||
|
|
@ -56,7 +81,7 @@ fn every_registered_command_is_in_the_list_and_nothing_else() {
|
|||
.map(|s| s.trim().trim_start_matches("commands::").to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect();
|
||||
assert_eq!(registered, COMMANDS.iter().map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
assert_eq!(registered, COMMANDS.iter().chain(CARD_COMMANDS).map(|s| s.to_string()).collect::<Vec<_>>());
|
||||
// every command checks the caller first
|
||||
let cmds = fs::read_to_string(root().join("src/commands.rs")).unwrap();
|
||||
for c in COMMANDS {
|
||||
|
|
@ -84,7 +109,7 @@ fn csp_is_strict_and_the_config_has_no_backdoors() {
|
|||
assert!(!v.contains("unsafe-eval") && !v.contains("unsafe-inline") && !v.contains('*'), "{k}: {v}");
|
||||
assert!(!v.contains("https:") && !v.contains("http://") || v == "ipc: http://ipc.localhost", "{k}: no remote sources: {v}");
|
||||
}
|
||||
assert_eq!(conf["app"]["security"]["capabilities"], json!(["main-window"]));
|
||||
assert_eq!(conf["app"]["security"]["capabilities"], json!(["main-window", "card-window"]));
|
||||
assert_eq!(conf["app"]["security"]["freezePrototype"], json!(true));
|
||||
assert!(conf["app"].get("withGlobalTauri").is_none() || conf["app"]["withGlobalTauri"] == json!(false));
|
||||
let windows = conf["app"]["windows"].as_array().unwrap();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue