pub trait ClipboardSink: Send + Sync {
fn copy_text(&mut self, text: String);
}
pub struct NullClipboard;
impl ClipboardSink for NullClipboard {
fn copy_text(&mut self, _text: String) {}
}
pub struct CaptureClipboard {
pub captured: Vec<String>,
}
impl CaptureClipboard {
pub fn new() -> Self {
Self {
captured: Vec::new(),
}
}
}
impl Default for CaptureClipboard {
fn default() -> Self {
Self::new()
}
}
impl ClipboardSink for CaptureClipboard {
fn copy_text(&mut self, text: String) {
self.captured.push(text);
}
}
pub fn selection_to_tsv(cells: &[Vec<String>]) -> String {
cells
.iter()
.map(|row| row.join("\t"))
.collect::<Vec<_>>()
.join("\n")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tsv_export_tab_separated() {
let cells = vec![vec!["a".to_string(), "b".to_string(), "c".to_string()]];
assert_eq!(selection_to_tsv(&cells), "a\tb\tc");
}
#[test]
fn tsv_export_newline_between_rows() {
let cells = vec![
vec!["1".to_string(), "2".to_string()],
vec!["3".to_string(), "4".to_string()],
];
assert_eq!(selection_to_tsv(&cells), "1\t2\n3\t4");
}
#[test]
fn tsv_empty_input() {
assert_eq!(selection_to_tsv(&[]), "");
}
#[test]
fn capture_clipboard_records_text() {
let mut sink = CaptureClipboard::new();
sink.copy_text("hello".to_string());
sink.copy_text("world".to_string());
assert_eq!(sink.captured, vec!["hello", "world"]);
}
#[test]
fn null_clipboard_does_not_panic() {
let mut sink = NullClipboard;
sink.copy_text("anything".to_string());
}
}