diff options
Diffstat (limited to 'src/steps.rs')
-rw-r--r-- | src/steps.rs | 124 |
1 files changed, 113 insertions, 11 deletions
diff --git a/src/steps.rs b/src/steps.rs index ccbc588..7f5e7d4 100644 --- a/src/steps.rs +++ b/src/steps.rs @@ -1,4 +1,4 @@ -use crate::SubplotError; +use crate::{html::Location, SubplotError}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -16,15 +16,17 @@ pub struct ScenarioStep { kind: StepKind, keyword: String, text: String, + origin: Location, } impl ScenarioStep { /// Construct a new step. - pub fn new(kind: StepKind, keyword: &str, text: &str) -> ScenarioStep { + pub fn new(kind: StepKind, keyword: &str, text: &str, origin: Location) -> ScenarioStep { ScenarioStep { kind, keyword: keyword.to_owned(), text: text.to_owned(), + origin, } } @@ -50,7 +52,12 @@ impl ScenarioStep { 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() { @@ -75,7 +82,11 @@ impl ScenarioStep { if joined.len() > 1 { joined.pop(); } - Ok(ScenarioStep::new(kind, keyword, &joined)) + Ok(ScenarioStep::new(kind, keyword, &joined, origin)) + } + + pub(crate) fn origin(&self) -> &Location { + &self.origin } } @@ -85,6 +96,85 @@ impl fmt::Display for ScenarioStep { } } +/// Parse a scenario snippet into a vector of steps. +pub(crate) fn parse_scenario_snippet( + text: &str, + loc: &Location, +) -> Result<Vec<ScenarioStep>, SubplotError> { + let mut steps = vec![]; + let mut prevkind = None; + for (idx, line) in text.lines().enumerate() { + let line_loc = match loc.clone() { + Location::Known { + filename, + line, + col, + } => Location::Known { + filename, + line: line + idx, + col, + }, + Location::Unknown => Location::Unknown, + }; + if !line.trim().is_empty() { + let step = ScenarioStep::new_from_str(line, prevkind, line_loc)?; + prevkind = Some(step.kind()); + steps.push(step); + } + } + Ok(steps) +} + +#[cfg(test)] +mod test_steps_parser { + use super::{parse_scenario_snippet, Location, ScenarioStep, StepKind, SubplotError}; + use std::path::Path; + + fn parse(text: &str) -> Result<Vec<ScenarioStep>, SubplotError> { + let loc = Location::new(Path::new("test"), 1, 1); + parse_scenario_snippet(text, &loc) + } + + #[test] + fn empty_string() { + assert_eq!(parse("").unwrap(), vec![]); + } + + #[test] + fn simple() { + assert_eq!( + parse("given foo").unwrap(), + vec![ScenarioStep::new( + StepKind::Given, + "given", + "foo", + Location::new(Path::new("test"), 1, 1), + )] + ); + } + + #[test] + fn two_simple() { + assert_eq!( + parse("given foo\nthen bar\n").unwrap(), + vec![ + ScenarioStep::new( + StepKind::Given, + "given", + "foo", + Location::new(Path::new("test"), 1, 1), + ), + ScenarioStep::new( + StepKind::Then, + "then", + "bar", + Location::new(Path::new("test"), 2, 1), + ) + ] + ); + } +} + /// The kind of scenario step we have: given, when, or then. /// /// This needs to be extended if the Subplot language gets extended with other @@ -109,53 +199,65 @@ impl fmt::Display for StepKind { StepKind::When => "when", StepKind::Then => "then", }; - write!(f, "{}", s) + 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).unwrap(); + 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).unwrap(); + 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).unwrap(); + 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).unwrap(); + 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)).unwrap(); + 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); + let step = ScenarioStep::new_from_str("and everyone accepts it", None, Location::Unknown); assert!(step.is_err()); match step.err() { None => unreachable!(), |