1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
//! TDD RED tests for Task 2: Form builder (huh pattern).
//!
//! These tests compile only AFTER `src/form.rs` (with Form + FormField) is implemented.
use std::collections::HashMap;
use std::io::Cursor;
use crate::form::{Form, FormField};
// ---------------------------------------------------------------------------
// Basic Form: Input + Confirm, happy path
// ---------------------------------------------------------------------------
#[test]
fn test_form_input_and_confirm_happy_path() {
// Input: "Alice", Confirm: "y"
let input_bytes: &[u8] = b"Alice\ny\n";
let mut cursor = Cursor::new(input_bytes);
let result = Form::new()
.field(FormField::input("name", "What is your name?"))
.field(FormField::confirm("ok", "Are you sure?", false))
.run_with_input(&mut cursor)
.expect("form should succeed");
assert_eq!(result.get("name").map(|s| s.as_str()), Some("Alice"));
assert_eq!(result.get("ok").map(|s| s.as_str()), Some("true"));
}
// ---------------------------------------------------------------------------
// Input validation: reject empty, accept non-empty
// ---------------------------------------------------------------------------
#[test]
fn test_form_input_validation_re_prompts_on_empty() {
// First line is empty (rejected), second is "Bob" (accepted)
let input_bytes: &[u8] = b"\nBob\n";
let mut cursor = Cursor::new(input_bytes);
let result = Form::new()
.field(FormField::input("name", "Name?").validate(|s| {
if s.trim().is_empty() {
Err("Name cannot be empty".to_string())
} else {
Ok(())
}
}))
.run_with_input(&mut cursor)
.expect("form should succeed after re-prompt");
assert_eq!(result.get("name").map(|s| s.as_str()), Some("Bob"));
}
// ---------------------------------------------------------------------------
// Confirm field: default false, enter pressed → "false"
// ---------------------------------------------------------------------------
#[test]
fn test_form_confirm_default_false() {
let input_bytes: &[u8] = b"\n"; // blank → use default (false)
let mut cursor = Cursor::new(input_bytes);
let result = Form::new()
.field(FormField::confirm("proceed", "Continue?", false))
.run_with_input(&mut cursor)
.expect("form should succeed");
assert_eq!(result.get("proceed").map(|s| s.as_str()), Some("false"));
}
// ---------------------------------------------------------------------------
// Select field: pick option 2 (0-indexed 1)
// ---------------------------------------------------------------------------
#[test]
fn test_form_select_field() {
// "banana" is the second option
let input_bytes: &[u8] = b"banana\n";
let mut cursor = Cursor::new(input_bytes);
let result = Form::new()
.field(FormField::select(
"fruit",
"Pick a fruit",
vec![
"apple".to_string(),
"banana".to_string(),
"cherry".to_string(),
],
))
.run_with_input(&mut cursor)
.expect("form should succeed");
assert_eq!(result.get("fruit").map(|s| s.as_str()), Some("banana"));
}
// ---------------------------------------------------------------------------
// run_with_input returns correct HashMap keys
// ---------------------------------------------------------------------------
#[test]
fn test_form_returns_all_keys() {
let input_bytes: &[u8] = b"Alice\ny\n";
let mut cursor = Cursor::new(input_bytes);
let result: HashMap<String, String> = Form::new()
.field(FormField::input("username", "Username:"))
.field(FormField::confirm("agree", "Agree to terms?", true))
.run_with_input(&mut cursor)
.expect("form should succeed");
assert!(
result.contains_key("username"),
"result must contain 'username'"
);
assert!(result.contains_key("agree"), "result must contain 'agree'");
}
// ---------------------------------------------------------------------------
// Accessible mode: force plain output, same result
// ---------------------------------------------------------------------------
#[test]
fn test_form_accessible_mode_same_result() {
let input_bytes: &[u8] = b"Carol\n";
let mut cursor = Cursor::new(input_bytes);
// accessible(true) forces plain text prompts (no ANSI styling)
let result = Form::new()
.accessible(true)
.field(FormField::input("name", "Your name?"))
.run_with_input(&mut cursor)
.expect("accessible form should succeed");
assert_eq!(result.get("name").map(|s| s.as_str()), Some("Carol"));
}
// ---------------------------------------------------------------------------
// Auto-detect accessible: NO_COLOR set → same flow (just no styling)
// ---------------------------------------------------------------------------
#[test]
fn test_form_auto_accessible_no_color_env() {
// We test the auto-detection logic here; the result should still be correct.
// We set NO_COLOR in env before construction, then clear it after.
// (In CI NO_COLOR may already be set; this is fine.)
let input_bytes: &[u8] = b"Dave\n";
let mut cursor = Cursor::new(input_bytes);
std::env::set_var("NO_COLOR", "1");
let result = Form::new()
.field(FormField::input("name", "Your name?"))
.run_with_input(&mut cursor);
std::env::remove_var("NO_COLOR");
let result = result.expect("form should succeed with NO_COLOR set");
assert_eq!(result.get("name").map(|s| s.as_str()), Some("Dave"));
}