#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Tab {
#[default]
Status,
Jobs,
Config,
Logs,
About,
}
impl Tab {
pub const ALL: [Tab; 5] = [Tab::Status, Tab::Jobs, Tab::Config, Tab::Logs, Tab::About];
pub fn label(self) -> &'static str {
match self {
Tab::Status => "Status",
Tab::Jobs => "Jobs",
Tab::Config => "Config",
Tab::Logs => "Logs",
Tab::About => "About",
}
}
pub fn parse(name: &str) -> Option<Self> {
match name.trim().to_ascii_lowercase().as_str() {
"status" => Some(Self::Status),
"jobs" => Some(Self::Jobs),
"config" => Some(Self::Config),
"logs" => Some(Self::Logs),
"about" => Some(Self::About),
_ => None,
}
}
pub fn initial() -> Self {
std::env::var("STUDIO_WORKER_UI_TAB")
.ok()
.and_then(|s| Self::parse(&s))
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_returns_five_tabs_in_render_order() {
let labels: Vec<&str> = Tab::ALL.iter().map(|t| t.label()).collect();
assert_eq!(
labels,
["Status", "Jobs", "Config", "Logs", "About"],
"tab labels + order are part of the UI contract"
);
}
#[test]
fn default_is_status() {
assert_eq!(Tab::default(), Tab::Status);
}
#[test]
fn parse_round_trips_with_label_case_insensitively() {
for tab in Tab::ALL {
assert_eq!(Tab::parse(tab.label()), Some(tab));
assert_eq!(Tab::parse(&tab.label().to_uppercase()), Some(tab));
}
assert!(Tab::parse("").is_none());
assert!(Tab::parse("nope").is_none());
}
#[test]
fn initial_falls_back_to_default_when_env_unset() {
let prev = std::env::var("STUDIO_WORKER_UI_TAB").ok();
std::env::remove_var("STUDIO_WORKER_UI_TAB");
assert_eq!(Tab::initial(), Tab::default());
if let Some(v) = prev {
std::env::set_var("STUDIO_WORKER_UI_TAB", v);
}
}
#[test]
fn labels_are_unique() {
use std::collections::HashSet;
let unique: HashSet<&str> = Tab::ALL.iter().map(|t| t.label()).collect();
assert_eq!(unique.len(), Tab::ALL.len());
}
}