use super::*;
#[test]
fn option_store_starts_without_explicit_overrides_but_resolves_defaults() {
let store = OptionStore::new();
assert!(store.is_empty());
assert_eq!(store.global_value(OptionName::Status), None);
assert_eq!(
store.resolve(Some(&session_name("alpha")), OptionName::Status),
Some("on")
);
assert_eq!(
store.resolve(None, OptionName::DefaultTerminal),
Some("tmux-256color")
);
assert_eq!(
store.resolve_for_window(&session_name("alpha"), 0, OptionName::AutomaticRenameFormat),
Some("#{?pane_in_mode,[tmux],#{pane_current_command}}#{?pane_dead,[dead],}")
);
}
#[test]
fn session_values_override_global_values_at_read_time() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
let beta = session_name("beta");
let alpha_window = WindowTarget::with_window(alpha.clone(), 0);
let beta_window = WindowTarget::with_window(beta.clone(), 0);
store
.set(
ScopeSelector::Global,
OptionName::PaneBorderStyle,
"colour1".to_owned(),
SetOptionMode::Replace,
)
.expect("global set succeeds");
store
.set(
ScopeSelector::Window(alpha_window.clone()),
OptionName::PaneBorderStyle,
"colour2".to_owned(),
SetOptionMode::Replace,
)
.expect("window set succeeds");
assert_eq!(
store.resolve_for_window(&alpha, 0, OptionName::PaneBorderStyle),
Some("colour2")
);
assert_eq!(
store.resolve_for_window(beta_window.session_name(), 0, OptionName::PaneBorderStyle),
Some("colour1")
);
}
#[test]
fn pane_values_override_window_session_and_global_values() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
let window = WindowTarget::with_window(alpha.clone(), 2);
let pane = PaneTarget::with_window(alpha.clone(), 2, 3);
store
.set(
ScopeSelector::Global,
OptionName::WindowStyle,
"fg=colour1".to_owned(),
SetOptionMode::Replace,
)
.expect("global set succeeds");
store
.set(
ScopeSelector::Window(window.clone()),
OptionName::WindowStyle,
"fg=colour2".to_owned(),
SetOptionMode::Replace,
)
.expect("window set succeeds");
store
.set(
ScopeSelector::Pane(pane.clone()),
OptionName::WindowStyle,
"fg=colour3".to_owned(),
SetOptionMode::Replace,
)
.expect("pane set succeeds");
assert_eq!(
store.resolve_for_pane(&alpha, 2, 3, OptionName::WindowStyle),
Some("fg=colour3")
);
assert_eq!(
store.resolve_for_pane(&alpha, 2, 4, OptionName::WindowStyle),
Some("fg=colour2")
);
assert_eq!(
store.resolve_for_pane(&alpha, 5, 0, OptionName::WindowStyle),
Some("fg=colour1")
);
}
#[test]
fn terminal_features_append_preserves_default_then_existing_order() {
let mut store = OptionStore::new();
store
.set(
ScopeSelector::Global,
OptionName::TerminalFeatures,
",xterm*:RGB".to_owned(),
SetOptionMode::Append,
)
.expect("first append succeeds");
store
.set(
ScopeSelector::Global,
OptionName::TerminalFeatures,
"screen*:AX".to_owned(),
SetOptionMode::Append,
)
.expect("second append succeeds");
assert_eq!(
store.global_value(OptionName::TerminalFeatures),
Some(
"xterm*:clipboard:ccolour:cstyle:focus:title,screen*:title,rxvt*:ignorefkeys,xterm*:RGB,screen*:AX"
)
);
}
#[test]
fn append_to_non_string_scalar_options_behaves_like_replace() {
let mut store = OptionStore::new();
store
.set(
ScopeSelector::Global,
OptionName::Status,
"off".to_owned(),
SetOptionMode::Append,
)
.expect("tmux treats append on non-string scalar as replace");
assert_eq!(store.global_value(OptionName::Status), Some("off"));
}
#[test]
fn status_line_counts_are_preserved_for_tmux_compatibility() {
let mut store = OptionStore::new();
store
.set(
ScopeSelector::Global,
OptionName::Status,
"2".to_owned(),
SetOptionMode::Replace,
)
.expect("tmux-compatible status line count is accepted");
assert_eq!(
store.resolve(Some(&session_name("alpha")), OptionName::Status),
Some("2")
);
}
#[test]
fn pane_border_lines_number_is_preserved_for_tmux_compatibility() {
let mut store = OptionStore::new();
store
.set(
ScopeSelector::Global,
OptionName::PaneBorderLines,
"number".to_owned(),
SetOptionMode::Replace,
)
.expect("tmux-compatible numbered pane border value is accepted");
assert_eq!(
store.resolve_for_window(&session_name("alpha"), 0, OptionName::PaneBorderLines),
Some("number")
);
}
#[test]
fn allow_passthrough_preserves_all_for_tmux_compatibility() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
store
.set(
ScopeSelector::Global,
OptionName::AllowPassthrough,
"all".to_owned(),
SetOptionMode::Replace,
)
.expect("tmux-compatible all value is accepted");
assert_eq!(
store.resolve_for_window(&alpha, 0, OptionName::AllowPassthrough),
Some("all")
);
}
#[test]
fn prefix_options_accept_modified_named_keys() {
let mut store = OptionStore::new();
for (input, canonical) in [
("C-PageUp", "C-PPage"),
("C-PageDown", "C-NPage"),
("C-Insert", "C-IC"),
("C-Delete", "C-DC"),
("C-PPage", "C-PPage"),
("C-NPage", "C-NPage"),
("C-IC", "C-IC"),
("C-DC", "C-DC"),
] {
store
.set(
ScopeSelector::Global,
OptionName::Prefix,
input.to_owned(),
SetOptionMode::Replace,
)
.expect("modified named key is accepted as a prefix");
assert_eq!(
store.resolve(Some(&session_name("alpha")), OptionName::Prefix),
Some(canonical),
"{input} should store as canonical key name"
);
}
}
#[test]
fn server_only_options_can_be_stored_at_explicit_session_scopes() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
store
.set(
ScopeSelector::Session(alpha.clone()),
OptionName::BufferLimit,
"100".to_owned(),
SetOptionMode::Replace,
)
.expect("tmux accepts explicit local storage for server option names");
assert_eq!(
store.session_value(&alpha, OptionName::BufferLimit),
Some("100")
);
assert_eq!(
store.resolve(Some(&alpha), OptionName::BufferLimit),
Some("100")
);
}
#[test]
fn utf8_rendering_options_are_marked_as_render_affecting() {
assert!(registry::option_affects_rendering(
OptionName::CodepointWidths
));
assert!(registry::option_affects_rendering(
OptionName::VariationSelectorAlwaysWide
));
assert!(!registry::option_affects_rendering(OptionName::ExitEmpty));
}
#[test]
fn resolved_snapshots_merge_defaults_global_session_window_and_pane_values() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
store
.set(
ScopeSelector::Global,
OptionName::Status,
"off".to_owned(),
SetOptionMode::Replace,
)
.expect("global status succeeds");
store
.set(
ScopeSelector::Session(alpha.clone()),
OptionName::Status,
"on".to_owned(),
SetOptionMode::Replace,
)
.expect("session status succeeds");
store
.set(
ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 1)),
OptionName::WindowStyle,
"fg=colour2".to_owned(),
SetOptionMode::Replace,
)
.expect("window style succeeds");
store
.set(
ScopeSelector::Pane(PaneTarget::with_window(alpha.clone(), 1, 2)),
OptionName::WindowStyle,
"fg=colour3".to_owned(),
SetOptionMode::Replace,
)
.expect("pane style succeeds");
let resolved = store.resolved_for_pane(&alpha, 1, 2);
assert_eq!(
resolved.get(&OptionName::Status).map(String::as_str),
Some("on")
);
assert_eq!(
resolved.get(&OptionName::WindowStyle).map(String::as_str),
Some("fg=colour3")
);
assert_eq!(
resolved
.get(&OptionName::DefaultTerminal)
.map(String::as_str),
Some("tmux-256color")
);
}
#[test]
fn removing_a_session_discards_session_window_and_pane_values() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
store
.set(
ScopeSelector::Session(alpha.clone()),
OptionName::Status,
"off".to_owned(),
SetOptionMode::Replace,
)
.expect("session set succeeds");
store
.set(
ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 3)),
OptionName::MainPaneWidth,
"90".to_owned(),
SetOptionMode::Replace,
)
.expect("window set succeeds");
store
.set(
ScopeSelector::Pane(PaneTarget::with_window(alpha.clone(), 3, 1)),
OptionName::WindowStyle,
"default,bold".to_owned(),
SetOptionMode::Replace,
)
.expect("pane set succeeds");
assert!(store.remove_session(&alpha).is_some());
assert_eq!(store.resolve(Some(&alpha), OptionName::Status), Some("on"));
assert_eq!(
store.resolve_for_pane(&alpha, 3, 1, OptionName::MainPaneWidth),
Some("80")
);
assert_eq!(
store.resolve_for_pane(&alpha, 3, 1, OptionName::WindowStyle),
Some("default")
);
}
#[test]
fn renaming_a_session_rekeys_session_window_and_pane_values() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
let beta = session_name("beta");
store
.set(
ScopeSelector::Session(alpha.clone()),
OptionName::Status,
"off".to_owned(),
SetOptionMode::Replace,
)
.expect("session set succeeds");
store
.set(
ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 3)),
OptionName::MainPaneWidth,
"90".to_owned(),
SetOptionMode::Replace,
)
.expect("window set succeeds");
store
.set(
ScopeSelector::Pane(PaneTarget::with_window(alpha.clone(), 3, 1)),
OptionName::WindowStyle,
"default,bold".to_owned(),
SetOptionMode::Replace,
)
.expect("pane set succeeds");
store
.rename_session(&alpha, beta.clone())
.expect("rename succeeds");
assert_eq!(store.resolve(Some(&alpha), OptionName::Status), Some("on"));
assert_eq!(store.resolve(Some(&beta), OptionName::Status), Some("off"));
assert_eq!(
store.resolve_for_window(&beta, 3, OptionName::MainPaneWidth),
Some("90")
);
assert_eq!(
store.resolve_for_pane(&beta, 3, 1, OptionName::WindowStyle),
Some("default,bold")
);
}
#[test]
fn swapping_windows_rekeys_window_and_pane_values() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
let source = WindowTarget::with_window(alpha.clone(), 1);
let target = WindowTarget::with_window(alpha.clone(), 3);
let source_pane = PaneTarget::with_window(alpha.clone(), 1, 0);
let target_pane = PaneTarget::with_window(alpha.clone(), 3, 0);
store
.set(
ScopeSelector::Window(source.clone()),
OptionName::AutomaticRename,
"off".to_owned(),
SetOptionMode::Replace,
)
.expect("source window set succeeds");
store
.set(
ScopeSelector::Pane(source_pane.clone()),
OptionName::WindowStyle,
"default,bold".to_owned(),
SetOptionMode::Replace,
)
.expect("source pane set succeeds");
store.swap_window_overrides(&source, &target);
assert_eq!(
store.window_value(&source, OptionName::AutomaticRename),
None
);
assert_eq!(
store.pane_value(&source_pane, OptionName::WindowStyle),
None
);
assert_eq!(
store.window_value(&target, OptionName::AutomaticRename),
Some("off")
);
assert_eq!(
store.pane_value(&target_pane, OptionName::WindowStyle),
Some("default,bold")
);
}
#[test]
fn moving_windows_rekeys_window_and_pane_values() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
let source = WindowTarget::with_window(alpha.clone(), 2);
let target = WindowTarget::with_window(alpha.clone(), 4);
let source_pane = PaneTarget::with_window(alpha.clone(), 2, 0);
let target_pane = PaneTarget::with_window(alpha.clone(), 4, 0);
store
.set(
ScopeSelector::Window(source.clone()),
OptionName::AutomaticRename,
"off".to_owned(),
SetOptionMode::Replace,
)
.expect("source window set succeeds");
store
.set(
ScopeSelector::Pane(source_pane.clone()),
OptionName::WindowStyle,
"default,bold".to_owned(),
SetOptionMode::Replace,
)
.expect("source pane set succeeds");
store.move_window_overrides(&source, &target);
assert_eq!(
store.window_value(&source, OptionName::AutomaticRename),
None
);
assert_eq!(
store.pane_value(&source_pane, OptionName::WindowStyle),
None
);
assert_eq!(
store.window_value(&target, OptionName::AutomaticRename),
Some("off")
);
assert_eq!(
store.pane_value(&target_pane, OptionName::WindowStyle),
Some("default,bold")
);
}
#[test]
fn renaming_to_an_existing_option_key_fails_without_mutation() {
let mut store = OptionStore::new();
let alpha = session_name("alpha");
let beta = session_name("beta");
store
.set(
ScopeSelector::Window(WindowTarget::with_window(alpha.clone(), 3)),
OptionName::MainPaneWidth,
"90".to_owned(),
SetOptionMode::Replace,
)
.expect("window set succeeds");
store
.set(
ScopeSelector::Window(WindowTarget::with_window(beta.clone(), 3)),
OptionName::MainPaneWidth,
"70".to_owned(),
SetOptionMode::Replace,
)
.expect("window set succeeds");
let error = store
.rename_session(&alpha, beta.clone())
.expect_err("existing destination rejects rename");
assert_eq!(
error,
RmuxError::Server("window options already exist for beta:3".to_owned())
);
assert_eq!(
store.resolve_for_window(&alpha, 3, OptionName::MainPaneWidth),
Some("90")
);
assert_eq!(
store.resolve_for_window(&beta, 3, OptionName::MainPaneWidth),
Some("70")
);
}