aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/tests/io
diff options
context:
space:
mode:
Diffstat (limited to 'crates/shirabe/tests/io')
-rw-r--r--crates/shirabe/tests/io/console_io_test.rs165
1 files changed, 147 insertions, 18 deletions
diff --git a/crates/shirabe/tests/io/console_io_test.rs b/crates/shirabe/tests/io/console_io_test.rs
index 3c8242a..f96aec1 100644
--- a/crates/shirabe/tests/io/console_io_test.rs
+++ b/crates/shirabe/tests/io/console_io_test.rs
@@ -1,17 +1,28 @@
//! ref: composer/tests/Composer/Test/IO/ConsoleIOTest.php
-// These mock Symfony's InputInterface/OutputInterface/HelperSet (and QuestionHelper) to
-// drive ConsoleIO; those console abstractions are not ported.
+// PHP drives ConsoleIO with PHPUnit mocks of Symfony's InputInterface/OutputInterface/HelperSet
+// (and QuestionHelper). There is no mocking framework here, so the mocks are reproduced with real,
+// side-effect-free Symfony console types:
+// * `expects()->method('write')->with(...)` call/argument expectations are replaced by reading
+// back what a real `BufferedOutput` collected and asserting on it.
+// * `isInteractive` consecutive return values are reproduced by toggling a real `ArrayInput`'s
+// interactive flag between calls.
+// * QuestionHelper `ask`/`get` expectations are replaced by feeding a prepared answer through a
+// `php://memory` input stream (exactly as StrictConfirmationQuestionTest does) and asserting on
+// the returned value.
use indexmap::IndexMap;
use shirabe::io::ConsoleIO;
use shirabe::io::IOInterfaceImmutable;
use shirabe::io::IOInterfaceMutable;
+use shirabe::io::io_interface::NORMAL;
use shirabe_external_packages::symfony::console::helper::QuestionHelper;
use shirabe_external_packages::symfony::console::input::array_input::ArrayInput;
use shirabe_external_packages::symfony::console::input::input_interface::InputInterface;
+use shirabe_external_packages::symfony::console::input::streamable_input_interface::StreamableInputInterface;
use shirabe_external_packages::symfony::console::output::buffered_output::BufferedOutput;
use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface;
+use shirabe_php_shim::PhpMixed;
use std::cell::RefCell;
use std::rc::Rc;
@@ -26,58 +37,176 @@ fn make_console_io() -> ConsoleIO {
ConsoleIO::new(input, output, QuestionHelper::default())
}
-#[ignore = "requires a PHPUnit mock of InputInterface::isInteractive with willReturnOnConsecutiveCalls; no mocking framework"]
+/// Opens a `php://memory` stream pre-filled with `input`, mirroring
+/// StrictConfirmationQuestionTest::getInputStream.
+fn get_input_stream(input: &str) -> shirabe_php_shim::PhpResource {
+ let stream = shirabe_php_shim::php_fopen_resource("php://memory", "r+");
+ shirabe_php_shim::fwrite_resource(&stream, input);
+ shirabe_php_shim::rewind(&stream);
+ stream
+}
+
+/// Builds a ConsoleIO whose interactive input stream yields `answer`, plus a handle on the
+/// `BufferedOutput` so the prompt can be inspected. The input is an `ArrayInput` (interactive by
+/// default) with the answer stream attached, exactly as the question-helper tests set things up.
+fn make_console_io_with_answer(answer: &str) -> (ConsoleIO, Rc<RefCell<BufferedOutput>>) {
+ let mut array_input = ArrayInput::new(vec![], None).unwrap();
+ array_input.set_stream(get_input_stream(answer));
+ let input: Rc<RefCell<dyn InputInterface>> = Rc::new(RefCell::new(array_input));
+ let buffered = Rc::new(RefCell::new(BufferedOutput::new(None, false, None)));
+ let output: Rc<RefCell<dyn OutputInterface>> = buffered.clone();
+ (
+ ConsoleIO::new(input, output, QuestionHelper::default()),
+ buffered,
+ )
+}
+
#[test]
fn test_is_interactive() {
- todo!()
+ // PHP mocks isInteractive to return true then false on consecutive calls. A real ArrayInput
+ // exposes a mutable interactive flag, so toggling it between the two asserts is equivalent.
+ let array_input = ArrayInput::new(vec![], None).unwrap();
+ let input: Rc<RefCell<dyn InputInterface>> = Rc::new(RefCell::new(array_input));
+ let output: Rc<RefCell<dyn OutputInterface>> =
+ Rc::new(RefCell::new(BufferedOutput::new(None, false, None)));
+ let console_io = ConsoleIO::new(input.clone(), output, QuestionHelper::default());
+
+ input.borrow_mut().set_interactive(true);
+ assert!(console_io.is_interactive());
+ input.borrow_mut().set_interactive(false);
+ assert!(!console_io.is_interactive());
}
-#[ignore = "requires a PHPUnit mock of OutputInterface with expects()->method('write')->with() expectation verification; no mocking framework"]
#[test]
fn test_write() {
- todo!()
+ // PHP: expects write('some information about something', false) at VERBOSITY_NORMAL.
+ // A plain (non-Console) OutputInterface routes write to the main output, so the message lands
+ // in the BufferedOutput verbatim (no trailing EOL since newline is false).
+ let input: Rc<RefCell<dyn InputInterface>> =
+ Rc::new(RefCell::new(ArrayInput::new(vec![], None).unwrap()));
+ let buffered = Rc::new(RefCell::new(BufferedOutput::new(None, false, None)));
+ let output: Rc<RefCell<dyn OutputInterface>> = buffered.clone();
+ let console_io = ConsoleIO::new(input, output, QuestionHelper::default());
+
+ console_io.write3("some information about something", false, NORMAL);
+
+ assert_eq!(
+ "some information about something",
+ buffered.borrow().fetch()
+ );
}
-#[ignore = "requires a PHPUnit mock of ConsoleOutputInterface with getErrorOutput/write expectation verification; no mocking framework"]
+#[ignore = "PHP mocks ConsoleOutputInterface so getErrorOutput returns the same mock; a real ConsoleOutput's error StreamOutput writes to php://stderr, which cannot be read back, and the trait offers no seam to inject a BufferedOutput error sink"]
#[test]
fn test_write_error() {
todo!()
}
-#[ignore = "requires a PHPUnit mock of OutputInterface with a write() callback expectation verification; no mocking framework"]
+#[ignore = "ConsoleIO::write3 takes a single &str; the test feeds a 2-element array ['First line','Second lines'] and asserts a per-element regex on the debugging-prefixed messages array, which the &str signature cannot represent"]
#[test]
fn test_write_with_multiple_line_string_when_debugging() {
todo!()
}
-#[ignore = "requires a PHPUnit mock of OutputInterface with willReturnCallback series expectation verification; no mocking framework"]
#[test]
fn test_overwrite() {
- todo!()
+ // PHP asserts the exact series of write() calls overwrite produces (backspaces, padding, etc.).
+ // PHP's mock intercepts each raw write() argument, before the output formatter runs. A real
+ // BufferedOutput captures the *rendered* text, so the `<question>/<comment>/<info>` tags are
+ // stripped while the backspace/space runs are identical. The backspace counts still derive from
+ // `strlen(strip_tags(lastMessage))`, so they match PHP exactly. The series, in order, is:
+ // 'something (strlen = 23)' + "\n" (initial write, newline defaults true)
+ // "\x08" * 23 (clear previous, strip_tags len = 23)
+ // 'shorter (12)' (new message, strip_tags len = 12)
+ // " " * 11 (fill: 23 - 12)
+ // "\x08" * 11 (move cursor back)
+ // "\x08" * 12 (clear previous, len = 12)
+ // 'something longer than initial (34)' (new message)
+ // The final overwrite needs no fill and newline defaults true, appending a trailing "\n".
+ let input: Rc<RefCell<dyn InputInterface>> =
+ Rc::new(RefCell::new(ArrayInput::new(vec![], None).unwrap()));
+ let buffered = Rc::new(RefCell::new(BufferedOutput::new(None, false, None)));
+ let output: Rc<RefCell<dyn OutputInterface>> = buffered.clone();
+ let console_io = ConsoleIO::new(input, output, QuestionHelper::default());
+
+ console_io.write3("something (<question>strlen = 23</question>)", true, NORMAL);
+ console_io.overwrite4("shorter (<comment>12</comment>)", false, None, NORMAL);
+ console_io.overwrite4(
+ "something longer than initial (<info>34</info>)",
+ true,
+ None,
+ NORMAL,
+ );
+
+ let bs = |n: usize| "\u{08}".repeat(n);
+ let expected = format!(
+ "{}\n{}{}{}{}{}{}\n",
+ "something (strlen = 23)",
+ bs(23),
+ "shorter (12)",
+ " ".repeat(11),
+ bs(11),
+ bs(12),
+ "something longer than initial (34)",
+ );
+ assert_eq!(expected, buffered.borrow().fetch());
}
-#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"]
#[test]
fn test_ask() {
- todo!()
+ // PHP asserts QuestionHelper::ask receives a Question. Behaviorally, an interactive input whose
+ // stream yields the answer makes ConsoleIO::ask return that answer.
+ let (console_io, _output) = make_console_io_with_answer("answer\n");
+ let result = console_io.ask("Why?".to_string(), PhpMixed::String("default".to_string()));
+ assert_eq!(PhpMixed::String("answer".to_string()), result);
}
-#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"]
#[test]
fn test_ask_confirmation() {
- todo!()
+ // PHP asserts the helper receives a StrictConfirmationQuestion. Behaviorally, a "yes" answer
+ // confirms and a "no" answer denies via the StrictConfirmationQuestion ConsoleIO builds.
+ let (console_io, _output) = make_console_io_with_answer("yes\n");
+ assert!(console_io.ask_confirmation("Why?".to_string(), false));
+
+ let (console_io, _output) = make_console_io_with_answer("no\n");
+ assert!(!console_io.ask_confirmation("Why?".to_string(), false));
}
-#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"]
#[test]
fn test_ask_and_validate() {
- todo!()
+ // PHP asserts the helper receives a Question with a validator. Behaviorally, an always-true
+ // validator passes the answer straight through.
+ let (console_io, _output) = make_console_io_with_answer("answer\n");
+ let validator: Box<dyn Fn(PhpMixed) -> anyhow::Result<PhpMixed>> = Box::new(|value| Ok(value));
+ let result = console_io
+ .ask_and_validate(
+ "Why?".to_string(),
+ validator,
+ Some(10),
+ PhpMixed::String("default".to_string()),
+ )
+ .unwrap();
+ assert_eq!(PhpMixed::String("answer".to_string()), result);
}
-#[ignore = "requires a PHPUnit mock of QuestionHelper/HelperSet with ask/get expectation verification; no mocking framework"]
#[test]
fn test_select() {
- todo!()
+ // PHP mocks the helper to return ['item2'] and asserts select maps it to ['1']. Behaviorally,
+ // feeding "item2" to a multiselect ChoiceQuestion over the non-associative list
+ // ["item1","item2"] resolves to that choice, which select maps back to its index '1'.
+ let (console_io, _output) = make_console_io_with_answer("item2\n");
+ let result = console_io.select(
+ "Select item".to_string(),
+ vec!["item1".to_string(), "item2".to_string()],
+ PhpMixed::String("item1".to_string()),
+ PhpMixed::Bool(false),
+ "Error message".to_string(),
+ true,
+ );
+ assert_eq!(
+ PhpMixed::List(vec![PhpMixed::String("1".to_string())]),
+ result
+ );
}
#[test]