From e62d64874f04d5288c1be2cd1add6847bd550789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Fri, 8 May 2026 05:53:02 -0400 Subject: [PATCH] Track tab favicon keys --- crates/ely_browser_core/src/state/tabs.rs | 24 +++++++++ crates/ely_browser_core/tests/tab_metadata.rs | 52 +++++++++++++++++++ crates/ely_domain/src/tab.rs | 24 ++++++++- 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 crates/ely_browser_core/tests/tab_metadata.rs diff --git a/crates/ely_browser_core/src/state/tabs.rs b/crates/ely_browser_core/src/state/tabs.rs index ced2337..6ce909f 100644 --- a/crates/ely_browser_core/src/state/tabs.rs +++ b/crates/ely_browser_core/src/state/tabs.rs @@ -274,6 +274,30 @@ impl BrowserCore { Ok(()) } + pub fn set_tab_favicon_key( + &mut self, + tab_id: &TabId, + favicon_key: impl Into, + ) -> Result<(), CoreError> { + let tab = self + .tabs + .iter_mut() + .find(|tab| tab.id() == tab_id) + .ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?; + tab.set_favicon_key(favicon_key)?; + Ok(()) + } + + pub fn clear_tab_favicon_key(&mut self, tab_id: &TabId) -> Result<(), CoreError> { + let tab = self + .tabs + .iter_mut() + .find(|tab| tab.id() == tab_id) + .ok_or_else(|| CoreError::TabNotFound { id: tab_id.clone() })?; + tab.clear_favicon_key(); + Ok(()) + } + pub fn active_tab(&self) -> Result<&BrowserTab, CoreError> { self.tabs .iter() diff --git a/crates/ely_browser_core/tests/tab_metadata.rs b/crates/ely_browser_core/tests/tab_metadata.rs new file mode 100644 index 0000000..5e4b797 --- /dev/null +++ b/crates/ely_browser_core/tests/tab_metadata.rs @@ -0,0 +1,52 @@ +use std::error::Error; + +use ely_browser_core::{BrowserCore, CoreError, InitialBrowserConfig}; +use ely_domain::DomainError; + +#[test] +fn new_tabs_start_without_favicon_key() -> Result<(), Box> { + let core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + + assert_eq!(core.active_tab()?.favicon_key(), None); + Ok(()) +} + +#[test] +fn tab_favicon_key_can_be_set_and_cleared() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let tab_id = core.active_tab()?.id().clone(); + + core.set_tab_favicon_key(&tab_id, "favicons/example.ico")?; + assert_eq!(core.active_tab()?.favicon_key(), Some("favicons/example.ico")); + + core.clear_tab_favicon_key(&tab_id)?; + assert_eq!(core.active_tab()?.favicon_key(), None); + Ok(()) +} + +#[test] +fn tab_favicon_key_requires_text() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let tab_id = core.active_tab()?.id().clone(); + + let error = match core.set_tab_favicon_key(&tab_id, " ") { + Err(error) => error, + Ok(_) => return Err("favicon key should require text".into()), + }; + + assert_eq!(error, CoreError::Domain(DomainError::EmptyField { field: "favicon_key" })); + Ok(()) +} + +#[test] +fn archived_tabs_preserve_favicon_key() -> Result<(), Box> { + let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?; + let tab_id = core.active_tab()?.id().clone(); + + core.set_tab_favicon_key(&tab_id, "favicons/example.ico")?; + core.close_active_tab()?; + let snapshot = core.snapshot()?; + + assert_eq!(snapshot.archived_tabs[0].tab().favicon_key(), Some("favicons/example.ico")); + Ok(()) +} diff --git a/crates/ely_domain/src/tab.rs b/crates/ely_domain/src/tab.rs index 0c7056c..52b8d7b 100644 --- a/crates/ely_domain/src/tab.rs +++ b/crates/ely_domain/src/tab.rs @@ -1,6 +1,6 @@ use std::time::SystemTime; -use crate::{ProfileId, SpaceId, SplitId, TabId, UrlText}; +use crate::{DomainError, ProfileId, SpaceId, SplitId, TabId, UrlText}; #[derive(Clone, Debug, Eq, PartialEq)] pub enum TabState { @@ -26,6 +26,7 @@ pub struct BrowserTab { profile_id: ProfileId, title: String, url: UrlText, + favicon_key: Option, parent_tab_id: Option, state: TabState, flags: TabFlags, @@ -52,6 +53,7 @@ impl BrowserTab { profile_id, title: title.into(), url, + favicon_key: None, parent_tab_id: None, state: TabState::Ready, flags: TabFlags::default(), @@ -100,6 +102,26 @@ impl BrowserTab { &self.url } + #[must_use] + pub fn favicon_key(&self) -> Option<&str> { + self.favicon_key.as_deref() + } + + pub fn set_favicon_key(&mut self, favicon_key: impl Into) -> Result<(), DomainError> { + let favicon_key = favicon_key.into(); + let favicon_key = favicon_key.trim(); + if favicon_key.is_empty() { + return Err(DomainError::EmptyField { field: "favicon_key" }); + } + + self.favicon_key = Some(favicon_key.to_string()); + Ok(()) + } + + pub fn clear_favicon_key(&mut self) { + self.favicon_key = None; + } + #[must_use] pub fn parent_tab_id(&self) -> Option<&TabId> { self.parent_tab_id.as_ref()