Skip to content

Commit

Permalink
add unit tests for converting backend input events into Input
Browse files Browse the repository at this point in the history
  • Loading branch information
rhysd committed Oct 25, 2023
1 parent 60ed6f8 commit 945756f
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 48 deletions.
4 changes: 2 additions & 2 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[alias]
watch-check = ["watch", "-x", "clippy --features search --examples --tests", "-x", "clippy --features tuirs-crossterm,search --no-default-features --examples --tests"]
watch-test = ["watch", "-x", "test --features=search", "-w", "src", "-w", "tests"]
watch-check = ["watch", "-x", "clippy --features=search,termwiz --examples --tests", "-x", "clippy --features tuirs-crossterm,search --no-default-features --examples --tests"]
watch-test = ["watch", "-x", "test --features=search,termwiz", "-w", "src", "-w", "tests"]
31 changes: 18 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ jobs:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --features=search
- run: cargo test --features=search,termwiz,termion,arbitrary
if: ${{ matrix.os != 'windows-latest' }}
- run: cargo test --features=search,termwiz,arbitrary
if: ${{ matrix.os == 'windows-latest' }}
- run: cargo test --no-default-features --features=tuirs-crossterm,search -- --skip .rs
- run: cargo test --no-default-features --features=tuirs-termion,search -- --skip .rs
if: ${{ matrix.os != 'windows-latest' }}
Expand All @@ -29,16 +32,18 @@ jobs:
components: clippy,rustfmt
- uses: Swatinem/rust-cache@v2
- run: cargo fmt -- --check
- run: cargo clippy --examples -- -D warnings
- run: cargo clippy --examples --features search -- -D warnings
- run: cargo clippy --examples --no-default-features --features termion -- -D warnings
- run: cargo clippy --examples --no-default-features --features termion,search -- -D warnings
- run: cargo clippy --examples --no-default-features --features no-backend -- -D warnings
- run: cargo clippy --examples --no-default-features --features no-backend,search -- -D warnings
- run: cargo clippy --examples --no-default-features --features tuirs-crossterm -- -D warnings
- run: cargo clippy --examples --no-default-features --features tuirs-crossterm,search -- -D warnings
- run: cargo clippy --examples --no-default-features --features tuirs-termion -- -D warnings
- run: cargo clippy --examples --no-default-features --features tuirs-termion,search -- -D warnings
- run: cargo clippy --examples --no-default-features --features tuirs-no-backend -- -D warnings
- run: cargo clippy --examples --no-default-features --features tuirs-no-backend,search -- -D warnings
- run: cargo clippy --examples --tests -- -D warnings
- run: cargo clippy --examples --tests --features search -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features termion -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features termion,search -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features termwiz -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features termwiz,search -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features no-backend -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features no-backend,search -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features tuirs-crossterm -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features tuirs-crossterm,search -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features tuirs-termion -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features tuirs-termion,search -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features tuirs-no-backend -- -D warnings
- run: cargo clippy --examples --tests --no-default-features --features tuirs-no-backend,search -- -D warnings
- run: cargo rustdoc --features=search,termwiz,termion -p tui-textarea -- -D warnings
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ cargo test --features=search
To run linters:

```sh
cargo clippy --features=search --tests --examples
cargo clippy --features=tuirs-crossterm,search --no-default-features --tests --examples
cargo clippy --features=search,termwiz,termion --tests --examples
cargo clippy --features=tuirs-crossterm,tuirs-termion,search --no-default-features --tests --examples
cargo fmt -- --check
```

Note: On Windows, remove `termion` and `tuirs-termion` features from `--features` argument since termion doesn't support Windows.

If you use [cargo-watch][], `cargo watch-check` and `cargo watch-test` aliases are useful to run checks/tests automatically
on files being changed.

Expand Down
120 changes: 119 additions & 1 deletion src/input/crossterm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,128 @@ impl From<MouseEvent> for Input {
let key = match mouse.kind {
MouseEventKind::ScrollDown => Key::MouseScrollDown,
MouseEventKind::ScrollUp => Key::MouseScrollUp,
_ => return Self::default(),
_ => Key::Null,
};
let ctrl = mouse.modifiers.contains(KeyModifiers::CONTROL);
let alt = mouse.modifiers.contains(KeyModifiers::ALT);
Self { key, ctrl, alt }
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::crossterm::event::{KeyEventKind, KeyEventState};
use crate::input::tests::input;

fn key_event(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind: KeyEventKind::Release,
state: KeyEventState::empty(),
}
}

fn mouse_event(kind: MouseEventKind, modifiers: KeyModifiers) -> MouseEvent {
MouseEvent {
kind,
column: 1,
row: 1,
modifiers,
}
}

#[test]
fn key_to_input() {
for (from, to) in [
(
key_event(KeyCode::Char('a'), KeyModifiers::empty()),
input(Key::Char('a'), false, false),
),
(
key_event(KeyCode::Enter, KeyModifiers::empty()),
input(Key::Enter, false, false),
),
(
key_event(KeyCode::Left, KeyModifiers::CONTROL),
input(Key::Left, true, false),
),
(
key_event(KeyCode::Home, KeyModifiers::ALT),
input(Key::Home, false, true),
),
(
key_event(KeyCode::F(1), KeyModifiers::ALT | KeyModifiers::CONTROL),
input(Key::F(1), true, true),
),
(
key_event(KeyCode::NumLock, KeyModifiers::CONTROL),
input(Key::Null, true, false),
),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
}

#[test]
fn mouse_to_input() {
for (from, to) in [
(
mouse_event(MouseEventKind::ScrollDown, KeyModifiers::empty()),
input(Key::MouseScrollDown, false, false),
),
(
mouse_event(MouseEventKind::ScrollUp, KeyModifiers::CONTROL),
input(Key::MouseScrollUp, true, false),
),
(
mouse_event(MouseEventKind::ScrollDown, KeyModifiers::ALT),
input(Key::MouseScrollDown, false, true),
),
(
mouse_event(
MouseEventKind::ScrollUp,
KeyModifiers::CONTROL | KeyModifiers::ALT,
),
input(Key::MouseScrollUp, true, true),
),
(
mouse_event(MouseEventKind::Moved, KeyModifiers::CONTROL),
input(Key::Null, true, false),
),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
}

#[test]
fn event_to_input() {
for (from, to) in [
(
Event::Key(key_event(KeyCode::Char('a'), KeyModifiers::empty())),
input(Key::Char('a'), false, false),
),
(
Event::Mouse(mouse_event(
MouseEventKind::ScrollDown,
KeyModifiers::empty(),
)),
input(Key::MouseScrollDown, false, false),
),
(Event::FocusGained, input(Key::Null, false, false)),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
}
}

// Regression for https://github.com/rhysd/tui-textarea/issues/14
#[test]
#[cfg(target_os = "windows")]
fn press_ignore_on_windows() {
let mut k = key_event(KeyCode::Char('a'), KeyModifiers::empty());
k.kind = KeyEventKind::Press;
let want = input(Key::Null, false, false);
assert_eq!(Input::from(k.clone()), want, "{:?} -> {:?}", k, want);
}
}
27 changes: 25 additions & 2 deletions src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use arbitrary::Arbitrary;
///
/// This type is marked as `#[non_exhaustive]` since more keys may be supported in the future.
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub enum Key {
/// Normal letter key input
Expand Down Expand Up @@ -93,7 +93,7 @@ impl Default for Key {
/// alt: false,
/// });
/// ```
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Default, PartialEq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct Input {
/// Typed key.
Expand All @@ -103,3 +103,26 @@ pub struct Input {
/// Alt modifier key. `true` means Alt key was pressed.
pub alt: bool,
}

#[cfg(test)]
mod tests {
use super::*;

pub(crate) fn input(key: Key, ctrl: bool, alt: bool) -> Input {
Input { key, ctrl, alt }
}

#[test]
#[cfg(feature = "arbitrary")]
fn arbitrary_input() {
let mut u = arbitrary::Unstructured::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
Input::arbitrary(&mut u).unwrap();
}

#[test]
#[cfg(feature = "arbitrary")]
fn arbitrary_key() {
let mut u = arbitrary::Unstructured::new(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
Key::arbitrary(&mut u).unwrap();
}
}
100 changes: 79 additions & 21 deletions src/input/termion.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{Input, Key};
use termion::event::{Event, Key as KeyEvent, MouseEvent};
use termion::event::{Event, Key as KeyEvent, MouseButton, MouseEvent};

impl From<Event> for Input {
/// Convert [`termion::event::Event`] to [`Input`].
Expand All @@ -15,34 +15,32 @@ impl From<Event> for Input {
impl From<KeyEvent> for Input {
/// Convert [`termion::event::Key`] to [`Input`].
fn from(key: KeyEvent) -> Self {
use KeyEvent::*;

let mut ctrl = false;
let mut alt = false;
let key = match key {
Char('\n' | '\r') => Key::Enter,
Char(c) => Key::Char(c),
Ctrl(c) => {
KeyEvent::Char('\n' | '\r') => Key::Enter,
KeyEvent::Char(c) => Key::Char(c),
KeyEvent::Ctrl(c) => {
ctrl = true;
Key::Char(c)
}
Alt(c) => {
KeyEvent::Alt(c) => {
alt = true;
Key::Char(c)
}
Backspace => Key::Backspace,
Left => Key::Left,
Right => Key::Right,
Up => Key::Up,
Down => Key::Down,
Home => Key::Home,
End => Key::End,
PageUp => Key::PageUp,
PageDown => Key::PageDown,
BackTab => Key::Tab,
Delete => Key::Delete,
Esc => Key::Esc,
F(x) => Key::F(x),
KeyEvent::Backspace => Key::Backspace,
KeyEvent::Left => Key::Left,
KeyEvent::Right => Key::Right,
KeyEvent::Up => Key::Up,
KeyEvent::Down => Key::Down,
KeyEvent::Home => Key::Home,
KeyEvent::End => Key::End,
KeyEvent::PageUp => Key::PageUp,
KeyEvent::PageDown => Key::PageDown,
KeyEvent::BackTab => Key::Tab,
KeyEvent::Delete => Key::Delete,
KeyEvent::Esc => Key::Esc,
KeyEvent::F(x) => Key::F(x),
_ => Key::Null,
};

Expand All @@ -53,7 +51,6 @@ impl From<KeyEvent> for Input {
impl From<MouseEvent> for Input {
/// Convert [`termion::event::MouseEvent`] to [`Input`].
fn from(mouse: MouseEvent) -> Self {
use termion::event::MouseButton;
let key = match mouse {
MouseEvent::Press(MouseButton::WheelUp, ..) => Key::MouseScrollUp,
MouseEvent::Press(MouseButton::WheelDown, ..) => Key::MouseScrollDown,
Expand All @@ -66,3 +63,64 @@ impl From<MouseEvent> for Input {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::input::tests::input;

#[test]
fn key_to_input() {
for (from, to) in [
(KeyEvent::Char('a'), input(Key::Char('a'), false, false)),
(KeyEvent::Ctrl('a'), input(Key::Char('a'), true, false)),
(KeyEvent::Alt('a'), input(Key::Char('a'), false, true)),
(KeyEvent::Char('\n'), input(Key::Enter, false, false)),
(KeyEvent::Char('\r'), input(Key::Enter, false, false)),
(KeyEvent::F(1), input(Key::F(1), false, false)),
(KeyEvent::BackTab, input(Key::Tab, false, false)),
(KeyEvent::Null, input(Key::Null, false, false)),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
}

#[test]
fn mouse_to_input() {
for (from, to) in [
(
MouseEvent::Press(MouseButton::WheelDown, 1, 1),
input(Key::MouseScrollDown, false, false),
),
(
MouseEvent::Press(MouseButton::WheelUp, 1, 1),
input(Key::MouseScrollUp, false, false),
),
(
MouseEvent::Press(MouseButton::Left, 1, 1),
input(Key::Null, false, false),
),
(MouseEvent::Release(1, 1), input(Key::Null, false, false)),
(MouseEvent::Hold(1, 1), input(Key::Null, false, false)),
] {
assert_eq!(Input::from(from), to, "{:?} -> {:?}", from, to);
}
}

#[test]
fn event_to_input() {
for (from, to) in [
(
Event::Key(KeyEvent::Char('a')),
input(Key::Char('a'), false, false),
),
(
Event::Mouse(MouseEvent::Press(MouseButton::WheelDown, 1, 1)),
input(Key::MouseScrollDown, false, false),
),
(Event::Unsupported(vec![]), input(Key::Null, false, false)),
] {
assert_eq!(Input::from(from.clone()), to, "{:?} -> {:?}", from, to);
}
}
}
Loading

0 comments on commit 945756f

Please sign in to comment.