mirror of
https://github.com/zellij-org/zellij.git
synced 2026-10-11 22:50:07 +00:00
* initial chrome * mobile state * feat(mobile-web): implement browser→server actions (fit toggle, single-pane fullscreen, pane/tab focus) with fullscreen-truth reconciliation * panning * remove mobile plugin * fix first load race * improve web handshake * rustfmt * cleanups * moar cleanups * add bundle asset check to ci * fix(mobile-web): tab-scope co-presence, yield single-pane to desktop clients, fix render-mode desync * make mobile use no-ui fullscreen * client side welcome screen for mobile * attach web clients to first tab on tiles surface * fix: opening new pane in mobile single mode no longer exits mobile single mode * mobile chrome light theme support * fix server races and flaky tests * rustfmt * fix flakes * add pr
771 lines
27 KiB
Rust
771 lines
27 KiB
Rust
#![cfg(unix)]
|
|
|
|
use insta::assert_snapshot;
|
|
use zellij_integration_tests::{
|
|
claim_first_terminal_and_wait_for_prompt, col, keys, normalized,
|
|
split_down_and_wait_for_prompt, start_zellij, FakePtyHandle, GridSnapshot, LayoutInfo, Size,
|
|
TestRunner, TestSession, PROMPT, TERMINAL_SIZE,
|
|
};
|
|
use zellij_utils::input::command::TerminalAction;
|
|
|
|
const STACKED_LAYOUT: &str = r#"
|
|
layout {
|
|
default_tab_template {
|
|
pane size=1 borderless=true {
|
|
plugin location="tab-bar"
|
|
}
|
|
children
|
|
pane size=1 borderless=true {
|
|
plugin location="status-bar"
|
|
}
|
|
}
|
|
tab {
|
|
pane stacked=true {
|
|
pane
|
|
pane
|
|
pane
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
|
|
const STACK_WITH_COMMAND_MEMBER_LAYOUT: &str = r#"
|
|
layout {
|
|
default_tab_template {
|
|
pane size=1 borderless=true {
|
|
plugin location="tab-bar"
|
|
}
|
|
children
|
|
pane size=1 borderless=true {
|
|
plugin location="status-bar"
|
|
}
|
|
}
|
|
tab {
|
|
pane stacked=true {
|
|
pane
|
|
pane command="member-command"
|
|
}
|
|
}
|
|
}
|
|
"#;
|
|
|
|
fn selected_entry(title: &str) -> String {
|
|
format!("> │ {}", title)
|
|
}
|
|
|
|
fn add_stacked_pane_and_wait_for_selected_entry(
|
|
zellij: &TestSession,
|
|
selected_entry_title: &str,
|
|
) -> FakePtyHandle {
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('s'));
|
|
let terminal = zellij.expect_pty_spawn();
|
|
terminal.output(PROMPT);
|
|
let selected_marker = selected_entry(selected_entry_title);
|
|
zellij.wait_until("stacked pane added and selected", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears() && grid_snapshot.contains(&selected_marker)
|
|
});
|
|
terminal
|
|
}
|
|
|
|
fn build_three_member_stack(zellij: &TestSession) -> [FakePtyHandle; 3] {
|
|
let first_member = claim_first_terminal_and_wait_for_prompt(zellij);
|
|
first_member.output(b"first-member\r\n$ ");
|
|
zellij.wait_until("first member content rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("first-member")
|
|
});
|
|
let second_member = add_stacked_pane_and_wait_for_selected_entry(zellij, "Pane #2");
|
|
second_member.output(b"second-member\r\n$ ");
|
|
zellij.wait_until("second member content rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("second-member")
|
|
});
|
|
let third_member = add_stacked_pane_and_wait_for_selected_entry(zellij, "Pane #3");
|
|
third_member.output(b"third-member\r\n$ ");
|
|
zellij.wait_until("third member content rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("third-member")
|
|
});
|
|
[first_member, second_member, third_member]
|
|
}
|
|
|
|
fn sgr_left_click(column: usize, line: usize) -> Vec<u8> {
|
|
format!(
|
|
"\u{1b}[<0;{};{}M\u{1b}[<0;{};{}m",
|
|
column, line, column, line
|
|
)
|
|
.into_bytes()
|
|
}
|
|
|
|
fn sgr_alt_left_click(column: usize, line: usize) -> Vec<u8> {
|
|
format!(
|
|
"\u{1b}[<8;{};{}M\u{1b}[<8;{};{}m",
|
|
column, line, column, line
|
|
)
|
|
.into_bytes()
|
|
}
|
|
|
|
fn display_column_of(line: &str, needle: &str) -> Option<usize> {
|
|
line.find(needle)
|
|
.map(|byte_offset| line[..byte_offset].chars().count())
|
|
}
|
|
|
|
fn entry_click_coordinates(grid_snapshot: &GridSnapshot, entry_title: &str) -> (usize, usize) {
|
|
let lines = grid_snapshot.lines();
|
|
let (line_index, line) = lines
|
|
.iter()
|
|
.enumerate()
|
|
.find(|(_, line)| line.contains(entry_title))
|
|
.expect("entry title is on a header row");
|
|
let column = display_column_of(line, entry_title).expect("entry title is on the line") + 1;
|
|
(column, line_index + 1)
|
|
}
|
|
|
|
#[test]
|
|
fn stack_renders_as_title_list_above_the_visible_pane() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
let grid_snapshot = zellij.wait_until("stack list settled", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
&& grid_snapshot.contains("third-member")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn a_layout_stack_renders_as_a_title_list() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_layout(LayoutInfo::Stringified(STACKED_LAYOUT.to_string()))
|
|
.start();
|
|
for _ in 0..3 {
|
|
let member = zellij.expect_pty_spawn();
|
|
member.output(PROMPT);
|
|
}
|
|
|
|
let grid_snapshot = zellij.wait_until("layout stack rendered as a list", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains(&selected_entry("Pane #1"))
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot.contains("Pane #3")
|
|
&& grid_snapshot.cursor_is_at(col(2).row(5))
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn disabling_stacked_pane_list_restores_the_classic_stack() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_config("stacked_pane_list false")
|
|
.start();
|
|
let first_member = claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
first_member.output(b"first-member\r\n$ ");
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('s'));
|
|
let second_member = zellij.expect_pty_spawn();
|
|
second_member.output(PROMPT);
|
|
|
|
let grid_snapshot = zellij.wait_until("classic stack rendered", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& !grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.cursor_is_at(col(2).row(3))
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn up_and_down_move_the_selection_through_the_list() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
let middle_member_selected = zellij.wait_until("middle member swapped in", |grid_snapshot| {
|
|
grid_snapshot.contains("<g> LOCK")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("second-member")
|
|
&& !grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
assert_snapshot!(normalized(&middle_member_selected));
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("top member swapped in", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #1")) && grid_snapshot.contains("first-member")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('j'));
|
|
zellij.wait_until("selection moved back down", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("second-member")
|
|
});
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn navigating_past_the_top_entry_leaves_the_stack() {
|
|
let mut zellij = start_zellij();
|
|
claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
let _lower_terminal = split_down_and_wait_for_prompt(&zellij);
|
|
add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #3");
|
|
add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #4");
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("selection on the middle entry", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("selection on the top entry", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("focus left the stack to the pane above", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.cursor_is_at(col(2).row(2))
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('j'));
|
|
zellij.wait_until("focus re-entered the stack", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.cursor_is_at(col(2).row(16))
|
|
});
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn closing_the_visible_member_promotes_its_neighbor() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('x'));
|
|
|
|
let grid_snapshot =
|
|
zellij.wait_until("third member closed, second promoted", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& !grid_snapshot.contains("Pane #3")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("second-member")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn a_stack_list_dissolves_into_a_normal_pane_at_one_member() {
|
|
let mut zellij = start_zellij();
|
|
let first_member = claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
first_member.output(b"first-member\r\n$ ");
|
|
add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #2");
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('x'));
|
|
|
|
let grid_snapshot = zellij.wait_until("list dissolved to a single pane", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& !grid_snapshot.contains("Pane #2")
|
|
&& !grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains("first-member")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn a_hidden_member_exiting_removes_its_entry() {
|
|
let mut zellij = start_zellij();
|
|
let [_first_member, second_member, _third_member] = build_three_member_stack(&zellij);
|
|
|
|
second_member.exit(Some(0));
|
|
|
|
let grid_snapshot = zellij.wait_until("hidden member removed from the list", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& !grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn breaking_the_visible_member_out_promotes_and_moves_it_to_a_new_tab() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::ctrl('t'));
|
|
zellij.send_stdin(&keys::key('b'));
|
|
zellij.wait_until("visible member moved to a new tab", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("Tab #2")
|
|
&& grid_snapshot.contains("third-member")
|
|
&& !grid_snapshot.contains("Pane #1")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('h'));
|
|
let grid_snapshot = zellij.wait_until("stack shrank to two members", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& !grid_snapshot.contains("Pane #3")
|
|
&& grid_snapshot.contains("second-member")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn floating_the_visible_member_promotes_its_neighbor() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('e'));
|
|
|
|
let grid_snapshot = zellij.wait_until("member floated over the stack", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("Pane #3")
|
|
&& grid_snapshot.contains("third-member")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn fullscreen_suspends_the_list_and_restores_it() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('f'));
|
|
zellij.wait_until("stack member fullscreened", |grid_snapshot| {
|
|
grid_snapshot.contains("(FULLSCREEN)")
|
|
&& !grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('f'));
|
|
let grid_snapshot = zellij.wait_until("list restored after fullscreen", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& !grid_snapshot.contains("(FULLSCREEN)")
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
&& grid_snapshot.contains("third-member")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn clicking_a_hidden_entry_swaps_it_in() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_config("mouse_mode true")
|
|
.start();
|
|
build_three_member_stack(&zellij);
|
|
|
|
let grid_snapshot = zellij.wait_until("stack list rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("Pane #1") && grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
let (entry_column, entry_line) = entry_click_coordinates(&grid_snapshot, "Pane #1");
|
|
zellij.send_stdin(&sgr_left_click(entry_column, entry_line));
|
|
|
|
let swapped_in = zellij.wait_until("clicked member swapped in", |grid_snapshot| {
|
|
grid_snapshot.contains("<g> LOCK")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #1"))
|
|
&& grid_snapshot.contains("first-member")
|
|
});
|
|
assert_snapshot!(normalized(&swapped_in));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn alt_clicking_a_hidden_entry_marks_it_without_selecting() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_config("mouse_mode true\nadvanced_mouse_actions true")
|
|
.start();
|
|
build_three_member_stack(&zellij);
|
|
|
|
let grid_snapshot = zellij.wait_until("stack list rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("Pane #1") && grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
let (entry_column, entry_line) = entry_click_coordinates(&grid_snapshot, "Pane #1");
|
|
zellij.send_stdin(&sgr_alt_left_click(entry_column, entry_line));
|
|
|
|
zellij.wait_until(
|
|
"hidden member marked, selection unchanged",
|
|
|grid_snapshot| {
|
|
grid_snapshot.contains("GROUP ACTIONS")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
},
|
|
);
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn synced_input_reaches_hidden_members() {
|
|
let mut zellij = start_zellij();
|
|
let members = build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::ctrl('t'));
|
|
zellij.send_stdin(&keys::key('s'));
|
|
zellij.wait_until("tab marked as syncing", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears() && grid_snapshot.contains("SYNC")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::ENTER);
|
|
zellij.send_stdin(b"synced-input");
|
|
|
|
for member in &members {
|
|
member.wait_for_stdin("synced input reached member", |stdin| {
|
|
stdin.windows(12).any(|window| window == b"synced-input")
|
|
});
|
|
}
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn entries_share_a_uniform_width_sized_by_the_widest_title() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_layout(LayoutInfo::Stringified(
|
|
STACK_WITH_COMMAND_MEMBER_LAYOUT.to_string(),
|
|
))
|
|
.start();
|
|
for _ in 0..2 {
|
|
let member = zellij.expect_pty_spawn();
|
|
member.output(PROMPT);
|
|
}
|
|
let grid_snapshot = zellij.wait_until("mixed-width entries rendered", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("member-command")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #1"))
|
|
&& grid_snapshot.cursor_is_at(col(2).row(4))
|
|
});
|
|
|
|
let lines = grid_snapshot.lines();
|
|
let selected_entry_line = lines
|
|
.iter()
|
|
.find(|line| line.contains(&selected_entry("Pane #1")))
|
|
.expect("selected entry is on a header row");
|
|
let hidden_entry_line = lines
|
|
.iter()
|
|
.find(|line| line.contains("member-command"))
|
|
.expect("hidden entry is on a header row");
|
|
let selected_title_column =
|
|
display_column_of(selected_entry_line, "Pane #1").expect("selected title is on the line");
|
|
let hidden_title_column = display_column_of(hidden_entry_line, "member-command")
|
|
.expect("hidden title is on the line");
|
|
assert_eq!(selected_title_column, hidden_title_column);
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn a_hidden_held_members_exit_code_shows_on_its_entry() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_layout(LayoutInfo::Stringified(
|
|
STACK_WITH_COMMAND_MEMBER_LAYOUT.to_string(),
|
|
))
|
|
.start();
|
|
let first_spawned = zellij.expect_pty_spawn();
|
|
let second_spawned = zellij.expect_pty_spawn();
|
|
let command_member = [&first_spawned, &second_spawned]
|
|
.into_iter()
|
|
.find(|terminal| match terminal.terminal_action() {
|
|
Some(TerminalAction::RunCommand(run_command)) => run_command
|
|
.command
|
|
.to_string_lossy()
|
|
.contains("member-command"),
|
|
_ => false,
|
|
})
|
|
.expect("one member runs a command")
|
|
.clone();
|
|
zellij.wait_until(
|
|
"stack list rendered with the command entry",
|
|
|grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("member-command")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #1"))
|
|
},
|
|
);
|
|
|
|
command_member.exit(Some(2));
|
|
|
|
let grid_snapshot =
|
|
zellij.wait_until("exit code rendered on the hidden entry", |grid_snapshot| {
|
|
grid_snapshot.contains("EXIT CODE: 2")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #1"))
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn the_visible_members_scroll_state_shows_on_its_entry() {
|
|
let mut zellij = start_zellij();
|
|
claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
let second_member = add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #2");
|
|
let mut scrollback = String::new();
|
|
for line_number in 0..40 {
|
|
scrollback.push_str(&format!("line{}\r\n", line_number));
|
|
}
|
|
second_member.output(scrollback.as_bytes());
|
|
zellij.wait_until("scrollback filled", |grid_snapshot| {
|
|
grid_snapshot.contains("line39")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::ctrl('s'));
|
|
zellij.send_stdin(&keys::ctrl('b'));
|
|
|
|
let grid_snapshot = zellij.wait_until("scroll indicator on the entry row", |grid_snapshot| {
|
|
grid_snapshot.contains("SCROLL: ")
|
|
&& !grid_snapshot.contains("SCROLL: 0/")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("PgDn|PgUp")
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn hidden_members_follow_whole_tab_resizes() {
|
|
let mut zellij = start_zellij();
|
|
let [first_member, second_member, third_member] = build_three_member_stack(&zellij);
|
|
|
|
zellij.resize(Size {
|
|
cols: 100,
|
|
rows: 20,
|
|
});
|
|
|
|
let (resized_cols, resized_rows) =
|
|
third_member.wait_for_size("visible member resized", |cols, _rows| cols <= 100);
|
|
for hidden_member in [&first_member, &second_member] {
|
|
hidden_member.wait_for_size("hidden member resized with the tab", |cols, rows| {
|
|
(cols, rows) == (resized_cols, resized_rows)
|
|
});
|
|
}
|
|
zellij.quit();
|
|
}
|
|
|
|
fn build_two_member_stack_with_scrollback_editor() -> (TestSession, FakePtyHandle) {
|
|
let zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_config("scrollback_editor \"fake-editor\"")
|
|
.start();
|
|
let first_member = claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
first_member.output(b"first-member\r\n$ ");
|
|
let second_member = add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #2");
|
|
second_member.output(b"second-member\r\n$ ");
|
|
zellij.wait_until("second member content rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("second-member")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::ctrl('s'));
|
|
zellij.send_stdin(&keys::key('e'));
|
|
let editor = zellij.expect_pty_spawn();
|
|
editor.output(b"editor-open");
|
|
zellij.wait_until(
|
|
"scrollback editor opened on the visible member",
|
|
|grid_snapshot| grid_snapshot.contains("editor-open"),
|
|
);
|
|
(zellij, editor)
|
|
}
|
|
|
|
#[test]
|
|
fn a_scrollback_editor_follows_its_member_through_selection_changes() {
|
|
let (mut zellij, editor) = build_two_member_stack_with_scrollback_editor();
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("editor member swapped out", |grid_snapshot| {
|
|
grid_snapshot.contains("first-member") && !grid_snapshot.contains("editor-open")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('j'));
|
|
zellij.wait_until("editor member swapped back in", |grid_snapshot| {
|
|
grid_snapshot.contains("editor-open")
|
|
});
|
|
|
|
editor.exit(Some(0));
|
|
zellij.wait_until("editor closed back into its member", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("second-member")
|
|
&& !grid_snapshot.contains("editor-open")
|
|
});
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn a_hidden_scrollback_editor_pair_survives_fullscreen() {
|
|
let (mut zellij, editor) = build_two_member_stack_with_scrollback_editor();
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("editor member swapped out", |grid_snapshot| {
|
|
grid_snapshot.contains("first-member") && !grid_snapshot.contains("editor-open")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('f'));
|
|
zellij.wait_until("member fullscreened over the list", |grid_snapshot| {
|
|
grid_snapshot.contains("(FULLSCREEN)")
|
|
});
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('f'));
|
|
zellij.wait_until("list re-formed after fullscreen", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #1")) && grid_snapshot.contains("first-member")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('j'));
|
|
zellij.wait_until("editor member swapped back in", |grid_snapshot| {
|
|
grid_snapshot.contains("editor-open")
|
|
});
|
|
|
|
editor.exit(Some(0));
|
|
zellij.wait_until("editor closed back into its member", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("second-member")
|
|
});
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn closing_a_hidden_editor_member_substitutes_its_parked_pane() {
|
|
let (mut zellij, editor) = build_two_member_stack_with_scrollback_editor();
|
|
|
|
zellij.send_stdin(&keys::alt('k'));
|
|
zellij.wait_until("editor member swapped out", |grid_snapshot| {
|
|
grid_snapshot.contains("first-member") && !grid_snapshot.contains("editor-open")
|
|
});
|
|
|
|
editor.exit(Some(0));
|
|
zellij.wait_until("parked pane substituted on the entry", |grid_snapshot| {
|
|
grid_snapshot.contains("Pane #2") && !grid_snapshot.contains("fake-editor")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::alt('j'));
|
|
zellij.wait_until("substituted member swapped in", |grid_snapshot| {
|
|
grid_snapshot.contains(&selected_entry("Pane #2"))
|
|
&& grid_snapshot.contains("second-member")
|
|
});
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn splitting_a_new_pane_reforms_the_list_around_it() {
|
|
let mut zellij = start_zellij();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.send_stdin(&keys::ctrl('p'));
|
|
zellij.send_stdin(&keys::key('d'));
|
|
let new_pane = zellij.expect_pty_spawn();
|
|
new_pane.output(PROMPT);
|
|
|
|
let grid_snapshot = zellij.wait_until("list re-formed above the new pane", |grid_snapshot| {
|
|
grid_snapshot.contains("<g> LOCK")
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
&& grid_snapshot.contains("Pane #4")
|
|
&& grid_snapshot.cursor_is_at(col(2).row(13))
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn resizing_the_visible_member_keeps_the_list() {
|
|
let mut zellij = start_zellij();
|
|
let first_member = claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
first_member.output(b"first-member\r\n$ ");
|
|
let _lower_terminal = split_down_and_wait_for_prompt(&zellij);
|
|
add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #3");
|
|
let before_resize = zellij.wait_until("list settled before resize", |grid_snapshot| {
|
|
grid_snapshot.contains("<g> LOCK") && grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
});
|
|
let entry_row_before = before_resize
|
|
.lines()
|
|
.iter()
|
|
.position(|line| line.contains(&selected_entry("Pane #3")))
|
|
.expect("selected entry is on a header row");
|
|
|
|
zellij.send_stdin(&keys::ctrl('n'));
|
|
zellij.send_stdin(&keys::key('k'));
|
|
zellij.send_stdin(&keys::ctrl('n'));
|
|
|
|
let grid_snapshot = zellij.wait_until("stack region grew upward", move |grid_snapshot| {
|
|
grid_snapshot.contains("<g> LOCK")
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot
|
|
.lines()
|
|
.iter()
|
|
.position(|line| line.contains(&selected_entry("Pane #3")))
|
|
.map_or(false, |entry_row| entry_row < entry_row_before)
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn moving_the_visible_member_reorders_the_list() {
|
|
let mut zellij = start_zellij();
|
|
let first_member = claim_first_terminal_and_wait_for_prompt(&zellij);
|
|
first_member.output(b"first-member\r\n$ ");
|
|
let _lower_terminal = split_down_and_wait_for_prompt(&zellij);
|
|
let third_member = add_stacked_pane_and_wait_for_selected_entry(&zellij, "Pane #3");
|
|
third_member.output(b"third-member\r\n$ ");
|
|
zellij.wait_until("third member content rendered", |grid_snapshot| {
|
|
grid_snapshot.contains("third-member")
|
|
});
|
|
|
|
zellij.send_stdin(&keys::ctrl('h'));
|
|
zellij.send_stdin(&keys::key('k'));
|
|
zellij.send_stdin(&keys::ctrl('h'));
|
|
|
|
let grid_snapshot =
|
|
zellij.wait_until("visible member moved above its neighbor", |grid_snapshot| {
|
|
let lines = grid_snapshot.lines();
|
|
let row_of = |needle: &str| lines.iter().position(|line| line.contains(needle));
|
|
grid_snapshot.contains("<g> LOCK")
|
|
&& grid_snapshot.contains("third-member")
|
|
&& matches!(
|
|
(row_of(&selected_entry("Pane #3")), row_of("Pane #2")),
|
|
(Some(selected_row), Some(hidden_row)) if selected_row < hidden_row
|
|
)
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|
|
|
|
#[test]
|
|
fn a_stack_list_survives_session_resurrection() {
|
|
let mut zellij = TestRunner::new(TERMINAL_SIZE)
|
|
.with_config("session_serialization true")
|
|
.start();
|
|
build_three_member_stack(&zellij);
|
|
|
|
zellij.save_session();
|
|
zellij.wait_for_serialized_session();
|
|
zellij.quit();
|
|
|
|
zellij.resurrect(TERMINAL_SIZE);
|
|
for _ in 0..3 {
|
|
let member = zellij.expect_pty_spawn();
|
|
member.output(PROMPT);
|
|
}
|
|
|
|
let grid_snapshot = zellij.wait_until("stack list resurrected", |grid_snapshot| {
|
|
grid_snapshot.status_bar_appears()
|
|
&& grid_snapshot.contains("Pane #1")
|
|
&& grid_snapshot.contains("Pane #2")
|
|
&& grid_snapshot.contains(&selected_entry("Pane #3"))
|
|
&& grid_snapshot.cursor_is_at(col(2).row(5))
|
|
});
|
|
assert_snapshot!(normalized(&grid_snapshot));
|
|
zellij.quit();
|
|
}
|