use crate::{html::Location, SubplotError};
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ScenarioStep {
kind: StepKind,
keyword: String,
text: String,
origin: Location,
}
impl ScenarioStep {
pub fn new(kind: StepKind, keyword: &str, text: &str, origin: Location) -> ScenarioStep {
ScenarioStep {
kind,
keyword: keyword.to_owned(),
text: text.to_owned(),
origin,
}
}
pub fn kind(&self) -> StepKind {
self.kind
}
pub fn keyword(&self) -> &str {
&self.keyword
}
pub fn text(&self) -> &str {
&self.text
}
pub fn new_from_str(
text: &str,
default: Option<StepKind>,
origin: Location,
) -> Result<ScenarioStep, SubplotError> {
if text.trim_start() != text {
return Err(SubplotError::NotAtBoln(text.into()));
}
let mut words = text.split_whitespace();
let keyword = match words.next() {
Some(s) => s,
_ => return Err(SubplotError::NoStepKeyword(text.to_string())),
};
let kind = match keyword.to_ascii_lowercase().as_str() {
"given" => StepKind::Given,
"when" => StepKind::When,
"then" => StepKind::Then,
"and" => default.ok_or(SubplotError::ContinuationTooEarly)?,
"but" => default.ok_or(SubplotError::ContinuationTooEarly)?,
_ => return Err(SubplotError::UnknownStepKind(keyword.to_string())),
};
let mut joined = String::new();
for word in words {
joined.push_str(word);
joined.push(' ');
}
if joined.len() > 1 {
joined.pop();
}
Ok(ScenarioStep::new(kind, keyword, &joined, origin))
}
pub(crate) fn origin(&self) -> &Location {
&self.origin
}
}
impl fmt::Display for ScenarioStep {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {}", self.keyword(), self.text())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum StepKind {
Given,
When,
Then,
}
impl fmt::Display for StepKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
StepKind::Given => "given",
StepKind::When => "when",
StepKind::Then => "then",
};
write!(f, "{s}")
}
}
#[cfg(test)]
mod test {
use crate::html::Location;
use super::{ScenarioStep, StepKind, SubplotError};
#[test]
fn parses_given() {
let step =
ScenarioStep::new_from_str("GIVEN I am Tomjon", None, Location::Unknown).unwrap();
assert_eq!(step.kind(), StepKind::Given);
assert_eq!(step.text(), "I am Tomjon");
}
#[test]
fn parses_given_with_extra_spaces() {
let step =
ScenarioStep::new_from_str("given I am Tomjon ", None, Location::Unknown)
.unwrap();
assert_eq!(step.kind(), StepKind::Given);
assert_eq!(step.text(), "I am Tomjon");
}
#[test]
fn parses_when() {
let step =
ScenarioStep::new_from_str("when I declare myself king", None, Location::Unknown)
.unwrap();
assert_eq!(step.kind(), StepKind::When);
assert_eq!(step.text(), "I declare myself king");
}
#[test]
fn parses_then() {
let step = ScenarioStep::new_from_str("thEN everyone accepts it", None, Location::Unknown)
.unwrap();
assert_eq!(step.kind(), StepKind::Then);
assert_eq!(step.text(), "everyone accepts it");
}
#[test]
fn parses_and() {
let step = ScenarioStep::new_from_str(
"and everyone accepts it",
Some(StepKind::Then),
Location::Unknown,
)
.unwrap();
assert_eq!(step.kind(), StepKind::Then);
assert_eq!(step.text(), "everyone accepts it");
}
#[test]
fn fails_to_parse_and() {
let step = ScenarioStep::new_from_str("and everyone accepts it", None, Location::Unknown);
assert!(step.is_err());
match step.err() {
None => unreachable!(),
Some(SubplotError::ContinuationTooEarly) => (),
Some(e) => panic!("Incorrect error: {}", e),
}
}
}