zellij/zellij-server/src/unit/screen_tests.rs
Aram Drevekenin d780bd9105
feat(plugins): introduce 'pipes', allowing users to pipe data to and control plugins from the command line (#3066)
* prototype - working with message from the cli

* prototype - pipe from the CLI to plugins

* prototype - pipe from the CLI to plugins and back again

* prototype - working with better cli interface

* prototype - working after removing unused stuff

* prototype - working with launching plugin if it is not launched, also fixed event ordering

* refactor: change message to cli-message

* prototype - allow plugins to send messages to each other

* fix: allow cli messages to send plugin parameters (and implement backpressure)

* fix: use input_pipe_id to identify cli pipes instead of their message name

* fix: come cleanups and add skip_cache parameter

* fix: pipe/client-server communication robustness

* fix: leaking messages between plugins while loading

* feat: allow plugins to specify how a new plugin instance is launched when sending messages

* fix: add permissions

* refactor: adjust cli api

* fix: improve cli plugin loading error messages

* docs: cli pipe

* fix: take plugin configuration into account when messaging between plugins

* refactor: pipe message protobuf interface

* refactor: update(event) -> pipe

* refactor - rename CliMessage to CliPipe

* fix: add is_private to pipes and change some naming

* refactor - cli client

* refactor: various cleanups

* style(fmt): rustfmt

* fix(pipes): backpressure across multiple plugins

* style: some cleanups

* style(fmt): rustfmt

* style: fix merge conflict mistake

* style(wording): clarify pipe permission
2024-01-17 12:10:49 +01:00

3131 lines
120 KiB
Rust

use super::{screen_thread_main, CopyOptions, Screen, ScreenInstruction};
use crate::panes::PaneId;
use crate::{
channels::SenderWithContext,
os_input_output::{AsyncReader, Pid, ServerOsApi},
route::route_action,
thread_bus::Bus,
ClientId, ServerInstruction, SessionMetaData, ThreadSenders,
};
use insta::assert_snapshot;
use std::path::PathBuf;
use zellij_utils::cli::CliAction;
use zellij_utils::data::{Event, Resize, Style};
use zellij_utils::errors::{prelude::*, ErrorContext};
use zellij_utils::input::actions::Action;
use zellij_utils::input::command::{RunCommand, TerminalAction};
use zellij_utils::input::layout::{
FloatingPaneLayout, Layout, Run, RunPlugin, RunPluginLocation, SplitDirection, TiledPaneLayout,
};
use zellij_utils::input::options::Options;
use zellij_utils::ipc::IpcReceiverWithContext;
use zellij_utils::pane_size::{Size, SizeInPixels};
use crate::background_jobs::BackgroundJob;
use crate::pty_writer::PtyWriteInstruction;
use std::env::set_var;
use std::os::unix::io::RawFd;
use std::sync::{Arc, Mutex};
use crate::{plugins::PluginInstruction, pty::PtyInstruction};
use zellij_utils::ipc::PixelDimensions;
use zellij_utils::{
channels::{self, ChannelWithContext, Receiver},
data::{Direction, InputMode, ModeInfo, Palette, PluginCapabilities},
interprocess::local_socket::LocalSocketStream,
ipc::{ClientAttributes, ClientToServerMsg, ServerToClientMsg},
};
use crate::panes::grid::Grid;
use crate::panes::link_handler::LinkHandler;
use crate::panes::sixel::SixelImageStore;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use zellij_utils::vte;
fn take_snapshot_and_cursor_coordinates(
ansi_instructions: &str,
grid: &mut Grid,
) -> (Option<(usize, usize)>, String) {
let mut vte_parser = vte::Parser::new();
for &byte in ansi_instructions.as_bytes() {
vte_parser.advance(grid, byte);
}
(grid.cursor_coordinates(), format!("{:?}", grid))
}
fn take_snapshots_and_cursor_coordinates_from_render_events<'a>(
all_events: impl Iterator<Item = &'a ServerInstruction>,
screen_size: Size,
) -> Vec<(Option<(usize, usize)>, String)> {
let sixel_image_store = Rc::new(RefCell::new(SixelImageStore::default()));
let terminal_emulator_color_codes = Rc::new(RefCell::new(HashMap::new()));
let character_cell_size = Rc::new(RefCell::new(Some(SizeInPixels {
width: 8,
height: 21,
})));
let debug = false;
let arrow_fonts = true;
let styled_underlines = true;
let mut grid = Grid::new(
screen_size.rows,
screen_size.cols,
Rc::new(RefCell::new(Palette::default())),
terminal_emulator_color_codes,
Rc::new(RefCell::new(LinkHandler::new())),
character_cell_size,
sixel_image_store,
Style::default(),
debug,
arrow_fonts,
styled_underlines,
);
let snapshots: Vec<(Option<(usize, usize)>, String)> = all_events
.filter_map(|server_instruction| {
match server_instruction {
ServerInstruction::Render(output) => {
if let Some(output) = output {
// note this only takes a snapshot of the first client!
let raw_snapshot = output.get(&1).unwrap();
let snapshot =
take_snapshot_and_cursor_coordinates(raw_snapshot, &mut grid);
Some(snapshot)
} else {
None
}
},
_ => None,
}
})
.collect();
snapshots
}
fn send_cli_action_to_server(
session_metadata: &SessionMetaData,
cli_action: CliAction,
client_id: ClientId,
) {
let get_current_dir = || PathBuf::from(".");
let actions = Action::actions_from_cli(cli_action, Box::new(get_current_dir), None).unwrap();
let senders = session_metadata.senders.clone();
let capabilities = PluginCapabilities::default();
let client_attributes = ClientAttributes::default();
let default_shell = None;
let default_layout = Box::new(Layout::default());
for action in actions {
route_action(
action,
client_id,
None,
senders.clone(),
capabilities,
client_attributes.clone(),
default_shell.clone(),
default_layout.clone(),
None,
)
.unwrap();
}
}
#[derive(Clone, Default)]
struct FakeInputOutput {
fake_filesystem: Arc<Mutex<HashMap<String, String>>>,
server_to_client_messages: Arc<Mutex<HashMap<ClientId, Vec<ServerToClientMsg>>>>,
}
impl ServerOsApi for FakeInputOutput {
fn set_terminal_size_using_terminal_id(
&self,
_terminal_id: u32,
_cols: u16,
_rows: u16,
_width_in_pixels: Option<u16>,
_height_in_pixels: Option<u16>,
) -> Result<()> {
// noop
Ok(())
}
fn spawn_terminal(
&self,
_file_to_open: TerminalAction,
_quit_db: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>,
_default_editor: Option<PathBuf>,
) -> Result<(u32, RawFd, RawFd)> {
unimplemented!()
}
fn read_from_tty_stdout(&self, _fd: RawFd, _buf: &mut [u8]) -> Result<usize> {
unimplemented!()
}
fn async_file_reader(&self, _fd: RawFd) -> Box<dyn AsyncReader> {
unimplemented!()
}
fn write_to_tty_stdin(&self, _id: u32, _buf: &[u8]) -> Result<usize> {
unimplemented!()
}
fn tcdrain(&self, _id: u32) -> Result<()> {
unimplemented!()
}
fn kill(&self, _pid: Pid) -> Result<()> {
unimplemented!()
}
fn force_kill(&self, _pid: Pid) -> Result<()> {
unimplemented!()
}
fn box_clone(&self) -> Box<dyn ServerOsApi> {
Box::new((*self).clone())
}
fn send_to_client(&self, client_id: ClientId, msg: ServerToClientMsg) -> Result<()> {
self.server_to_client_messages
.lock()
.unwrap()
.entry(client_id)
.or_insert_with(Vec::new)
.push(msg);
Ok(())
}
fn new_client(
&mut self,
_client_id: ClientId,
_stream: LocalSocketStream,
) -> Result<IpcReceiverWithContext<ClientToServerMsg>> {
unimplemented!()
}
fn remove_client(&mut self, _client_id: ClientId) -> Result<()> {
unimplemented!()
}
fn load_palette(&self) -> Palette {
unimplemented!()
}
fn get_cwd(&self, _pid: Pid) -> Option<PathBuf> {
unimplemented!()
}
fn write_to_file(&mut self, contents: String, filename: Option<String>) -> Result<()> {
if let Some(filename) = filename {
self.fake_filesystem
.lock()
.unwrap()
.insert(filename, contents);
}
Ok(())
}
fn re_run_command_in_terminal(
&self,
_terminal_id: u32,
_run_command: RunCommand,
_quit_cb: Box<dyn Fn(PaneId, Option<i32>, RunCommand) + Send>, // u32 is the exit status
) -> Result<(RawFd, RawFd)> {
unimplemented!()
}
fn clear_terminal_id(&self, _terminal_id: u32) -> Result<()> {
unimplemented!()
}
}
fn create_new_screen(size: Size) -> Screen {
let mut bus: Bus<ScreenInstruction> = Bus::empty();
let fake_os_input = FakeInputOutput::default();
bus.os_input = Some(Box::new(fake_os_input));
let client_attributes = ClientAttributes {
size,
..Default::default()
};
let max_panes = None;
let mut mode_info = ModeInfo::default();
mode_info.session_name = Some("zellij-test".into());
let draw_pane_frames = false;
let auto_layout = true;
let session_is_mirrored = true;
let copy_options = CopyOptions::default();
let default_layout = Box::new(Layout::default());
let default_shell = None;
let session_serialization = true;
let serialize_pane_viewport = false;
let scrollback_lines_to_serialize = None;
let debug = false;
let styled_underlines = true;
let arrow_fonts = true;
let screen = Screen::new(
bus,
&client_attributes,
max_panes,
mode_info,
draw_pane_frames,
auto_layout,
session_is_mirrored,
copy_options,
debug,
default_layout,
default_shell,
session_serialization,
serialize_pane_viewport,
scrollback_lines_to_serialize,
styled_underlines,
arrow_fonts,
);
screen
}
struct MockScreen {
pub main_client_id: u16,
pub pty_receiver: Option<Receiver<(PtyInstruction, ErrorContext)>>,
pub pty_writer_receiver: Option<Receiver<(PtyWriteInstruction, ErrorContext)>>,
pub background_jobs_receiver: Option<Receiver<(BackgroundJob, ErrorContext)>>,
pub screen_receiver: Option<Receiver<(ScreenInstruction, ErrorContext)>>,
pub server_receiver: Option<Receiver<(ServerInstruction, ErrorContext)>>,
pub plugin_receiver: Option<Receiver<(PluginInstruction, ErrorContext)>>,
pub to_screen: SenderWithContext<ScreenInstruction>,
pub to_pty: SenderWithContext<PtyInstruction>,
pub to_plugin: SenderWithContext<PluginInstruction>,
pub to_server: SenderWithContext<ServerInstruction>,
pub to_pty_writer: SenderWithContext<PtyWriteInstruction>,
pub to_background_jobs: SenderWithContext<BackgroundJob>,
pub os_input: FakeInputOutput,
pub client_attributes: ClientAttributes,
pub config_options: Options,
pub session_metadata: SessionMetaData,
last_opened_tab_index: Option<usize>,
}
impl MockScreen {
pub fn run(
&mut self,
initial_layout: Option<TiledPaneLayout>,
initial_floating_panes_layout: Vec<FloatingPaneLayout>,
) -> std::thread::JoinHandle<()> {
let config_options = self.config_options.clone();
let client_attributes = self.client_attributes.clone();
let screen_bus = Bus::new(
vec![self.screen_receiver.take().unwrap()],
None,
Some(&self.to_pty.clone()),
Some(&self.to_plugin.clone()),
Some(&self.to_server.clone()),
Some(&self.to_pty_writer.clone()),
Some(&self.to_background_jobs.clone()),
Some(Box::new(self.os_input.clone())),
)
.should_silently_fail();
let debug = false;
let screen_thread = std::thread::Builder::new()
.name("screen_thread".to_string())
.spawn(move || {
set_var("ZELLIJ_SESSION_NAME", "zellij-test");
screen_thread_main(
screen_bus,
None,
client_attributes,
Box::new(config_options),
debug,
Box::new(Layout::default()),
)
.expect("TEST")
})
.unwrap();
let pane_layout = initial_layout.unwrap_or_default();
let pane_count = pane_layout.extract_run_instructions().len();
let floating_pane_count = initial_floating_panes_layout.len();
let mut pane_ids = vec![];
let mut floating_pane_ids = vec![];
let mut plugin_ids = HashMap::new();
plugin_ids.insert(
(
RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
Default::default(),
),
vec![1],
);
for i in 0..pane_count {
pane_ids.push((i as u32, None));
}
for i in 0..floating_pane_count {
floating_pane_ids.push((i as u32, None));
}
let default_shell = None;
let tab_name = None;
let tab_index = self.last_opened_tab_index.map(|l| l + 1).unwrap_or(0);
let _ = self.to_screen.send(ScreenInstruction::NewTab(
None,
default_shell,
Some(pane_layout.clone()),
initial_floating_panes_layout.clone(),
// vec![], // floating_panes_layout
tab_name,
(vec![], vec![]), // swap layouts
self.main_client_id,
));
let _ = self.to_screen.send(ScreenInstruction::ApplyLayout(
pane_layout,
initial_floating_panes_layout,
// vec![], // floating panes layout
pane_ids,
floating_pane_ids,
// vec![], // floating pane ids
plugin_ids,
tab_index,
self.main_client_id,
));
self.last_opened_tab_index = Some(tab_index);
screen_thread
}
pub fn new_tab(&mut self, tab_layout: TiledPaneLayout) {
let pane_count = tab_layout.extract_run_instructions().len();
let mut pane_ids = vec![];
let plugin_ids = HashMap::new();
let default_shell = None;
let tab_name = None;
let tab_index = self.last_opened_tab_index.map(|l| l + 1).unwrap_or(0);
for i in 0..pane_count {
pane_ids.push((i as u32, None));
}
let _ = self.to_screen.send(ScreenInstruction::NewTab(
None,
default_shell,
Some(tab_layout.clone()),
vec![], // floating_panes_layout
tab_name,
(vec![], vec![]), // swap layouts
self.main_client_id,
));
let _ = self.to_screen.send(ScreenInstruction::ApplyLayout(
tab_layout,
vec![], // floating_panes_layout
pane_ids,
vec![], // floating panes ids
plugin_ids,
0,
self.main_client_id,
));
self.last_opened_tab_index = Some(tab_index);
}
pub fn teardown(&mut self, threads: Vec<std::thread::JoinHandle<()>>) {
let _ = self.to_pty.send(PtyInstruction::Exit);
let _ = self.to_pty_writer.send(PtyWriteInstruction::Exit);
let _ = self.to_screen.send(ScreenInstruction::Exit);
let _ = self.to_server.send(ServerInstruction::KillSession);
let _ = self.to_plugin.send(PluginInstruction::Exit);
for thread in threads {
let _ = thread.join();
}
}
pub fn clone_session_metadata(&self) -> SessionMetaData {
// hack that only clones the clonable parts of SessionMetaData
let layout = Box::new(Layout::default()); // this is not actually correct!!
SessionMetaData {
senders: self.session_metadata.senders.clone(),
capabilities: self.session_metadata.capabilities.clone(),
client_attributes: self.session_metadata.client_attributes.clone(),
default_shell: self.session_metadata.default_shell.clone(),
screen_thread: None,
pty_thread: None,
plugin_thread: None,
pty_writer_thread: None,
background_jobs_thread: None,
layout,
}
}
}
impl MockScreen {
pub fn new(size: Size) -> Self {
let (to_server, server_receiver): ChannelWithContext<ServerInstruction> =
channels::bounded(50);
let to_server = SenderWithContext::new(to_server);
let (to_screen, screen_receiver): ChannelWithContext<ScreenInstruction> =
channels::unbounded();
let to_screen = SenderWithContext::new(to_screen);
let (to_plugin, plugin_receiver): ChannelWithContext<PluginInstruction> =
channels::unbounded();
let to_plugin = SenderWithContext::new(to_plugin);
let (to_pty, pty_receiver): ChannelWithContext<PtyInstruction> = channels::unbounded();
let to_pty = SenderWithContext::new(to_pty);
let (to_pty_writer, pty_writer_receiver): ChannelWithContext<PtyWriteInstruction> =
channels::unbounded();
let to_pty_writer = SenderWithContext::new(to_pty_writer);
let (to_background_jobs, background_jobs_receiver): ChannelWithContext<BackgroundJob> =
channels::unbounded();
let to_background_jobs = SenderWithContext::new(to_background_jobs);
let client_attributes = ClientAttributes {
size,
..Default::default()
};
let capabilities = PluginCapabilities {
arrow_fonts: Default::default(),
};
let layout = Box::new(Layout::default()); // this is not actually correct!!
let session_metadata = SessionMetaData {
senders: ThreadSenders {
to_screen: Some(to_screen.clone()),
to_pty: Some(to_pty.clone()),
to_plugin: Some(to_plugin.clone()),
to_pty_writer: Some(to_pty_writer.clone()),
to_background_jobs: Some(to_background_jobs.clone()),
to_server: Some(to_server.clone()),
should_silently_fail: true,
},
capabilities,
default_shell: None,
client_attributes: client_attributes.clone(),
screen_thread: None,
pty_thread: None,
plugin_thread: None,
pty_writer_thread: None,
background_jobs_thread: None,
layout,
};
let os_input = FakeInputOutput::default();
let config_options = Options::default();
let main_client_id = 1;
MockScreen {
main_client_id,
pty_receiver: Some(pty_receiver),
pty_writer_receiver: Some(pty_writer_receiver),
background_jobs_receiver: Some(background_jobs_receiver),
screen_receiver: Some(screen_receiver),
server_receiver: Some(server_receiver),
plugin_receiver: Some(plugin_receiver),
to_screen,
to_pty,
to_plugin,
to_server,
to_pty_writer,
to_background_jobs,
os_input,
client_attributes,
config_options,
session_metadata,
last_opened_tab_index: None,
}
}
}
macro_rules! log_actions_in_thread {
( $arc_mutex_log:expr, $exit_event:path, $receiver:expr ) => {
std::thread::Builder::new()
.name("pty_writer_thread".to_string())
.spawn({
let log = $arc_mutex_log.clone();
move || loop {
let (event, _err_ctx) = $receiver
.recv()
.expect("failed to receive event on channel");
match event {
$exit_event => {
log.lock().unwrap().push(event);
break;
},
_ => {
log.lock().unwrap().push(event);
},
}
}
})
.unwrap()
};
}
fn new_tab(screen: &mut Screen, pid: u32, tab_index: usize) {
let client_id = 1;
let new_terminal_ids = vec![(pid, None)];
let new_plugin_ids = HashMap::new();
screen
.new_tab(tab_index, (vec![], vec![]), None, client_id)
.expect("TEST");
screen
.apply_layout(
TiledPaneLayout::default(),
vec![], // floating panes layout
new_terminal_ids,
vec![], // new floating terminal ids
new_plugin_ids,
tab_index,
client_id,
)
.expect("TEST");
}
#[test]
fn open_new_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1, 0);
new_tab(&mut screen, 2, 1);
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, 1);
new_tab(&mut screen, 2, 2);
screen.switch_tab_prev(None, true, 1).expect("TEST");
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, 1);
new_tab(&mut screen, 2, 2);
screen.switch_tab_prev(None, true, 1).expect("TEST");
screen.switch_tab_next(None, true, 1).expect("TEST");
assert_eq!(
screen.get_active_tab(1).unwrap().position,
1,
"Active tab switched to next tab"
);
}
#[test]
pub fn switch_to_tab_name() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
assert_eq!(
screen
.switch_active_tab_name("Tab #1".to_string(), 1)
.expect("TEST"),
false,
"Active tab switched to tab by name"
);
assert_eq!(
screen
.switch_active_tab_name("Tab #2".to_string(), 1)
.expect("TEST"),
true,
"Active tab switched to tab by name"
);
assert_eq!(
screen
.switch_active_tab_name("Tab #3".to_string(), 1)
.expect("TEST"),
true,
"Active tab switched to tab by name"
);
}
#[test]
pub fn close_tab() {
let size = Size {
cols: 121,
rows: 20,
};
let mut screen = create_new_screen(size);
new_tab(&mut screen, 1, 1);
new_tab(&mut screen, 2, 2);
screen.close_tab(1).expect("TEST");
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, 1);
new_tab(&mut screen, 2, 2);
new_tab(&mut screen, 3, 3);
screen.switch_tab_prev(None, true, 1).expect("TEST");
screen.close_tab(1).expect("TEST");
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, 1);
new_tab(&mut screen, 2, 2);
new_tab(&mut screen, 3, 3);
screen.switch_tab_prev(None, true, 1).expect("TEST");
screen.move_focus_left_or_previous_tab(1).expect("TEST");
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, 1);
new_tab(&mut screen, 2, 2);
new_tab(&mut screen, 3, 3);
screen.switch_tab_prev(None, true, 1).expect("TEST");
screen.move_focus_right_or_next_tab(1).expect("TEST");
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, 1);
new_tab(&mut screen, 2, 2);
screen.go_to_tab(1, 1).expect("TEST");
screen.go_to_tab(2, 1).expect("TEST");
screen.toggle_tab(1).expect("TEST");
assert_eq!(
screen.get_active_tab(1).unwrap().position,
0,
"Active tab toggler to previous tab"
);
screen.toggle_tab(1).expect("TEST");
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, 0);
new_tab(&mut screen, 2, 1);
new_tab(&mut screen, 3, 2);
assert_eq!(
screen.tab_history.get(&1).unwrap(),
&[0, 1],
"Tab history is invalid"
);
screen.toggle_tab(1).expect("TEST");
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).expect("TEST");
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).expect("TEST");
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).expect("TEST");
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).expect("TEST");
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(None, true, 1).expect("TEST");
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(None, true, 1).expect("TEST");
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).expect("TEST");
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).expect("TEST");
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, 1);
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab
.new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
}
new_tab(&mut screen, 2, 2);
screen.switch_tab_prev(None, true, 1).expect("TEST");
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, 0);
{
let active_tab = screen.get_active_tab_mut(1).unwrap();
active_tab
.new_pane(PaneId::Terminal(2), None, None, None, Some(1))
.unwrap();
active_tab.toggle_active_pane_fullscreen(1);
}
new_tab(&mut screen, 2, 1);
screen.close_tab_at_index(0).expect("TEST");
screen.remove_client(1).expect("TEST");
screen.add_client(1).expect("TEST");
}
// Following are tests for sending CLI actions
// these tests are only partially relevant to Screen
// and are included here for two reasons:
// 1. The best way to "integration test" these is combining the "screen_thread_main" and
// "route_action" functions and mocking everything around them
// 2. These inadvertently also test many parts of Screen that are not tested elsewhere
#[test]
pub fn send_cli_write_chars_action_to_screen() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_writer_receiver = mock_screen.pty_writer_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(None, vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_writer_thread = log_actions_in_thread!(
received_pty_instructions,
PtyWriteInstruction::Exit,
pty_writer_receiver
);
let cli_action = CliAction::WriteChars {
chars: "input from the cli".into(),
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_writer_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_write_action_to_screen() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_writer_receiver = mock_screen.pty_writer_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(None, vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_writer_thread = log_actions_in_thread!(
received_pty_instructions,
PtyWriteInstruction::Exit,
pty_writer_receiver
);
let cli_action = CliAction::Write {
bytes: vec![102, 111, 111],
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_writer_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_resize_action_to_screen() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let pty_writer_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let resize_cli_action = CliAction::Resize {
resize: Resize::Increase,
direction: Some(Direction::Left),
};
send_cli_action_to_server(&session_metadata, resize_cli_action, client_id);
mock_screen.teardown(vec![pty_writer_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_focus_next_pane_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let focus_next_pane_action = CliAction::FocusNextPane;
send_cli_action_to_server(&session_metadata, focus_next_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (cursor_coordinates, _snapshot) in snapshots {
// here we assert he cursor_coordinates to let us know if we switched the pane focus
assert_snapshot!(format!("{:?}", cursor_coordinates));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_focus_previous_pane_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let focus_next_pane_action = CliAction::FocusPreviousPane;
send_cli_action_to_server(&session_metadata, focus_next_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (cursor_coordinates, _snapshot) in snapshots {
// here we assert he cursor_coordinates to let us know if we switched the pane focus
assert_snapshot!(format!("{:?}", cursor_coordinates));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_move_focus_pane_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let move_focus_action = CliAction::MoveFocus {
direction: Direction::Right,
};
send_cli_action_to_server(&session_metadata, move_focus_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (cursor_coordinates, _snapshot) in snapshots {
// here we assert he cursor_coordinates to let us know if we switched the pane focus
assert_snapshot!(format!("{:?}", cursor_coordinates));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_move_focus_or_tab_pane_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let move_focus_action = CliAction::MoveFocusOrTab {
direction: Direction::Right,
};
send_cli_action_to_server(&session_metadata, move_focus_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (cursor_coordinates, _snapshot) in snapshots {
// here we assert he cursor_coordinates to let us know if we switched the pane focus
assert_snapshot!(format!("{:?}", cursor_coordinates));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_move_pane_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let cli_action = CliAction::MovePane {
direction: Some(Direction::Right),
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_dump_screen_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let cli_action = CliAction::DumpScreen {
path: PathBuf::from("/tmp/foo"),
full: true,
};
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
"fill pane up with something".as_bytes().to_vec(),
));
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
assert_snapshot!(format!(
"{:?}",
*mock_screen.os_input.fake_filesystem.lock().unwrap()
));
}
#[test]
pub fn send_cli_edit_scrollback_action() {
let size = Size { cols: 80, rows: 20 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_action = CliAction::EditScrollback;
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
"fill pane up with something".as_bytes().to_vec(),
));
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![pty_thread, screen_thread]);
let dumped_file_name = mock_screen
.os_input
.fake_filesystem
.lock()
.unwrap()
.keys()
.next()
.unwrap()
.clone();
let mut found_instruction = false;
for instruction in received_pty_instructions.lock().unwrap().iter() {
if let PtyInstruction::OpenInPlaceEditor(scrollback_contents_file, terminal_id, client_id) =
instruction
{
assert_eq!(scrollback_contents_file, &PathBuf::from(&dumped_file_name));
assert_eq!(terminal_id, &Some(1));
assert_eq!(client_id, &1);
found_instruction = true;
}
}
assert!(found_instruction);
}
#[test]
pub fn send_cli_scroll_up_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let cli_action = CliAction::ScrollUp;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
// we send two actions here because only the last line in the pane is empty, so one action
// won't show in a render
send_cli_action_to_server(&session_metadata, cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, cli_action.clone(), client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_scroll_down_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let scroll_up_cli_action = CliAction::ScrollUp;
let scroll_down_cli_action = CliAction::ScrollDown;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
// scroll up some
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
// scroll down some
send_cli_action_to_server(&session_metadata, scroll_down_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_down_cli_action.clone(), client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_scroll_to_bottom_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let scroll_up_cli_action = CliAction::ScrollUp;
let scroll_to_bottom_action = CliAction::ScrollToBottom;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
// scroll up some
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, scroll_up_cli_action.clone(), client_id);
// scroll to bottom
send_cli_action_to_server(
&session_metadata,
scroll_to_bottom_action.clone(),
client_id,
);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_scroll_to_top_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let scroll_to_top_action = CliAction::ScrollToTop;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
// scroll to top
send_cli_action_to_server(&session_metadata, scroll_to_top_action.clone(), client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_page_scroll_up_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let page_scroll_up_action = CliAction::PageScrollUp;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
send_cli_action_to_server(&session_metadata, page_scroll_up_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_page_scroll_down_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let page_scroll_up_action = CliAction::PageScrollUp;
let page_scroll_down_action = CliAction::PageScrollDown;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
// scroll up some
send_cli_action_to_server(&session_metadata, page_scroll_up_action.clone(), client_id);
send_cli_action_to_server(&session_metadata, page_scroll_up_action.clone(), client_id);
// scroll down
send_cli_action_to_server(&session_metadata, page_scroll_down_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_half_page_scroll_up_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let half_page_scroll_up_action = CliAction::HalfPageScrollUp;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
send_cli_action_to_server(&session_metadata, half_page_scroll_up_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_half_page_scroll_down_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let half_page_scroll_up_action = CliAction::HalfPageScrollUp;
let half_page_scroll_down_action = CliAction::HalfPageScrollDown;
let mut pane_contents = String::new();
for i in 0..20 {
pane_contents.push_str(&format!("fill pane up with something {}\n\r", i));
}
let _ = mock_screen.to_screen.send(ScreenInstruction::PtyBytes(
0,
pane_contents.as_bytes().to_vec(),
));
std::thread::sleep(std::time::Duration::from_millis(100));
// scroll up some
send_cli_action_to_server(
&session_metadata,
half_page_scroll_up_action.clone(),
client_id,
);
send_cli_action_to_server(
&session_metadata,
half_page_scroll_up_action.clone(),
client_id,
);
// scroll down
send_cli_action_to_server(&session_metadata, half_page_scroll_down_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_toggle_full_screen_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let toggle_full_screen_action = CliAction::ToggleFullscreen;
send_cli_action_to_server(&session_metadata, toggle_full_screen_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_toggle_pane_frames_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let toggle_pane_frames_action = CliAction::TogglePaneFrames;
send_cli_action_to_server(&session_metadata, toggle_pane_frames_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_toggle_active_tab_sync_action() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_writer_receiver = mock_screen.pty_writer_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_writer_thread = log_actions_in_thread!(
received_pty_instructions,
PtyWriteInstruction::Exit,
pty_writer_receiver
);
let cli_toggle_active_tab_sync_action = CliAction::ToggleActiveSyncTab;
let cli_write_action = CliAction::Write {
bytes: vec![102, 111, 111],
};
send_cli_action_to_server(
&session_metadata,
cli_toggle_active_tab_sync_action,
client_id,
);
send_cli_action_to_server(&session_metadata, cli_write_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_writer_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_new_pane_action_with_default_parameters() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
direction: None,
command: vec![],
plugin: None,
cwd: None,
floating: false,
in_place: false,
name: None,
close_on_exit: false,
start_suspended: false,
configuration: None,
skip_plugin_cache: false,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_new_pane_action_with_split_direction() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
direction: Some(Direction::Right),
command: vec![],
plugin: None,
cwd: None,
floating: false,
in_place: false,
name: None,
close_on_exit: false,
start_suspended: false,
configuration: None,
skip_plugin_cache: false,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_new_pane_action_with_command_and_cwd() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_new_pane_action = CliAction::NewPane {
direction: Some(Direction::Right),
command: vec!["htop".into()],
plugin: None,
cwd: Some("/some/folder".into()),
floating: false,
in_place: false,
name: None,
close_on_exit: false,
start_suspended: false,
configuration: None,
skip_plugin_cache: false,
};
send_cli_action_to_server(&session_metadata, cli_new_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_edit_action_with_default_parameters() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_edit_action = CliAction::Edit {
file: PathBuf::from("/file/to/edit"),
direction: None,
line_number: None,
floating: false,
in_place: false,
cwd: None,
};
send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_edit_action_with_line_number() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_edit_action = CliAction::Edit {
file: PathBuf::from("/file/to/edit"),
direction: None,
line_number: Some(100),
floating: false,
in_place: false,
cwd: None,
};
send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_edit_action_with_split_direction() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_edit_action = CliAction::Edit {
file: PathBuf::from("/file/to/edit"),
direction: Some(Direction::Down),
line_number: None,
floating: false,
in_place: false,
cwd: None,
};
send_cli_action_to_server(&session_metadata, cli_edit_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
assert_snapshot!(format!("{:?}", *received_pty_instructions.lock().unwrap()));
}
#[test]
pub fn send_cli_switch_mode_action() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let cli_switch_mode = CliAction::SwitchMode {
input_mode: InputMode::Locked,
};
send_cli_action_to_server(&session_metadata, cli_switch_mode, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![screen_thread]);
assert_snapshot!(format!(
"{:?}",
*mock_screen
.os_input
.server_to_client_messages
.lock()
.unwrap()
));
}
#[test]
pub fn send_cli_toggle_pane_embed_or_float() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let toggle_pane_embed_or_floating = CliAction::TogglePaneEmbedOrFloating;
// first time to float
send_cli_action_to_server(
&session_metadata,
toggle_pane_embed_or_floating.clone(),
client_id,
);
std::thread::sleep(std::time::Duration::from_millis(100));
// second time to embed
send_cli_action_to_server(
&session_metadata,
toggle_pane_embed_or_floating.clone(),
client_id,
);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_toggle_floating_panes() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let toggle_pane_embed_or_floating = CliAction::TogglePaneEmbedOrFloating;
let toggle_floating_panes = CliAction::ToggleFloatingPanes;
// float the focused pane
send_cli_action_to_server(&session_metadata, toggle_pane_embed_or_floating, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
// toggle floating panes (will hide the floated pane from the previous action)
send_cli_action_to_server(&session_metadata, toggle_floating_panes.clone(), client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
// toggle floating panes (will show the floated pane)
send_cli_action_to_server(&session_metadata, toggle_floating_panes.clone(), client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_close_pane_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_instruction = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let close_pane_action = CliAction::ClosePane;
send_cli_action_to_server(&session_metadata, close_pane_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_instruction, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_new_tab_action_default_params() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
let plugin_thread = log_actions_in_thread!(
received_plugin_instructions,
PluginInstruction::Exit,
plugin_receiver
);
let new_tab_action = CliAction::NewTab {
name: None,
layout: None,
layout_dir: None,
cwd: None,
};
send_cli_action_to_server(&session_metadata, new_tab_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![plugin_thread, screen_thread]);
let received_plugin_instructions = received_plugin_instructions.lock().unwrap();
let new_tab_action =
received_plugin_instructions
.iter()
.find(|instruction| match instruction {
PluginInstruction::NewTab(..) => true,
_ => false,
});
assert_snapshot!(format!("{:#?}", new_tab_action));
}
#[test]
pub fn send_cli_new_tab_action_with_name_and_layout() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
let plugin_thread = log_actions_in_thread!(
received_plugin_instructions,
PluginInstruction::Exit,
plugin_receiver
);
let new_tab_action = CliAction::NewTab {
name: Some("my-awesome-tab-name".into()),
layout: Some(PathBuf::from(format!(
"{}/src/unit/fixtures/layout-with-three-panes.kdl",
env!("CARGO_MANIFEST_DIR")
))),
layout_dir: None,
cwd: None,
};
send_cli_action_to_server(&session_metadata, new_tab_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![plugin_thread, screen_thread]);
let new_tab_instruction = received_plugin_instructions
.lock()
.unwrap()
.iter()
.rev()
.find(|i| {
if let PluginInstruction::NewTab(..) = i {
return true;
} else {
return false;
}
})
.unwrap()
.clone();
assert_snapshot!(format!("{:#?}", new_tab_instruction));
}
#[test]
pub fn send_cli_next_tab_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut second_tab_layout = TiledPaneLayout::default();
second_tab_layout.children_split_direction = SplitDirection::Horizontal;
second_tab_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(second_tab_layout);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let goto_next_tab = CliAction::GoToNextTab;
send_cli_action_to_server(&session_metadata, goto_next_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_previous_tab_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut second_tab_layout = TiledPaneLayout::default();
second_tab_layout.children_split_direction = SplitDirection::Horizontal;
second_tab_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(second_tab_layout);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let goto_previous_tab = CliAction::GoToPreviousTab;
send_cli_action_to_server(&session_metadata, goto_previous_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_goto_tab_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut second_tab_layout = TiledPaneLayout::default();
second_tab_layout.children_split_direction = SplitDirection::Horizontal;
second_tab_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(second_tab_layout);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let goto_tab = CliAction::GoToTab { index: 1 };
send_cli_action_to_server(&session_metadata, goto_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_close_tab_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut second_tab_layout = TiledPaneLayout::default();
second_tab_layout.children_split_direction = SplitDirection::Horizontal;
second_tab_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(second_tab_layout);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let close_tab = CliAction::CloseTab;
send_cli_action_to_server(&session_metadata, close_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn send_cli_rename_tab() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut second_tab_layout = TiledPaneLayout::default();
second_tab_layout.children_split_direction = SplitDirection::Horizontal;
second_tab_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(second_tab_layout);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
let plugin_thread = log_actions_in_thread!(
received_plugin_instructions,
PluginInstruction::Exit,
plugin_receiver
);
let rename_tab = CliAction::RenameTab {
name: "new-tab-name".into(),
};
send_cli_action_to_server(&session_metadata, rename_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![plugin_thread, screen_thread]);
let plugin_rename_tab_instruction = received_plugin_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
PluginInstruction::Update(updates) => updates
.iter()
.find(|u| match u {
(_, _, Event::TabUpdate(..)) => true,
_ => false,
})
.is_some(),
_ => false,
})
.cloned();
assert_snapshot!(format!("{:#?}", plugin_rename_tab_instruction))
}
#[test]
pub fn send_cli_undo_rename_tab() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut second_tab_layout = TiledPaneLayout::default();
second_tab_layout.children_split_direction = SplitDirection::Horizontal;
second_tab_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(second_tab_layout);
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
let plugin_thread = log_actions_in_thread!(
received_plugin_instructions,
PluginInstruction::Exit,
plugin_receiver
);
let rename_tab = CliAction::RenameTab {
name: "new-tab-name".into(),
};
let undo_rename_tab = CliAction::UndoRenameTab;
// first rename the tab
send_cli_action_to_server(&session_metadata, rename_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
// then undo the tab rename to go back to the default name
send_cli_action_to_server(&session_metadata, undo_rename_tab, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![plugin_thread, screen_thread]);
let plugin_undo_rename_tab_instruction = received_plugin_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
PluginInstruction::Update(updates) => updates
.iter()
.find(|u| match u {
(_, _, Event::TabUpdate(..)) => true,
_ => false,
})
.is_some(),
_ => false,
})
.cloned();
assert_snapshot!(format!("{:#?}", plugin_undo_rename_tab_instruction))
}
#[test]
pub fn send_cli_query_tab_names_action() {
let size = Size { cols: 80, rows: 10 };
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
mock_screen.new_tab(TiledPaneLayout::default());
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(Some(TiledPaneLayout::default()), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let query_tab_names = CliAction::QueryTabNames;
send_cli_action_to_server(&session_metadata, query_tab_names, client_id);
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let log_tab_names_instruction = received_server_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
ServerInstruction::Log(..) => true,
_ => false,
})
.cloned();
assert_snapshot!(format!("{:#?}", log_tab_names_instruction));
}
#[test]
pub fn send_cli_launch_or_focus_plugin_action() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let pty_receiver = mock_screen.pty_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let screen_thread = mock_screen.run(None, vec![]);
let received_pty_instructions = Arc::new(Mutex::new(vec![]));
let pty_thread = log_actions_in_thread!(
received_pty_instructions,
PtyInstruction::Exit,
pty_receiver
);
let cli_action = CliAction::LaunchOrFocusPlugin {
floating: true,
in_place: false,
move_to_focused_tab: true,
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
configuration: Default::default(),
skip_plugin_cache: false,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![pty_thread, screen_thread]);
let pty_fill_plugin_cwd_instruction = received_pty_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
PtyInstruction::FillPluginCwd(..) => true,
_ => false,
})
.cloned();
assert_snapshot!(format!("{:#?}", pty_fill_plugin_cwd_instruction));
}
#[test]
pub fn send_cli_launch_or_focus_plugin_action_when_plugin_is_already_loaded() {
let size = Size {
cols: 121,
rows: 20,
};
let client_id = 10; // fake client id should not appear in the screen's state
let mut mock_screen = MockScreen::new(size);
let plugin_receiver = mock_screen.plugin_receiver.take().unwrap();
let session_metadata = mock_screen.clone_session_metadata();
let mut initial_layout = TiledPaneLayout::default();
let existing_plugin_pane = TiledPaneLayout {
run: Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
configuration: Default::default(),
})),
..Default::default()
};
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), existing_plugin_pane];
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_plugin_instructions = Arc::new(Mutex::new(vec![]));
let plugin_thread = log_actions_in_thread!(
received_plugin_instructions,
PluginInstruction::Exit,
plugin_receiver
);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let cli_action = CliAction::LaunchOrFocusPlugin {
floating: true,
in_place: false,
move_to_focused_tab: true,
url: url::Url::parse("file:/path/to/fake/plugin").unwrap(),
configuration: Default::default(),
skip_plugin_cache: false,
};
send_cli_action_to_server(&session_metadata, cli_action, client_id);
std::thread::sleep(std::time::Duration::from_millis(100)); // give time for actions to be
mock_screen.teardown(vec![plugin_thread, server_thread, screen_thread]);
let plugin_load_instruction_sent = received_plugin_instructions
.lock()
.unwrap()
.iter()
.find(|instruction| match instruction {
PluginInstruction::Load(..) => true,
_ => false,
})
.is_some();
assert!(
!plugin_load_instruction_sent,
"Plugin Load instruction should not be sent for an already loaded plugin"
);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
assert_eq!(
snapshot_count, 2,
"Another render was sent for focusing the already loaded plugin"
);
for (cursor_coordinates, _snapshot) in snapshots.iter().skip(1) {
assert!(
cursor_coordinates.is_none(),
"Cursor moved to existing plugin in final snapshot indicating focus changed"
);
}
}
#[test]
pub fn screen_can_suppress_pane() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![TiledPaneLayout::default(), TiledPaneLayout::default()];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen
.to_screen
.send(ScreenInstruction::SuppressPane(PaneId::Terminal(1), 1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_can_break_pane_to_a_new_tab() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
let mut pane_to_break_free = TiledPaneLayout::default();
pane_to_break_free.name = Some("pane_to_break_free".to_owned());
let mut pane_to_stay = TiledPaneLayout::default();
pane_to_stay.name = Some("pane_to_stay".to_owned());
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![pane_to_break_free, pane_to_stay];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// we send ApplyLayout, because in prod this is eventually received after the message traverses
// through the plugin and pty threads (to open extra stuff we need in the layout, eg. the
// default plugins)
let _ = mock_screen.to_screen.send(ScreenInstruction::ApplyLayout(
TiledPaneLayout::default(),
vec![], // floating_panes_layout
Default::default(),
vec![], // floating panes ids
Default::default(),
1,
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// move back to make sure the other pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusLeftOrPreviousTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
// move forward to make sure the broken pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusRightOrNextTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_cannot_break_last_selectable_pane_to_a_new_tab() {
let size = Size { cols: 80, rows: 20 };
let initial_layout = TiledPaneLayout::default();
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_can_break_floating_pane_to_a_new_tab() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
let mut pane_to_break_free = TiledPaneLayout::default();
pane_to_break_free.name = Some("tiled_pane".to_owned());
let mut floating_pane = FloatingPaneLayout::default();
floating_pane.name = Some("floating_pane_to_eject".to_owned());
let mut floating_panes_layout = vec![floating_pane];
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![pane_to_break_free];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), floating_panes_layout.clone());
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// we send ApplyLayout, because in prod this is eventually received after the message traverses
// through the plugin and pty threads (to open extra stuff we need in the layout, eg. the
// default plugins)
floating_panes_layout.get_mut(0).unwrap().already_running = true;
let _ = mock_screen.to_screen.send(ScreenInstruction::ApplyLayout(
TiledPaneLayout::default(),
floating_panes_layout,
vec![(1, None)], // tiled pane ids - send these because one needs to be created under the
// ejected floating pane, lest the tab be closed as having no tiled panes
// (this happens in prod in the pty thread)
vec![], // floating panes ids
Default::default(),
1,
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// move back to make sure the other pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusLeftOrPreviousTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
// move forward to make sure the broken pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusRightOrNextTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_can_break_plugin_pane_to_a_new_tab() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
let mut pane_to_break_free = TiledPaneLayout::default();
pane_to_break_free.name = Some("plugin_pane_to_break_free".to_owned());
pane_to_break_free.run = Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
configuration: Default::default(),
}));
let mut pane_to_stay = TiledPaneLayout::default();
pane_to_stay.name = Some("pane_to_stay".to_owned());
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![pane_to_break_free, pane_to_stay];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// we send ApplyLayout, because in prod this is eventually received after the message traverses
// through the plugin and pty threads (to open extra stuff we need in the layout, eg. the
// default plugins)
let _ = mock_screen.to_screen.send(ScreenInstruction::ApplyLayout(
TiledPaneLayout::default(),
vec![], // floating_panes_layout
Default::default(),
vec![], // floating panes ids
Default::default(),
1,
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// move back to make sure the other pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusLeftOrPreviousTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
// move forward to make sure the broken pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusRightOrNextTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_can_break_floating_plugin_pane_to_a_new_tab() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
let mut pane_to_break_free = TiledPaneLayout::default();
pane_to_break_free.name = Some("tiled_pane".to_owned());
let mut floating_pane = FloatingPaneLayout::default();
floating_pane.name = Some("floating_plugin_pane_to_eject".to_owned());
floating_pane.run = Some(Run::Plugin(RunPlugin {
_allow_exec_host_cmd: false,
location: RunPluginLocation::File(PathBuf::from("/path/to/fake/plugin")),
configuration: Default::default(),
}));
let mut floating_panes_layout = vec![floating_pane];
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![pane_to_break_free];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), floating_panes_layout.clone());
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// we send ApplyLayout, because in prod this is eventually received after the message traverses
// through the plugin and pty threads (to open extra stuff we need in the layout, eg. the
// default plugins)
floating_panes_layout.get_mut(0).unwrap().already_running = true;
let _ = mock_screen.to_screen.send(ScreenInstruction::ApplyLayout(
TiledPaneLayout::default(),
floating_panes_layout,
vec![(1, None)], // tiled pane ids - send these because one needs to be created under the
// ejected floating pane, lest the tab be closed as having no tiled panes
// (this happens in prod in the pty thread)
vec![], // floating panes ids
Default::default(),
1,
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
// move back to make sure the other pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusLeftOrPreviousTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
// move forward to make sure the broken pane is in the previous tab
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusRightOrNextTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_can_move_pane_to_a_new_tab_right() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
let mut pane_to_break_free = TiledPaneLayout::default();
pane_to_break_free.name = Some("pane_to_break_free".to_owned());
let mut pane_to_stay = TiledPaneLayout::default();
pane_to_stay.name = Some("pane_to_stay".to_owned());
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![pane_to_break_free, pane_to_stay];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusLeftOrPreviousTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = mock_screen
.to_screen
.send(ScreenInstruction::BreakPaneRight(1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}
#[test]
pub fn screen_can_move_pane_to_a_new_tab_left() {
let size = Size { cols: 80, rows: 20 };
let mut initial_layout = TiledPaneLayout::default();
let mut pane_to_break_free = TiledPaneLayout::default();
pane_to_break_free.name = Some("pane_to_break_free".to_owned());
let mut pane_to_stay = TiledPaneLayout::default();
pane_to_stay.name = Some("pane_to_stay".to_owned());
initial_layout.children_split_direction = SplitDirection::Vertical;
initial_layout.children = vec![pane_to_break_free, pane_to_stay];
let mut mock_screen = MockScreen::new(size);
let screen_thread = mock_screen.run(Some(initial_layout), vec![]);
let received_server_instructions = Arc::new(Mutex::new(vec![]));
let server_receiver = mock_screen.server_receiver.take().unwrap();
let server_thread = log_actions_in_thread!(
received_server_instructions,
ServerInstruction::KillSession,
server_receiver
);
let _ = mock_screen.to_screen.send(ScreenInstruction::BreakPane(
Box::new(Layout::default()),
Default::default(),
1,
));
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = mock_screen
.to_screen
.send(ScreenInstruction::MoveFocusLeftOrPreviousTab(1));
std::thread::sleep(std::time::Duration::from_millis(100));
let _ = mock_screen
.to_screen
.send(ScreenInstruction::BreakPaneLeft(1));
std::thread::sleep(std::time::Duration::from_millis(100));
mock_screen.teardown(vec![server_thread, screen_thread]);
let snapshots = take_snapshots_and_cursor_coordinates_from_render_events(
received_server_instructions.lock().unwrap().iter(),
size,
);
let snapshot_count = snapshots.len();
for (_cursor_coordinates, snapshot) in snapshots {
assert_snapshot!(format!("{}", snapshot));
}
assert_snapshot!(format!("{}", snapshot_count));
}