1use std::collections::HashSet;
2
3use crate::{html::Location, ScenarioStep};
4use lazy_static::lazy_static;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Serialize, Deserialize)]
14pub struct Scenario {
15 title: String,
16 origin: Location,
17 steps: Vec<ScenarioStep>,
18 labels: HashSet<String>,
19}
20
21impl Scenario {
22 pub fn new(title: &str, origin: Location) -> Scenario {
26 Scenario {
27 title: title.to_string(),
28 origin,
29 steps: vec![],
30 labels: Default::default(),
31 }
32 }
33
34 pub fn add_label(&mut self, label: &str) {
36 self.labels.insert(String::from(label));
37 }
38
39 pub fn labels(&self) -> impl Iterator<Item = &str> {
41 self.labels.iter().map(String::as_str)
42 }
43
44 pub fn has_label(&self, label: &str) -> bool {
46 self.labels.contains(label)
47 }
48
49 pub fn title(&self) -> &str {
51 &self.title
52 }
53
54 pub fn has_steps(&self) -> bool {
56 !self.steps.is_empty()
57 }
58
59 pub fn steps(&self) -> &[ScenarioStep] {
61 &self.steps
62 }
63
64 pub fn add(&mut self, step: &ScenarioStep) {
66 self.steps.push(step.clone());
67 }
68
69 pub(crate) fn origin(&self) -> &Location {
70 &self.origin
71 }
72}
73
74#[cfg(test)]
75mod test {
76 use super::Scenario;
77 use crate::html::Location;
78 use crate::ScenarioStep;
79 use crate::StepKind;
80
81 #[test]
82 fn has_title() {
83 let scen = Scenario::new("title", Location::Unknown);
84 assert_eq!(scen.title(), "title");
85 }
86
87 #[test]
88 fn has_no_steps_initially() {
89 let scen = Scenario::new("title", Location::Unknown);
90 assert_eq!(scen.steps().len(), 0);
91 }
92
93 #[test]
94 fn adds_step() {
95 let mut scen = Scenario::new("title", Location::Unknown);
96 let step = ScenarioStep::new(StepKind::Given, "and", "foo", Location::Unknown);
97 scen.add(&step);
98 assert_eq!(scen.steps(), &[step]);
99 }
100}
101
102#[derive(Clone)]
107pub struct ScenarioFilterElement {
108 has: Vec<String>,
109 lacks: Vec<String>,
110 include: bool,
111}
112
113impl ScenarioFilterElement {
114 fn everything() -> Self {
115 Self {
116 has: Vec::new(),
117 lacks: Vec::new(),
118 include: true,
119 }
120 }
121
122 pub fn new(include: bool, filter: &str) -> Self {
130 let mut ret = Self {
131 has: Vec::new(),
132 lacks: Vec::new(),
133 include,
134 };
135
136 for label in filter.split(',').map(str::trim) {
137 if let Some(label) = label.strip_prefix('-') {
138 ret.lacks.push(label.into());
139 } else {
140 ret.has.push(label.into());
141 }
142 }
143
144 ret
145 }
146
147 fn includes(&self, scenario: &Scenario) -> bool {
148 let has = self.has.iter().all(|s| scenario.labels.contains(s));
149 let lacks = self.lacks.iter().any(|s| scenario.labels.contains(s));
150 let matched = has & !lacks;
152
153 !(matched ^ self.include)
160 }
161}
162
163#[derive(Clone)]
168pub struct ScenarioFilter {
169 elements: Vec<ScenarioFilterElement>,
170}
171
172lazy_static! {
173 pub static ref SCENARIO_FILTER_EVERYTHING: ScenarioFilter = ScenarioFilter::everything();
175 pub static ref SCENARIO_FILTER_NOTHING: ScenarioFilter = ScenarioFilter::nothing();
177}
178
179impl ScenarioFilter {
180 fn everything() -> Self {
181 Self {
182 elements: vec![ScenarioFilterElement::everything()],
183 }
184 }
185
186 fn nothing() -> Self {
187 Self { elements: vec![] }
188 }
189
190 pub fn push(&mut self, element: ScenarioFilterElement) {
192 self.elements.push(element);
193 }
194
195 pub fn includes(&self, scenario: &Scenario) -> bool {
197 !self.elements.is_empty() && self.elements.iter().all(|e| e.includes(scenario))
198 }
199}
200
201#[cfg(test)]
202mod filtertest {
203 use crate::html::Location;
204
205 use super::{Scenario, ScenarioFilter, ScenarioFilterElement};
206
207 fn scenario_none() -> Scenario {
208 Scenario::new("whatever", Location::unknown())
209 }
210
211 fn scenario_slow() -> Scenario {
212 let mut ret = Scenario::new("slow", Location::unknown());
213 ret.add_label("slow");
214 ret
215 }
216
217 fn scenario_slow_important() -> Scenario {
218 let mut ret = Scenario::new("slow-important", Location::unknown());
219 ret.add_label("slow");
220 ret.add_label("important");
221 ret
222 }
223
224 fn scenario_fast() -> Scenario {
225 let mut ret = Scenario::new("fast", Location::unknown());
226 ret.add_label("fast");
227 ret
228 }
229
230 fn scenario_fast_pointless() -> Scenario {
231 let mut ret = Scenario::new("fast-pointless", Location::unknown());
232 ret.add_label("fast");
233 ret.add_label("pointless");
234 ret
235 }
236
237 fn all_scenarios() -> Vec<Scenario> {
238 vec![
239 scenario_none(),
240 scenario_slow(),
241 scenario_fast(),
242 scenario_slow_important(),
243 scenario_fast_pointless(),
244 ]
245 }
246
247 #[test]
248 fn include_all() {
249 let filter = ScenarioFilter::everything();
250 let scenarios = all_scenarios();
251 assert_eq!(
252 scenarios.len(),
253 scenarios.iter().filter(|s| filter.includes(s)).count()
254 );
255 }
256
257 #[test]
258 fn include_none() {
259 let filter = ScenarioFilter::nothing();
260 assert!(!all_scenarios().into_iter().any(|s| filter.includes(&s)))
261 }
262
263 #[test]
264 fn include_fast() {
265 let mut filter = ScenarioFilter::nothing();
266 filter.push(ScenarioFilterElement::new(true, "fast"));
267 assert_eq!(
268 all_scenarios()
269 .into_iter()
270 .filter(|s| filter.includes(s))
271 .count(),
272 2
273 );
274 }
275
276 #[test]
277 fn exclude_slow() {
278 let mut filter = ScenarioFilter::everything();
279 filter.push(ScenarioFilterElement::new(false, "slow"));
280 assert_eq!(
281 all_scenarios()
282 .into_iter()
283 .filter(|s| filter.includes(s))
284 .count(),
285 3
286 );
287 }
288
289 #[test]
290 fn exclude_unimportant_slow() {
291 let mut filter = ScenarioFilter::everything();
292 filter.push(ScenarioFilterElement::new(false, "slow, -important"));
293 assert_eq!(
294 all_scenarios()
295 .into_iter()
296 .filter(|s| filter.includes(s))
297 .count(),
298 4
299 );
300 }
301}