use std::collections::HashMap;
use super::Refusal;
use crate::core::entity::snapshot::ViewSnapshot;
use crate::core::entity::{Organization, OrganizationId, Workspace};
pub type LeftViews = HashMap<OrganizationId, ViewSnapshot>;
pub fn signed_in(
sign_ins: impl IntoIterator<Item = (Option<Organization>, bool)>,
) -> Vec<Workspace> {
sign_ins
.into_iter()
.filter_map(|(organization, current)| {
Some(Workspace {
organization: organization?,
current,
})
})
.collect()
}
pub fn open_switcher(workspaces: &[Workspace]) -> Result<usize, Refusal> {
if workspaces.len() < 2 {
return Err(Refusal::OnlyOneWorkspace);
}
Ok(workspaces.iter().position(|w| w.current).unwrap_or(0))
}
pub fn pick(workspace: &Workspace) -> Option<OrganizationId> {
(!workspace.current).then(|| workspace.organization.id.clone())
}
pub fn leave(left: &mut LeftViews, workspace: OrganizationId, view: Option<ViewSnapshot>) {
if let Some(view) = view {
left.insert(workspace, view);
}
}
pub fn view_on_return(
left: &mut LeftViews,
workspace: &OrganizationId,
pinned: bool,
recorded: impl FnOnce() -> Option<ViewSnapshot>,
) -> Option<ViewSnapshot> {
if pinned {
return None;
}
left.remove(workspace).or_else(recorded)
}
#[cfg(test)]
mod tests {
use super::*;
fn organization(id: &str) -> Organization {
Organization {
id: id.into(),
name: id.to_uppercase(),
url_key: id.into(),
}
}
fn workspace(id: &str, current: bool) -> Workspace {
Workspace {
organization: organization(id),
current,
}
}
fn view(hour: u32) -> ViewSnapshot {
serde_json::from_str(&format!(
r#"{{"version":1,"workspace":"/repo","cwd":"/repo","pid":1,
"updated_at":"2026-09-25T{hour:02}:00:00Z","destination":{{"kind":"my_issues"}},
"screen":"issue_list","group_by":"status"}}"#
))
.unwrap()
}
#[test]
fn named_sign_ins_are_offered_and_unnamed_ones_left_out() {
let offered = signed_in([
(Some(organization("acme")), false),
(None, false),
(Some(organization("globex")), true),
]);
assert_eq!(
offered,
[workspace("acme", false), workspace("globex", true)]
);
}
#[test]
fn the_switcher_starts_on_the_workspace_in_use() {
let workspaces = [workspace("acme", false), workspace("globex", true)];
assert_eq!(open_switcher(&workspaces), Ok(1));
}
#[test]
fn with_one_workspace_switching_says_how_to_add_another() {
let refusal = open_switcher(&[workspace("acme", true)]).unwrap_err();
assert_eq!(refusal, Refusal::OnlyOneWorkspace);
assert!(refusal.to_string().contains("linear-tui auth login"));
}
#[test]
fn picking_another_workspace_moves_there_and_the_current_one_does_nothing() {
assert_eq!(pick(&workspace("acme", false)), Some("acme".into()));
assert_eq!(pick(&workspace("globex", true)), None);
}
#[test]
fn leaving_keeps_the_page_and_nothing_when_there_was_none() {
let mut left = LeftViews::new();
leave(&mut left, "acme".into(), Some(view(9)));
leave(&mut left, "globex".into(), None);
assert_eq!(left.get(&"acme".into()), Some(&view(9)));
assert!(!left.contains_key(&"globex".into()));
}
#[test]
fn coming_back_opens_the_page_left_this_run_once() {
let mut left = LeftViews::new();
leave(&mut left, "acme".into(), Some(view(9)));
let back = view_on_return(&mut left, &"acme".into(), false, || Some(view(8)));
assert_eq!(back, Some(view(9)));
let again = view_on_return(&mut left, &"acme".into(), false, || Some(view(8)));
assert_eq!(again, Some(view(8)));
}
#[test]
fn a_workspace_not_left_this_run_opens_on_its_recorded_view() {
let mut left = LeftViews::new();
let back = view_on_return(&mut left, &"acme".into(), false, || Some(view(8)));
assert_eq!(back, Some(view(8)));
}
#[test]
fn a_pinned_directory_reopens_no_view_on_return() {
let mut left = LeftViews::new();
leave(&mut left, "acme".into(), Some(view(9)));
let back = view_on_return(&mut left, &"acme".into(), true, || Some(view(8)));
assert_eq!(back, None);
}
}