tests.rsannotatedtests.rssource272 lines · 11.4 KB · raw
1//! The chooser's rules, through the state that acts on them.
2
3use std::collections::BTreeMap;
4
5use whiskers_core::wire::VoiceEntry;
6use whiskers_core::{Age, Audience, CharacterId, Child, ChildName, VoiceId, characters};
7
8use super::*;
9
10fn who(id: &str) -> CharacterId {
11    CharacterId::new(id).unwrap()
12}
13
14fn voice(id: &str) -> VoiceId {
15    VoiceId::new(id).unwrap()
16}
17
18fn ada() -> Audience {
19    Audience::new(Some(&Child { name: ChildName::new("Ada").unwrap(), age: Age::new(6).unwrap() }))
20}
21
22fn listed() -> Vec<VoiceEntry> {
23    vec![
24        VoiceEntry { id: voice("v-a"), name: "Alice".into() },
25        VoiceEntry { id: voice("v-b"), name: "Bella".into() },
26        VoiceEntry { id: voice("v-c"), name: "Cora".into() },
27    ]
28}
29
30/// The household has chosen the grey cat (no voice of its own) and given the panda Bella's voice; the base voice
31/// is Alice's.
32fn chooser() -> Chooser {
33    let saved = BTreeMap::from([(who("panda"), voice("v-b"))]);
34    Chooser::new(ada(), who("grey-cat"), saved, listed(), Some(voice("v-a")))
35}
36
37fn open() -> Chooser {
38    let mut c = chooser();
39    c.input(Input::Open);
40    c
41}
42
43#[test]
44fn the_characters_wrap_around_both_ways() {
45    let all: Vec<CharacterId> = characters().collect();
46    let mut c = open();
47    assert_eq!(c.view().character, all[0]);
48    for expected in all.iter().skip(1).chain(std::iter::once(&all[0])) {
49        assert_eq!(c.input(Input::Right).view.character, *expected);
50    }
51    assert_eq!(c.input(Input::Left).view.character, *all.last().unwrap(), "left of the first is the last");
52    for expected in all.iter().rev().skip(1) {
53        assert_eq!(c.input(Input::Left).view.character, *expected);
54    }
55}
56
57#[test]
58fn the_drawn_characters_come_first_in_order_and_the_bunny_is_one_of_them() {
59    let first: Vec<String> = characters().take(4).map(|c| c.to_string()).collect();
60    assert_eq!(first, ["grey-cat", "ginger-cat", "bunny", "panda"]);
61    let mut c = open();
62    c.input(Input::Right);
63    assert_eq!(c.input(Input::Right).view.character, who("bunny"));
64    assert_eq!(c.input(Input::Right).view.character, who("panda"));
65    assert_eq!(c.input(Input::Left).view.character, who("bunny"));
66    assert_eq!(c.input(Input::Left).view.character, who("ginger-cat"));
67}
68
69#[test]
70fn the_bunny_has_its_own_voice_slot_and_saves_it_like_any_character() {
71    let mut c = open();
72    c.input(Input::Right);
73    c.input(Input::Right);
74    assert_eq!(c.view().character, who("bunny"));
75    assert_eq!(c.view().voice, None, "no voice of its own: the base voice shows");
76    c.input(Input::VoiceRight);
77    let out = c.input(Input::Check);
78    assert_eq!(out.effects, [ShellEffect::Save { character: who("bunny"), voice: Some(voice("v-b")) }, ShellEffect::Leave]);
79    assert_eq!(c.saved().0, who("bunny"));
80    assert_eq!(c.saved().1.get(&who("bunny")), Some(&voice("v-b")));
81    assert_eq!(c.saved().1.get(&who("panda")), Some(&voice("v-b")), "another character's voice is untouched");
82}
83
84#[test]
85fn the_voices_wrap_around_both_ways() {
86    let mut c = open();
87    assert_eq!(c.view().voice, None, "the grey cat has none of its own: the base voice shows, by name");
88    assert_eq!(c.view().voice_name.as_deref(), Some("Alice"));
89    assert_eq!(c.input(Input::VoiceLeft).view.voice, Some(voice("v-c")), "left of the first is the last");
90    assert_eq!(c.input(Input::VoiceRight).view.voice, Some(voice("v-a")));
91    for expected in ["v-b", "v-c", "v-a"] {
92        assert_eq!(c.input(Input::VoiceRight).view.voice, Some(voice(expected)));
93    }
94}
95
96#[test]
97fn a_preview_does_not_save() {
98    let mut c = open();
99    c.input(Input::Right);
100    let out = c.input(Input::VoiceRight);
101    assert!(out.view.unsaved);
102    assert!(out.effects.iter().all(|e| !matches!(e, ShellEffect::Save { .. })), "{:?}", out.effects);
103    assert_eq!(c.saved().0, who("grey-cat"));
104    assert_eq!(c.saved().1, &BTreeMap::from([(who("panda"), voice("v-b"))]), "what is saved is as it was");
105}
106
107#[test]
108fn the_check_saves_exactly_the_shown_character_and_its_voice() {
109    let mut c = open();
110    c.input(Input::Right);
111    c.input(Input::Right);
112    c.input(Input::Right);
113    assert_eq!(c.view().character, who("panda"));
114    assert_eq!(c.view().voice, Some(voice("v-b")), "its saved voice");
115    c.input(Input::VoiceRight);
116    let out = c.input(Input::Check);
117    assert_eq!(out.effects, [ShellEffect::Save { character: who("panda"), voice: Some(voice("v-c")) }, ShellEffect::Leave]);
118    assert_eq!(c.saved().0, who("panda"));
119    assert_eq!(c.saved().1, &BTreeMap::from([(who("panda"), voice("v-c"))]), "its voice, and no other character's");
120    assert!(!out.view.editing && !out.view.unsaved);
121}
122
123#[test]
124fn saving_a_character_with_the_base_voice_keeps_no_voice_for_it() {
125    let mut c = open();
126    c.input(Input::Right);
127    assert_eq!(c.view().character, who("ginger-cat"));
128    let out = c.input(Input::Check);
129    assert_eq!(out.effects, [ShellEffect::Save { character: who("ginger-cat"), voice: None }, ShellEffect::Leave]);
130    assert_eq!(c.saved().1.get(&who("ginger-cat")), None);
131}
132
133#[test]
134fn the_cross_leaves_everything_as_it_was() {
135    let mut c = open();
136    c.input(Input::Right);
137    c.input(Input::Right);
138    c.input(Input::VoiceRight);
139    let out = c.input(Input::Cancel);
140    assert_eq!(out.effects, [ShellEffect::Discard, ShellEffect::Leave]);
141    assert_eq!(c.saved().0, who("grey-cat"));
142    assert_eq!(c.saved().1, &BTreeMap::from([(who("panda"), voice("v-b"))]));
143    assert!(!out.view.editing && !out.view.unsaved);
144    assert_eq!((out.view.character, out.view.voice), (who("grey-cat"), None), "what was shown is what is saved again");
145    assert_eq!(c.input(Input::Open).view.character, who("grey-cat"), "and opening again starts from it");
146}
147
148#[test]
149fn the_check_with_nothing_changed_just_closes() {
150    let mut c = open();
151    let out = c.input(Input::Check);
152    assert_eq!(out.effects, [ShellEffect::Leave], "nothing to save");
153    let mut c = open();
154    c.input(Input::Right);
155    c.input(Input::Left);
156    assert_eq!(c.input(Input::Check).effects, [ShellEffect::Leave], "back where it began is no change");
157}
158
159#[test]
160fn changing_character_shows_that_characters_saved_voice_and_not_the_previewed_one() {
161    let mut c = open();
162    assert_eq!(c.input(Input::VoiceRight).view.voice, Some(voice("v-b")), "previewing Bella on the grey cat");
163    let out = c.input(Input::Right);
164    assert_eq!(out.view.character, who("ginger-cat"));
165    assert_eq!(out.view.voice, None, "the ginger cat has none saved: not the Bella that was previewed");
166    let out = c.input(Input::Right);
167    assert_eq!((out.view.character, out.view.voice), (who("bunny"), None), "the bunny has none saved either");
168    let out = c.input(Input::Right);
169    assert_eq!((out.view.character, out.view.voice), (who("panda"), Some(voice("v-b"))), "the panda shows its own saved voice");
170    let out = c.input(Input::VoiceRight);
171    assert_eq!(out.view.voice, Some(voice("v-c")));
172    assert_eq!(c.input(Input::Left).view.voice, None, "and stepping on leaves that preview behind");
173}
174
175#[test]
176fn what_is_said_for_each_effect() {
177    let mut c = open();
178    // A character changes: "Hi <name>", in that character's saved voice, which is the base voice for one with none.
179    let out = c.input(Input::Right);
180    assert_eq!(out.effects, [ShellEffect::PreviewCharacter(Utterance { text: "Hi Ada".into(), voice: None })]);
181    let out = c.input(Input::Right);
182    assert_eq!(out.effects, [ShellEffect::PreviewCharacter(Utterance { text: "Hi Ada".into(), voice: None })], "the bunny has none: the base voice");
183    let out = c.input(Input::Right);
184    assert_eq!(out.effects, [ShellEffect::PreviewCharacter(Utterance { text: "Hi Ada".into(), voice: Some(voice("v-b")) })], "panda's own voice");
185    // A voice is chosen: its name, in that voice.
186    let out = c.input(Input::VoiceRight);
187    assert_eq!(out.effects, [ShellEffect::PreviewVoice(Utterance { text: "Hi Ada, this voice is called Cora".into(), voice: Some(voice("v-c")) })]);
188    // Opening, saving and leaving say nothing.
189    let mut c = chooser();
190    assert_eq!(c.input(Input::Open).effects, [ShellEffect::Enter]);
191}
192
193#[test]
194fn a_character_is_never_lent_another_characters_voice() {
195    let mut c = open();
196    c.input(Input::Right);
197    c.input(Input::Right);
198    c.input(Input::Right);
199    let out = c.input(Input::Right);
200    assert_eq!(out.view.character, who("koala"));
201    assert_eq!(out.effects, [ShellEffect::PreviewCharacter(Utterance { text: "Hi Ada".into(), voice: None })], "the koala has none: the base voice, not the panda's");
202}
203
204#[test]
205fn a_saved_voice_the_service_no_longer_lists_is_heard_in_the_base_voice() {
206    let saved = BTreeMap::from([(who("panda"), voice("gone"))]);
207    let mut c = Chooser::new(ada(), who("grey-cat"), saved, listed(), Some(voice("v-a")));
208    c.input(Input::Open);
209    c.input(Input::Right);
210    let out = c.input(Input::Right);
211    assert_eq!(out.effects, [ShellEffect::PreviewCharacter(Utterance { text: "Hi Ada".into(), voice: None })]);
212}
213
214#[test]
215fn inputs_that_mean_nothing_say_so_and_change_nothing() {
216    let mut c = chooser();
217    for input in [Input::Left, Input::Right, Input::VoiceLeft, Input::VoiceRight, Input::Check, Input::Cancel] {
218        let out = c.input(input);
219        assert!(out.effects.is_empty(), "{input:?} while closed");
220        assert_eq!(out.notes, [Note::NotOpen], "{input:?}");
221        assert!(!out.view.editing);
222    }
223    let mut c = open();
224    c.input(Input::Right);
225    let out = c.input(Input::Open);
226    assert!(out.effects.is_empty());
227    assert_eq!(out.notes, [Note::AlreadyOpen]);
228    assert_eq!(out.view.character, who("ginger-cat"), "opening again does not move her back");
229}
230
231#[test]
232fn with_no_voices_listed_the_voice_arrows_keep_the_voice_she_has() {
233    let saved = BTreeMap::from([(who("panda"), voice("v-b"))]);
234    let mut c = Chooser::new(ada(), who("panda"), saved, vec![], Some(voice("v-a")));
235    let out = c.input(Input::Open);
236    assert!(!out.view.voices_to_step);
237    for input in [Input::VoiceLeft, Input::VoiceRight] {
238        let out = c.input(input);
239        assert!(out.effects.is_empty());
240        assert_eq!(out.notes, [Note::NoVoices]);
241        assert_eq!(out.view.voice, Some(voice("v-b")));
242    }
243    let out = c.input(Input::Right);
244    assert_eq!(out.effects.len(), 1, "the characters still step");
245}
246
247#[test]
248fn every_transition_leaves_a_trace_of_rule_names_and_counts_and_never_a_word_she_hears() {
249    let mut c = open();
250    let out = c.input(Input::VoiceRight);
251    let joined = out.trace.join("\n");
252    assert!(joined.contains(r#""name":"next voice""#), "{joined}");
253    assert!(joined.contains(r#""name":"a voice says its name""#), "{joined}");
254    for line in &out.trace {
255        let parsed: serde_json::Value = serde_json::from_str(line).unwrap_or_else(|e| panic!("{line}: {e}"));
256        assert!(parsed["event"].is_string());
257    }
258    for secret in ["Ada", "Bella", "Cora", "Alice", "this voice is called"] {
259        assert!(!joined.contains(secret), "{secret} is in the trace");
260    }
261    let kinds: Vec<String> = out.trace.iter().map(|l| serde_json::from_str::<serde_json::Value>(l).unwrap()["event"].as_str().unwrap().to_owned()).collect();
262    assert_eq!(kinds.first().map(String::as_str), Some("asked"));
263    assert_eq!(kinds.last().map(String::as_str), Some("done"));
264    assert_eq!(kinds.iter().filter(|k| *k == "fired").count(), 2, "one rule moved the voice, the next said its name");
265}
266
267#[test]
268fn an_utterance_prints_lengths_and_not_the_words() {
269    let u = Utterance { text: "Hi Ada".into(), voice: Some(voice("secret-voice-id")) };
270    let shown = format!("{u:?}");
271    assert!(!shown.contains("Ada") && !shown.contains("secret-voice-id"), "{shown}");
272}