1pub mod plan;
5pub mod report;
6
7pub use plan::{Because, Check, Origin, Plan, PlanStep, Planned, coverage, coverage_plans, plan_stories, untested};
8pub use report::{CheckResult, PlanResult, Report, Status, StepResult, Verdict, load_results, load_results_for, results_file};
9
10#[cfg(test)]
11mod tests {
12 use super::*;
13 use expectation_model::Suite;
14
15 const SUITE: &str = "
16Vocabulary:
17 State: the kettle is off | heating | dry
18 State: the dial is 0..100 %
19 State: the help screen is shown | hidden
20 Event: the user presses reset
21 Event: the user presses help
22 Event: the water runs out
23
24Background:
25 Given the kettle is off
26 And the dial is 0 %
27
28Feature: Heating
29 Expectation: Turning the dial up starts the kettle
30 Given the kettle is off
31 When the dial is above 55 %
32 Then the kettle is heating
33
34 Expectation: Running dry stops the kettle
35 Given the kettle is heating
36 When the water runs out
37 Then the kettle is dry
38
39 Expectation: Reset turns the kettle off
40 When the user presses reset
41 Then the kettle is off
42
43 Expectation: Help opens the help screen
44 Given the help screen is hidden
45 When the user presses help
46 Then the help screen is shown
47";
48
49 fn planned(stories: &str) -> (Suite, expectation_graph::Analysis, Planned) {
50 let src = format!("{SUITE}{stories}");
51 let l = expectation_language::load_sources("t", &[("t.expect", &src)]);
52 assert!(!l.has_errors(), "{}", l.render_diagnostics());
53 let a = expectation_graph::analyze(&l.suite);
54 let p = plan_stories(&l.suite, &a);
55 (l.suite, a, p)
56 }
57
58 fn codes(p: &Planned) -> Vec<&'static str> {
59 p.diagnostics.iter().map(|d| d.code).collect()
60 }
61
62 #[test]
63 fn checks_come_from_the_expectations() {
64 let (s, _, p) = planned(
65 "
66Story: Boil dry
67 When the dial is 80 %
68 When the water runs out
69 Then the kettle is dry
70",
71 );
72 assert!(codes(&p).is_empty(), "{:?}", codes(&p));
73 let plan = &p.plans[0];
74 assert_eq!(plan.start_checks.len(), 2);
75 let first = &plan.steps[0];
77 assert_eq!(first.fired.len(), 1);
78 assert_eq!(s.vocabulary.describe(&first.state), "the kettle is heating and the dial is 80 %");
79 assert!(matches!(first.checks[0].because, Because::Expectation(_)));
80 assert_eq!(plan.steps[1].checks.len(), 1, "the story's Then duplicates the promise");
81 }
82
83 #[test]
84 fn stories_cant_rely_on_unspecified_or_contradicted_behaviour() {
85 let (_, _, p) = planned("Story: S\n When the water runs out\n");
86 assert_eq!(codes(&p), vec!["unspecified"]);
87
88 let (_, _, p) = planned("Story: S\n When the dial is 80 %\n Then the kettle is off\n");
89 assert_eq!(codes(&p), vec!["contradicts"]);
90
91 let (_, _, p) = planned("Story: S\n When the dial is at least 50 %\n");
93 assert_eq!(codes(&p), vec!["unclear-step"]);
94
95 let (_, _, p) = planned("Story: S\n When the user presses help\n");
97 assert_eq!(codes(&p), vec!["unclear-step"]);
98 let (_, _, p) = planned(
99 "Story: S\n Given the help screen is hidden\n When the user presses help\n Then the dial is 0 %\n",
100 );
101 assert!(codes(&p).is_empty());
102
103 let (_, _, p) = planned("Story: S\n When the dial is 80 %\n Then the help screen is hidden\n");
104 assert_eq!(codes(&p), vec!["unpromised"]);
105 }
106
107 #[test]
108 fn coverage_fills_the_gaps() {
109 let (s, a, p) = planned("Story: S\n When the dial is 80 %\n");
110 let cov = coverage(&s, &p.plans);
111 assert_eq!(cov.iter().filter(|c| c.is_empty()).count(), 3);
112 assert_eq!(untested(&s, &a, &p.plans).len(), 3);
113 let generated = coverage_plans(&s, &a, &p.plans);
114 let titles: Vec<&str> = generated.iter().map(|p| p.title.as_str()).collect();
115 assert_eq!(
116 titles,
117 vec![
118 "Reaching «Running dry stops the kettle»",
119 "Reaching «Reset turns the kettle off»",
120 "Reaching «Help opens the help screen»"
121 ]
122 );
123 assert_eq!(generated[0].steps.len(), 2);
125 assert_eq!(generated[2].given.last().unwrap().text, "the help screen is hidden");
127 }
128}