zellij/zellij-server/src/unit/screen_tests.rs
har7an dba5dcbd83
fix (screen): don't crash when first tab doesn't exist (#1648)
* screen: Don't crash when first tab doesn't exist

while trying to attach a new client. Instead, check whether the first
tab does exist and if not, take the first tab index from the tabs
present in the session. If no tabs exist, panic with a better error
message.

* changelog: Add PR #1648

* add test

* fix(tabs): send actual default mode info to new tab

Co-authored-by: Thomas Linford <linford.t@gmail.com>
Co-authored-by: Aram Drevekenin <aram@poor.dev>
2022-08-11 11:10:12 +02:00

568 lines
14 KiB
Rust

use super::{CopyOptions, Screen, ScreenInstruction};
use crate::panes::PaneId;
use crate::{
os_input_output::{AsyncReader, Pid, ServerOsApi},
thread_bus::Bus,
ClientId,
};
use std::convert::TryInto;
use std::path::PathBuf;
use zellij_utils::input::command::TerminalAction;
use zellij_utils::input::layout::LayoutTemplate;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
use std::os::unix::io::RawFd;
use zellij_utils::ipc::{ClientAttributes, PixelDimensions};
use zellij_utils::nix;
use zellij_utils::{
data::{ModeInfo, Palette},
interprocess::local_socket::LocalSocketStream,
ipc::{ClientToServerMsg, ServerToClientMsg},
};
#[derive(Clone)]
struct FakeInputOutput {}
impl ServerOsApi for FakeInputOutput {
fn set_terminal_size_using_fd(&self, _fd: RawFd, _cols: u16, _rows: u16) {
// noop
}
fn spawn_terminal(
&self,
_file_to_open: TerminalAction,
_quit_db: Box<dyn Fn(PaneId) + Send>,
_default_editor: Option<PathBuf>,
) -> Result<(RawFd, RawFd), &'static str> {
unimplemented!()
}
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize, nix::Error> {
unimplemented!()
}
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
unimplemented!()
}
fn write_to_tty_stdin(&self, _fd: RawFd, _buf: &[u8]) -> Result<usize, nix::Error> {
unimplemented!()
}
fn tcdrain(&self, _fd: RawFd) -> Result<(), nix::Error> {
unimplemented!()
}
fn kill(&self, _pid: Pid) -> Result<(), nix::Error> {
unimplemented!()
}
fn force_kill(&self, _pid: Pid) -> Result<(), nix::Error> {
unimplemented!()
}
fn box_clone(&self) -> Box<dyn ServerOsApi> {
Box::new((*self).clone())
}
fn send_to_client(&self, _client_id: ClientId, _msg: ServerToClientMsg) {
unimplemented!()
}
fn new_client(
&mut self,
_client_id: ClientId,
_stream: LocalSocketStream,
) -> IpcReceiverWithContext<ClientToServerMsg> {
unimplemented!()
}
fn remove_client(&mut self, _client_id: ClientId) {
unimplemented!()
}
fn load_palette(&self) -> Palette {
unimplemented!()
}
fn get_cwd(&self, _pid: Pid) -> Option<PathBuf> {
unimplemented!()
}
fn write_to_file(&mut self, _: String, _: Option<String>) {
unimplemented!()
}
}
fn create_new_screen(size: Size) -> Screen {
let mut bus: Bus<ScreenInstruction> = Bus::empty();
let fake_os_input = FakeInputOutput {};
bus.os_input = Some(Box::new(fake_os_input));
let client_attributes = ClientAttributes {
size,
..Default::default()
};
let max_panes = None;
let mode_info = ModeInfo::default();
let draw_pane_frames = false;
let session_is_mirrored = true;
let copy_options = CopyOptions::default();
Screen::new(
bus,
&client_attributes,
max_panes,
mode_info,
draw_pane_frames,
session_is_mirrored,
copy_options,
)
}
fn new_tab(screen: &mut Screen, pid: i32) {
let client_id = 1;
screen.new_tab(
LayoutTemplate::default().try_into().unwrap(),
vec![pid],
client_id,
);
}
#[test]
fn open_new_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
assert_eq!(screen.tabs.len(), 2, "Screen now has two tabs");
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab switched to new tab"
);
}
#[test]
pub fn switch_to_prev_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.switch_tab_prev(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
0,
"Active tab switched to previous tab"
);
}
#[test]
pub fn switch_to_next_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.switch_tab_prev(1);
screen.switch_tab_next(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab switched to next tab"
);
}
#[test]
pub fn close_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.close_tab(1);
assert_eq!(screen.tabs.len(), 1, "Only one tab left");
assert_eq!(
screen.get_active_tab(1).unwrap().position,
0,
"Active tab switched to previous tab"
);
}
#[test]
pub fn close_the_middle_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
screen.switch_tab_prev(1);
screen.close_tab(1);
assert_eq!(screen.tabs.len(), 2, "Two tabs left");
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab switched to previous tab"
);
}
#[test]
fn move_focus_left_at_left_screen_edge_changes_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
screen.switch_tab_prev(1);
screen.move_focus_left_or_previous_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
0,
"Active tab switched to previous"
);
}
#[test]
fn move_focus_right_at_right_screen_edge_changes_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
screen.switch_tab_prev(1);
screen.move_focus_right_or_next_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
2,
"Active tab switched to next"
);
}
#[test]
pub fn toggle_to_previous_tab_simple() {
let position_and_size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(position_and_size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
screen.go_to_tab(1, 1);
screen.go_to_tab(2, 1);
screen.toggle_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
0,
"Active tab toggler to previous tab"
);
screen.toggle_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab toggler to previous tab"
);
}
#[test]
pub fn toggle_to_previous_tab_create_tabs_only() {
let position_and_size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(position_and_size);
new_tab(&mut screen, 1);
new_tab(&mut screen, 2);
new_tab(&mut screen, 3);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1],
"Tab history is invalid"
);
screen.toggle_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab toggler to previous tab"
);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 2],
"Tab history is invalid"
);
screen.toggle_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
2,
"Active tab toggler to previous tab"
);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1],
"Tab history is invalid"
);
screen.toggle_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab toggler to previous tab"
);
}
#[test]
pub fn toggle_to_previous_tab_delete() {
let position_and_size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(position_and_size);
new_tab(&mut screen, 1); // 0
new_tab(&mut screen, 2); // 1
new_tab(&mut screen, 3); // 2
new_tab(&mut screen, 4); // 3
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1, 2],
"Tab history is invalid"
);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
3,
"Active tab toggler to previous tab"
);
screen.toggle_tab(1);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1, 3],
"Tab history is invalid"
);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
2,
"Active tab toggler to previous tab"
);
screen.toggle_tab(1);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1, 2],
"Tab history is invalid"
);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
3,
"Active tab toggler to previous tab"
);
screen.switch_tab_prev(1);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1, 3],
"Tab history is invalid"
);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
2,
"Active tab toggler to previous tab"
);
screen.switch_tab_prev(1);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 3, 2],
"Tab history is invalid"
);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab toggler to previous tab"
);
screen.close_tab(1);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 3],
"Tab history is invalid"
);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab toggler to previous tab"
);
screen.toggle_tab(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
2,
"Active tab toggler to previous tab"
);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 2],
"Tab history is invalid"
);
}
#[test]
fn switch_to_tab_with_fullscreen() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab.new_pane(PaneId::Terminal(2), Some(1));
active_tab.toggle_active_pane_fullscreen(1);
}
new_tab(&mut screen, 2);
screen.switch_tab_prev(1);
assert_eq!(
screen.get_active_tab(1).unwrap().position,
0,
"Active tab switched to previous"
);
assert_eq!(
screen
.get_active_tab(1)
.unwrap()
.get_active_pane_id(1)
.unwrap(),
PaneId::Terminal(2),
"Active pane is still the fullscreen pane"
);
}
#[test]
fn update_screen_pixel_dimensions() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
let initial_pixel_dimensions = screen.pixel_dimensions;
screen.update_pixel_dimensions(PixelDimensions {
character_cell_size: Some(SizeInPixels {
height: 10,
width: 5,
}),
text_area_size: None,
});
let pixel_dimensions_after_first_update = screen.pixel_dimensions;
screen.update_pixel_dimensions(PixelDimensions {
character_cell_size: None,
text_area_size: Some(SizeInPixels {
height: 100,
width: 50,
}),
});
let pixel_dimensions_after_second_update = screen.pixel_dimensions;
screen.update_pixel_dimensions(PixelDimensions {
character_cell_size: None,
text_area_size: None,
});
let pixel_dimensions_after_third_update = screen.pixel_dimensions;
assert_eq!(
initial_pixel_dimensions,
PixelDimensions {
character_cell_size: None,
text_area_size: None
},
"Initial pixel dimensions empty"
);
assert_eq!(
pixel_dimensions_after_first_update,
PixelDimensions {
character_cell_size: Some(SizeInPixels {
height: 10,
width: 5
}),
text_area_size: None
},
"character_cell_size updated properly",
);
assert_eq!(
pixel_dimensions_after_second_update,
PixelDimensions {
character_cell_size: Some(SizeInPixels {
height: 10,
width: 5
}),
text_area_size: Some(SizeInPixels {
height: 100,
width: 50,
}),
},
"text_area_size updated properly without overriding character_cell_size",
);
assert_eq!(
pixel_dimensions_after_third_update,
PixelDimensions {
character_cell_size: Some(SizeInPixels {
height: 10,
width: 5
}),
text_area_size: Some(SizeInPixels {
height: 100,
width: 50,
}),
},
"empty update does not delete existing data",
);
}
#[test]
fn attach_after_first_tab_closed() {
// ensure https://github.com/zellij-org/zellij/issues/1645 is fixed
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1);
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab.new_pane(PaneId::Terminal(2), Some(1));
active_tab.toggle_active_pane_fullscreen(1);
}
new_tab(&mut screen, 2);
screen.close_tab_at_index(0);
screen.remove_client(1);
screen.add_client(1);
}