1 parent
12e0c99850
commit
edda5fc9e4
42 files changed
+733
-122
No files matched your search
@@ -414,12 +414,6 @@ pub fn update_preferences(
|
||||
if let Some(v) = data.row_height {
|
||||
range("row_height", v, 42, 88)?;
|
||||
}
|
||||
if let Some(v) = data.folder_pane_width {
|
||||
range("folder_pane_width", v, 180, 360)?;
|
||||
}
|
||||
if let Some(v) = data.message_pane_width {
|
||||
range("message_pane_width", v, 300, 720)?;
|
||||
}
|
||||
if let Some(v) = data.border_radius {
|
||||
range("border_radius", v, 0, 12)?;
|
||||
}
|
||||
@@ -505,8 +499,16 @@ pub fn update_preferences(
|
||||
set_text!(font_family, "font_family");
|
||||
set_int!(font_size, "font_size");
|
||||
set_int!(row_height, "row_height");
|
||||
set_int!(folder_pane_width, "folder_pane_width");
|
||||
set_int!(message_pane_width, "message_pane_width");
|
||||
if let Some(value) = data.folder_pane_width {
|
||||
sets.push(format!("folder_pane_width = ?{idx}"));
|
||||
values.push(value.clamp(180, 420).to_db_value());
|
||||
idx += 1;
|
||||
}
|
||||
if let Some(value) = data.message_pane_width {
|
||||
sets.push(format!("message_pane_width = ?{idx}"));
|
||||
values.push(value.clamp(300, 760).to_db_value());
|
||||
idx += 1;
|
||||
}
|
||||
set_int!(border_radius, "border_radius");
|
||||
set_text!(contrast, "contrast");
|
||||
set_bool!(show_avatars, "show_avatars");
|
||||
@@ -578,6 +580,28 @@ mod tests {
|
||||
assert!(prefs.preset_scale_unlocked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pane_widths_are_clamped_instead_of_rejected() {
|
||||
let conn = open_test_db();
|
||||
let update: UpdateDisplayPreferences = serde_json::from_value(serde_json::json!({
|
||||
"folder_pane_width": 9999,
|
||||
"message_pane_width": 9999
|
||||
}))
|
||||
.unwrap();
|
||||
let prefs = update_preferences(&conn, &update).unwrap();
|
||||
assert_eq!(prefs.folder_pane_width, 420);
|
||||
assert_eq!(prefs.message_pane_width, 760);
|
||||
|
||||
let update: UpdateDisplayPreferences = serde_json::from_value(serde_json::json!({
|
||||
"folder_pane_width": -1,
|
||||
"message_pane_width": -1
|
||||
}))
|
||||
.unwrap();
|
||||
let prefs = update_preferences(&conn, &update).unwrap();
|
||||
assert_eq!(prefs.folder_pane_width, 180);
|
||||
assert_eq!(prefs.message_pane_width, 300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn built_in_interface_skins_are_accepted() {
|
||||
for skin in [
|
||||
|
||||
@@ -116,6 +116,7 @@ mod tests {
|
||||
default_sort: None,
|
||||
flagged_on_top: Some(true),
|
||||
sync_imap_keywords: None,
|
||||
compact_wide_rows: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
+37
-4
@@ -17,6 +17,7 @@ pub struct Identity {
|
||||
pub signature_reply: bool,
|
||||
pub signature_forward: bool,
|
||||
pub signature_position: String,
|
||||
pub signature_insert_mode: String,
|
||||
pub pgp_fingerprint: Option<String>,
|
||||
pub pgp_sign_default: bool,
|
||||
pub pgp_encrypt_default: bool,
|
||||
@@ -50,6 +51,8 @@ pub struct CreateIdentity {
|
||||
pub signature_forward: bool,
|
||||
#[serde(default = "default_signature_position")]
|
||||
pub signature_position: String,
|
||||
#[serde(default = "default_signature_insert_mode")]
|
||||
pub signature_insert_mode: String,
|
||||
#[serde(default)]
|
||||
pub pgp_fingerprint: Option<String>,
|
||||
#[serde(default)]
|
||||
@@ -74,6 +77,7 @@ pub struct UpdateIdentity {
|
||||
pub signature_reply: Option<bool>,
|
||||
pub signature_forward: Option<bool>,
|
||||
pub signature_position: Option<String>,
|
||||
pub signature_insert_mode: Option<String>,
|
||||
pub pgp_fingerprint: Option<Option<String>>,
|
||||
pub pgp_sign_default: Option<bool>,
|
||||
pub pgp_encrypt_default: Option<bool>,
|
||||
@@ -87,6 +91,18 @@ fn default_signature_position() -> String {
|
||||
"above".to_string()
|
||||
}
|
||||
|
||||
fn default_signature_insert_mode() -> String {
|
||||
"silent".to_string()
|
||||
}
|
||||
|
||||
fn validate_signature_insert_mode(value: &str) -> Result<(), String> {
|
||||
if matches!(value, "silent" | "visible") {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Invalid signature_insert_mode: {value}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_signature_position(value: &str) -> Result<(), String> {
|
||||
if matches!(value, "above" | "below") {
|
||||
Ok(())
|
||||
@@ -112,10 +128,11 @@ fn row_to_identity(row: &crate::db::pool::Row<'_>) -> Result<Identity, crate::db
|
||||
signature_reply: row.get::<_, i32>(12)? != 0,
|
||||
signature_forward: row.get::<_, i32>(13)? != 0,
|
||||
signature_position: row.get(14)?,
|
||||
pgp_fingerprint: row.get(15)?,
|
||||
pgp_sign_default: row.get::<_, i32>(16)? != 0,
|
||||
pgp_encrypt_default: row.get::<_, i32>(17)? != 0,
|
||||
pgp_inline_default: row.get::<_, i32>(18)? != 0,
|
||||
signature_insert_mode: row.get(15)?,
|
||||
pgp_fingerprint: row.get(16)?,
|
||||
pgp_sign_default: row.get::<_, i32>(17)? != 0,
|
||||
pgp_encrypt_default: row.get::<_, i32>(18)? != 0,
|
||||
pgp_inline_default: row.get::<_, i32>(19)? != 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -155,6 +172,7 @@ pub fn get_default_identity(conn: &Connection) -> Result<Option<Identity>, Strin
|
||||
|
||||
pub fn create_identity(conn: &Connection, data: &CreateIdentity) -> Result<Identity, String> {
|
||||
validate_signature_position(&data.signature_position)?;
|
||||
validate_signature_insert_mode(&data.signature_insert_mode)?;
|
||||
if data.is_default {
|
||||
clear_default(conn)?;
|
||||
}
|
||||
@@ -174,6 +192,7 @@ pub fn create_identity(conn: &Connection, data: &CreateIdentity) -> Result<Ident
|
||||
data.signature_reply as i32,
|
||||
data.signature_forward as i32,
|
||||
data.signature_position,
|
||||
data.signature_insert_mode,
|
||||
data.pgp_fingerprint,
|
||||
data.pgp_sign_default as i32,
|
||||
data.pgp_encrypt_default as i32,
|
||||
@@ -198,6 +217,9 @@ pub fn update_identity(
|
||||
if let Some(value) = data.signature_position.as_deref() {
|
||||
validate_signature_position(value)?;
|
||||
}
|
||||
if let Some(value) = data.signature_insert_mode.as_deref() {
|
||||
validate_signature_insert_mode(value)?;
|
||||
}
|
||||
let mut sets = Vec::new();
|
||||
let mut values: Vec<DbValue> = Vec::new();
|
||||
let mut idx = 1usize;
|
||||
@@ -231,6 +253,7 @@ pub fn update_identity(
|
||||
boolean!(signature_reply, "signature_reply");
|
||||
boolean!(signature_forward, "signature_forward");
|
||||
text!(signature_position, "signature_position");
|
||||
text!(signature_insert_mode, "signature_insert_mode");
|
||||
if let Some(ref value) = data.pgp_fingerprint {
|
||||
sets.push(format!("pgp_fingerprint = ?{idx}"));
|
||||
values.push(value.to_db_value());
|
||||
@@ -293,6 +316,7 @@ mod tests {
|
||||
signature_reply: true,
|
||||
signature_forward: true,
|
||||
signature_position: "above".into(),
|
||||
signature_insert_mode: "silent".into(),
|
||||
pgp_fingerprint: None,
|
||||
pgp_sign_default: false,
|
||||
pgp_encrypt_default: false,
|
||||
@@ -313,6 +337,7 @@ mod tests {
|
||||
assert_eq!(identity.bcc, "archive@example.com");
|
||||
assert_eq!(identity.organization, "Example");
|
||||
assert_eq!(identity.signature_position, "below");
|
||||
assert_eq!(identity.signature_insert_mode, "silent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -324,6 +349,14 @@ mod tests {
|
||||
assert!(!get_identity(&conn, first.id).unwrap().unwrap().is_default);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_signature_insert_mode() {
|
||||
let conn = open_test_db();
|
||||
let mut value = sample("a@example.com", true);
|
||||
value.signature_insert_mode = "magic".into();
|
||||
assert!(create_identity(&conn, &value).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_signature_position() {
|
||||
let conn = open_test_db();
|
||||
|
||||
@@ -13,6 +13,7 @@ pub struct MailPreferences {
|
||||
pub default_sort: String,
|
||||
pub flagged_on_top: bool,
|
||||
pub sync_imap_keywords: bool,
|
||||
pub compact_wide_rows: bool,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
@@ -28,6 +29,7 @@ pub struct UpdateMailPreferences {
|
||||
pub default_sort: Option<String>,
|
||||
pub flagged_on_top: Option<bool>,
|
||||
pub sync_imap_keywords: Option<bool>,
|
||||
pub compact_wide_rows: Option<bool>,
|
||||
}
|
||||
|
||||
pub fn get_preferences(conn: &Connection) -> Result<MailPreferences, String> {
|
||||
@@ -43,7 +45,8 @@ pub fn get_preferences(conn: &Connection) -> Result<MailPreferences, String> {
|
||||
default_sort: row.get(7)?,
|
||||
flagged_on_top: row.get::<_, i32>(8)? != 0,
|
||||
sync_imap_keywords: row.get::<_, i32>(9)? != 0,
|
||||
updated_at: row.get(10)?,
|
||||
compact_wide_rows: row.get::<_, i32>(10)? != 0,
|
||||
updated_at: row.get(11)?,
|
||||
})
|
||||
})
|
||||
.map_err(|error| format!("Failed to read mail preferences: {error}"))
|
||||
@@ -121,6 +124,7 @@ pub fn update_preferences(
|
||||
let sync_imap_keywords = data
|
||||
.sync_imap_keywords
|
||||
.unwrap_or(current.sync_imap_keywords);
|
||||
let compact_wide_rows = data.compact_wide_rows.unwrap_or(current.compact_wide_rows);
|
||||
|
||||
conn.execute(
|
||||
crate::dbq!(db_mail_preferences::Q_003),
|
||||
@@ -134,7 +138,8 @@ pub fn update_preferences(
|
||||
show_snippets as i32,
|
||||
default_sort,
|
||||
flagged_on_top as i32,
|
||||
sync_imap_keywords as i32
|
||||
sync_imap_keywords as i32,
|
||||
compact_wide_rows as i32
|
||||
],
|
||||
)
|
||||
.map_err(|error| format!("Failed to update mail preferences: {error}"))?;
|
||||
@@ -167,6 +172,7 @@ mod tests {
|
||||
default_sort: Some("date_asc".to_string()),
|
||||
flagged_on_top: Some(true),
|
||||
sync_imap_keywords: Some(true),
|
||||
compact_wide_rows: Some(false),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
@@ -175,5 +181,6 @@ mod tests {
|
||||
assert_eq!(updated.thread_mode, "flat");
|
||||
assert!(updated.flagged_on_top);
|
||||
assert!(updated.sync_imap_keywords);
|
||||
assert!(!updated.compact_wide_rows);
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,10 @@ const SQLITE_MIGRATIONS: &[(u32, &str)] = &[
|
||||
49,
|
||||
include_str!("../../migrations/sqlite/V049__blob_delete_outbox.sql"),
|
||||
),
|
||||
(
|
||||
50,
|
||||
include_str!("../../migrations/sqlite/V050__compact_rows_and_signature_mode.sql"),
|
||||
),
|
||||
];
|
||||
const MYSQL_MIGRATIONS: &[(u32, &str)] = &[
|
||||
(
|
||||
@@ -48,6 +52,10 @@ const MYSQL_MIGRATIONS: &[(u32, &str)] = &[
|
||||
49,
|
||||
include_str!("../../migrations/mysql/V049__blob_delete_outbox.sql"),
|
||||
),
|
||||
(
|
||||
50,
|
||||
include_str!("../../migrations/mysql/V050__compact_rows_and_signature_mode.sql"),
|
||||
),
|
||||
];
|
||||
const PGSQL_MIGRATIONS: &[(u32, &str)] = &[
|
||||
(
|
||||
@@ -62,6 +70,10 @@ const PGSQL_MIGRATIONS: &[(u32, &str)] = &[
|
||||
49,
|
||||
include_str!("../../migrations/pgsql/V049__blob_delete_outbox.sql"),
|
||||
),
|
||||
(
|
||||
50,
|
||||
include_str!("../../migrations/pgsql/V050__compact_rows_and_signature_mode.sql"),
|
||||
),
|
||||
];
|
||||
|
||||
const TENANT_TABLES: &[&str] = &[
|
||||
|
||||
@@ -62,6 +62,7 @@ mod tests {
|
||||
default_sort: None,
|
||||
flagged_on_top: None,
|
||||
sync_imap_keywords: None,
|
||||
compact_wide_rows: None,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Reference in new issue
Block a user