use std::collections::HashSet;
use crate::{html::Location, ScenarioStep};
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Scenario {
title: String,
origin: Location,
steps: Vec<ScenarioStep>,
labels: HashSet<String>,
}
impl Scenario {
pub fn new(title: &str, origin: Location) -> Scenario {
Scenario {
title: title.to_string(),
origin,
steps: vec![],
labels: Default::default(),
}
}
pub fn add_label(&mut self, label: &str) {
self.labels.insert(String::from(label));
}
pub fn labels(&self) -> impl Iterator<Item = &str> {
self.labels.iter().map(String::as_str)
}
pub fn has_label(&self, label: &str) -> bool {
self.labels.contains(label)
}
pub fn title(&self) -> &str {
&self.title
}
pub fn has_steps(&self) -> bool {
!self.steps.is_empty()
}
pub fn steps(&self) -> &[ScenarioStep] {
&self.steps
}
pub fn add(&mut self, step: &ScenarioStep) {
self.steps.push(step.clone());
}
pub(crate) fn origin(&self) -> &Location {
&self.origin
}
}
#[cfg(test)]
mod test {
use super::Scenario;
use crate::html::Location;
use crate::ScenarioStep;
use crate::StepKind;
#[test]
fn has_title() {
let scen = Scenario::new("title", Location::Unknown);
assert_eq!(scen.title(), "title");
}
#[test]
fn has_no_steps_initially() {
let scen = Scenario::new("title", Location::Unknown);
assert_eq!(scen.steps().len(), 0);
}
#[test]
fn adds_step() {
let mut scen = Scenario::new("title", Location::Unknown);
let step = ScenarioStep::new(StepKind::Given, "and", "foo", Location::Unknown);
scen.add(&step);
assert_eq!(scen.steps(), &[step]);
}
}
#[derive(Clone)]
pub struct ScenarioFilterElement {
has: Vec<String>,
lacks: Vec<String>,
include: bool,
}
impl ScenarioFilterElement {
fn everything() -> Self {
Self {
has: Vec::new(),
lacks: Vec::new(),
include: true,
}
}
pub fn new(include: bool, filter: &str) -> Self {
let mut ret = Self {
has: Vec::new(),
lacks: Vec::new(),
include,
};
for label in filter.split(',').map(str::trim) {
if let Some(label) = label.strip_prefix('-') {
ret.lacks.push(label.into());
} else {
ret.has.push(label.into());
}
}
ret
}
fn includes(&self, scenario: &Scenario) -> bool {
let has = self.has.iter().all(|s| scenario.labels.contains(s));
let lacks = self.lacks.iter().any(|s| scenario.labels.contains(s));
let matched = has & !lacks;
!(matched ^ self.include)
}
}
#[derive(Clone)]
pub struct ScenarioFilter {
elements: Vec<ScenarioFilterElement>,
}
lazy_static! {
pub static ref SCENARIO_FILTER_EVERYTHING: ScenarioFilter = ScenarioFilter::everything();
pub static ref SCENARIO_FILTER_NOTHING: ScenarioFilter = ScenarioFilter::nothing();
}
impl ScenarioFilter {
fn everything() -> Self {
Self {
elements: vec![ScenarioFilterElement::everything()],
}
}
fn nothing() -> Self {
Self { elements: vec![] }
}
pub fn push(&mut self, element: ScenarioFilterElement) {
self.elements.push(element);
}
pub fn includes(&self, scenario: &Scenario) -> bool {
!self.elements.is_empty() && self.elements.iter().all(|e| e.includes(scenario))
}
}
#[cfg(test)]
mod filtertest {
use crate::html::Location;
use super::{Scenario, ScenarioFilter, ScenarioFilterElement};
fn scenario_none() -> Scenario {
Scenario::new("whatever", Location::unknown())
}
fn scenario_slow() -> Scenario {
let mut ret = Scenario::new("slow", Location::unknown());
ret.add_label("slow");
ret
}
fn scenario_slow_important() -> Scenario {
let mut ret = Scenario::new("slow-important", Location::unknown());
ret.add_label("slow");
ret.add_label("important");
ret
}
fn scenario_fast() -> Scenario {
let mut ret = Scenario::new("fast", Location::unknown());
ret.add_label("fast");
ret
}
fn scenario_fast_pointless() -> Scenario {
let mut ret = Scenario::new("fast-pointless", Location::unknown());
ret.add_label("fast");
ret.add_label("pointless");
ret
}
fn all_scenarios() -> Vec<Scenario> {
vec![
scenario_none(),
scenario_slow(),
scenario_fast(),
scenario_slow_important(),
scenario_fast_pointless(),
]
}
#[test]
fn include_all() {
let filter = ScenarioFilter::everything();
let scenarios = all_scenarios();
assert_eq!(
scenarios.len(),
scenarios.iter().filter(|s| filter.includes(s)).count()
);
}
#[test]
fn include_none() {
let filter = ScenarioFilter::nothing();
assert!(!all_scenarios().into_iter().any(|s| filter.includes(&s)))
}
#[test]
fn include_fast() {
let mut filter = ScenarioFilter::nothing();
filter.push(ScenarioFilterElement::new(true, "fast"));
assert_eq!(
all_scenarios()
.into_iter()
.filter(|s| filter.includes(s))
.count(),
2
);
}
#[test]
fn exclude_slow() {
let mut filter = ScenarioFilter::everything();
filter.push(ScenarioFilterElement::new(false, "slow"));
assert_eq!(
all_scenarios()
.into_iter()
.filter(|s| filter.includes(s))
.count(),
3
);
}
#[test]
fn exclude_unimportant_slow() {
let mut filter = ScenarioFilter::everything();
filter.push(ScenarioFilterElement::new(false, "slow, -important"));
assert_eq!(
all_scenarios()
.into_iter()
.filter(|s| filter.includes(s))
.count(),
4
);
}
}