Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose a testing API that accepts a sequence of commands and/or keystrokes to execute the prompt. #71

Open
mikaelmello opened this issue Aug 19, 2022 · 7 comments
Milestone

Comments

@mikaelmello
Copy link
Owner

This will allow both we and our users to make the prompts fully testable.

It will heavily rely on #70

@mikaelmello mikaelmello added this to the v1.0.0 milestone Aug 19, 2022
@mikaelmello
Copy link
Owner Author

mikaelmello commented Aug 19, 2022

Layer 1: receive a sequence of commands as input. This layer could not even know about the existence of keystrokes.

Layer 2: receive a sequence of ui::key::Keys. Then they will be handled by the normal key handler implemented by each prompt.

Layer 3: Full E2E testing with a tty. This will allow us to test our integrations with each terminal backend. enigo might be a good way to go about it

@JimLynchCodes
Copy link

JimLynchCodes commented Mar 23, 2023

Hey @mikaelmello 👋

I am relatively new to Rust and starting playing with this library for a cool way for my cli tools to take input from users rather than always as args.

I really like this library. The type to filter functionality in the select and multiselect was a cool surprise for me. Nice! 👍

I’m trying to convince my boss to use Rust and things like this at work, but they want to see good tests for “enterprise level code”.

I took a stab at it on my own, but I’m not exactly sure how to go from the ‘Key’ to making it think that key was pressed. See this related issue .

I think sending the raw Key events is fine for me- at least it will allow me to navigate through the prompts and get stuff done.

I think at least adding an example of doing this in the README could be an easy win.

I do also like the idea of having some higher level commands though… for example a function that takes a vec of choices and under the hood maps to all the arrow / filtering / space key presses that go into selecting a bunch of things on a multiselect…

I have some bandwidth to think about this if you need any help as well.

Thanks!

@mikaelmello
Copy link
Owner Author

hey @JimLynchCodes , 100%. Unfortunately this hasn't been implemented as of now, a few months back I did try tackling this but it was more complex than I expected, so it kind of got brushed aside, I intend to revisit it over the next couple of weeks

@JimLynchCodes
Copy link

JimLynchCodes commented Mar 27, 2023

No worries @mikaelmello. It's not super urgent for me. Here are some things I've been trying, nothing exactly working yet but it might be interesting to you... actually ChatGPT gave me some of this code, but it doesn't compile. 😆 😅

I think a chainable "stdin" function like this where I can just pass in a string would be a nice API, at least for "Confirm".

let output = Command::cargo_bin("mybin")
        .unwrap()
        .current_dir(temp_dir.path())
        .stdin("yes\n") // Mock the user input
        .stdin(Stdio::piped("foo"))
        .assert()
        .success();
        
assert_eq!(output, "You said yes!");

Unfortunately though, this "stdin" function doesn't take a string or string slice... here is my compiler error:

 ----- ^^^^^^^ the trait `From<&str>` is not implemented for `Stdio`
    |          |
    |          required by a bound introduced by this call

I also tried using this strange and imo very ugly syntax, but I am still getting a compile error, "no method names as_mut found for struct Assert in the current scope" 🤔

use std::io::{Read, Write};
use std::process::{Command, Stdio};

...

 let mut child_process = Command::new("mybin")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    // Send input to the child process
    let input_data = "hello world\n";
    child_process
        .stdin
        .as_mut()
        .unwrap()
        .write_all(input_data.as_bytes())
        .unwrap();

    // Read output from the child process
    let mut output_data = String::new();
    child_process
        .stdout
        .as_mut()
        .unwrap()
        .read_to_string(&mut output_data)
        .unwrap();

    // Wait for the child process to exit
    let exit_status = child_process.wait().unwrap();

@Kobzol
Copy link

Kobzol commented Mar 4, 2024

If anyone finds it helpful, I had some basic success with using https://github.com/rust-cli/rexpect to test inquire CLIs.

@mikaelmello
Copy link
Owner Author

That is indeed really helpful, I found the project where you're doing that (cargo-wizard right? really awesome btw)!

Will take some inspiration from it when I find the time: https://github.com/Kobzol/cargo-wizard/blob/main/tests/integration/utils/terminal.rs

@srid
Copy link

srid commented Jul 22, 2024

@JimLynchCodes assert_cmd provides a write_stdin that you can use to write stdin:

#[test]
fn om_init() -> anyhow::Result<()> {
    let temp_dir = assert_fs::TempDir::new().unwrap();
    om()?
        .arg("init")
        .arg(temp_dir.path())
        .write_stdin("\n\n")
        .assert()
        .success();
    temp_dir.close().unwrap();
    Ok(())
}

However, this crashes at runtime (during Select prompt) throwing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants