summaryrefslogtreecommitdiffstats
path: root/tests/client_core.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-28 22:31:18 +0100
committersalmonik <salmonik@tuta.io>2026-10-28 22:31:18 +0100
commit828470137cb4e806c31a26fa741d5d67c64f98c2 (patch)
tree45dbe92af0f61166559d73c21fabd7759eca4b11 /tests/client_core.rs
parentv0.2.3: add ClientCore and fix the game board rendering (diff)
downloadzagan-bet-828470137cb4e806c31a26fa741d5d67c64f98c2.tar.gz
zagan-bet-828470137cb4e806c31a26fa741d5d67c64f98c2.zip
v0.2.4: fix command UX, completion wrapping and auth prompt cancellation
Diffstat (limited to '')
-rw-r--r--tests/client_core.rs174
1 files changed, 173 insertions, 1 deletions
diff --git a/tests/client_core.rs b/tests/client_core.rs
index 3dbbfe5..79b3dd8 100644
--- a/tests/client_core.rs
+++ b/tests/client_core.rs
@@ -5,6 +5,7 @@
5//! player would see, not about ANSI sequences. 5//! player would see, not about ANSI sequences.
6 6
7use zagan_bet::client::core::{ClientCore, Effect, InteractiveCommand, Key}; 7use zagan_bet::client::core::{ClientCore, Effect, InteractiveCommand, Key};
8use zagan_bet::client::ui::command_names;
8 9
9#[derive(Default)] 10#[derive(Default)]
10struct Screen { 11struct Screen {
@@ -12,6 +13,7 @@ struct Screen {
12 region_start: Option<usize>, 13 region_start: Option<usize>,
13 region_len: usize, 14 region_len: usize,
14 prompt: String, 15 prompt: String,
16 prompt_cursor: u16,
15} 17}
16 18
17impl Screen { 19impl Screen {
@@ -40,7 +42,10 @@ impl Screen {
40 self.region_start = None; 42 self.region_start = None;
41 self.region_len = 0; 43 self.region_len = 0;
42 } 44 }
43 Effect::SetPrompt { text, .. } => self.prompt = text.clone(), 45 Effect::SetPrompt { text, cursor_col } => {
46 self.prompt = text.clone();
47 self.prompt_cursor = *cursor_col;
48 }
44 Effect::SendLine(line) => sent.push(line.clone()), 49 Effect::SendLine(line) => sent.push(line.clone()),
45 Effect::Interactive(command) => interactive.push(command.clone()), 50 Effect::Interactive(command) => interactive.push(command.clone()),
46 Effect::Quit 51 Effect::Quit
@@ -214,3 +219,170 @@ fn an_empty_reply_is_an_error_not_a_silent_spin() {
214 assert_eq!(h.board().len(), 7); 219 assert_eq!(h.board().len(), 7);
215 assert!(h.board()[4].contains("No response from server")); 220 assert!(h.board()[4].contains("No response from server"));
216} 221}
222
223#[test]
224fn empty_enter_does_not_print_help() {
225 let mut h = Harness::new();
226 h.line("games");
227 let lines_before = h.screen.lines.clone();
228
229 h.key(Key::Enter);
230
231 assert_eq!(h.screen.lines, lines_before);
232 assert_eq!(h.screen.prompt, "");
233}
234
235#[test]
236fn history_does_not_duplicate_consecutive_commands() {
237 let mut h = Harness::new();
238 h.line("games");
239 h.line("games");
240
241 h.key(Key::Up);
242 assert_eq!(h.screen.prompt, "games");
243 h.key(Key::Up);
244 assert_eq!(h.screen.prompt, "games");
245}
246
247#[test]
248fn history_recalls_previous_commands() {
249 let mut h = Harness::new();
250 h.line("games");
251 h.line("help");
252
253 h.key(Key::Up);
254 assert_eq!(h.screen.prompt, "help");
255 h.key(Key::Up);
256 assert_eq!(h.screen.prompt, "games");
257}
258
259#[test]
260fn tab_completes_a_unique_command() {
261 let mut h = Harness::new();
262 for c in "con".chars() {
263 h.key(Key::Char(c));
264 }
265
266 h.key(Key::Tab);
267
268 assert_eq!(h.screen.prompt, "connect");
269 assert_eq!(h.screen.prompt_cursor, 9);
270}
271
272#[test]
273fn tab_completion_list_wraps() {
274 let mut h = Harness::new();
275
276 h.key(Key::Tab);
277
278 assert!(h
279 .screen
280 .lines
281 .iter()
282 .any(|line| line == "Possible completions:"));
283 for line in h
284 .screen
285 .lines
286 .iter()
287 .skip_while(|line| line.as_str() != "Possible completions:")
288 .skip(1)
289 {
290 assert!(
291 line.len() <= 76,
292 "completion line is too wide ({}): {line:?}",
293 line.len()
294 );
295 }
296 assert_eq!(h.screen.prompt, "");
297}
298
299#[test]
300fn play_without_a_bet_uses_the_default() {
301 let mut h = Harness::new();
302 h.line("play slots");
303
304 assert_eq!(h.board().len(), 7);
305 assert!(h.board()[1].contains("Bet: 10 PLN"));
306}
307
308#[test]
309fn an_invalid_bet_does_not_start_a_game() {
310 let mut h = Harness::new();
311 h.line("play slots 5");
312
313 assert!(h.board().is_empty());
314 assert!(h
315 .screen
316 .lines
317 .iter()
318 .any(|line| line.contains("Bet must be at least 10 PLN")));
319}
320
321#[test]
322fn help_lists_every_command() {
323 let mut h = Harness::new();
324 h.line("help");
325
326 let transcript = h.screen.lines.join("\n");
327 for command in command_names() {
328 assert!(
329 transcript.contains(command),
330 "help is missing '{command}'"
331 );
332 }
333}
334
335#[test]
336fn send_keeps_the_whole_message() {
337 let mut h = Harness::new();
338 h.line("send hello world");
339
340 assert_eq!(h.sent.last().map(String::as_str), Some("hello world"));
341}
342
343#[test]
344fn play_requires_a_connection_and_a_login() {
345 let mut h = Harness::new();
346 h.core.set_connected(false);
347 h.line("play slots");
348 assert!(h
349 .screen
350 .lines
351 .iter()
352 .any(|line| line.contains("Not connected")));
353
354 let mut h = Harness::new();
355 h.core.set_logged_in(None);
356 h.line("play slots");
357 assert!(h
358 .screen
359 .lines
360 .iter()
361 .any(|line| line.contains("Not logged in")));
362}
363
364#[test]
365fn ctrl_c_quits_from_the_prompt() {
366 let mut core = ClientCore::new();
367 assert_eq!(core.handle_key(Key::CtrlC), vec![Effect::Quit]);
368}
369
370#[test]
371fn ctrl_c_quits_from_a_game() {
372 let mut h = Harness::new();
373 h.enter_slots("50");
374
375 assert_eq!(h.core.handle_key(Key::CtrlC), vec![Effect::Quit]);
376}
377
378#[test]
379fn prompt_cursor_tracks_the_input() {
380 let mut h = Harness::new();
381 h.key(Key::Char('a'));
382 assert_eq!(h.screen.prompt, "a");
383 assert_eq!(h.screen.prompt_cursor, 3);
384
385 h.key(Key::Backspace);
386 assert_eq!(h.screen.prompt, "");
387 assert_eq!(h.screen.prompt_cursor, 2);
388}