use termcolor::ColorChoice;
#[cfg(feature = "svg")]
use std::ffi::OsStr;
use std::{env, process::Command, str};
mod color_diff;
mod config_impl;
mod parser;
#[cfg(test)]
mod tests;
mod utils;
pub use self::parser::Parsed;
#[cfg(feature = "svg")]
use crate::svg::Template;
use crate::{traits::SpawnShell, ShellOptions};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum TestOutputConfig {
Quiet,
Normal,
Verbose,
}
impl Default for TestOutputConfig {
fn default() -> Self {
Self::Normal
}
}
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
#[non_exhaustive]
#[cfg(feature = "svg")]
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
pub enum UpdateMode {
Never,
Always,
}
#[cfg(feature = "svg")]
impl UpdateMode {
pub fn from_env() -> Self {
const ENV_VAR: &str = "TERM_TRANSCRIPT_UPDATE";
match env::var_os(ENV_VAR) {
Some(s) => Self::from_os_str(&s).unwrap_or_else(|| {
panic!(
"Cannot read update mode from env variable {}: `{}` is not a valid value \
(use one of `never` or `always`)",
ENV_VAR,
s.to_string_lossy()
);
}),
None => {
if env::var_os("CI").is_some() {
Self::Never
} else {
Self::Always
}
}
}
}
fn from_os_str(s: &OsStr) -> Option<Self> {
match s {
s if s == "never" => Some(Self::Never),
s if s == "always" => Some(Self::Always),
_ => None,
}
}
fn should_create_snapshot(self) -> bool {
match self {
Self::Always => true,
Self::Never => false,
}
}
}
#[derive(Debug)]
pub struct TestConfig<Cmd = Command> {
shell_options: ShellOptions<Cmd>,
match_kind: MatchKind,
output: TestOutputConfig,
color_choice: ColorChoice,
#[cfg(feature = "svg")]
update_mode: UpdateMode,
#[cfg(feature = "svg")]
template: Template,
}
impl<Cmd: SpawnShell> TestConfig<Cmd> {
pub fn new(shell_options: ShellOptions<Cmd>) -> Self {
Self {
shell_options,
match_kind: MatchKind::TextOnly,
output: TestOutputConfig::Normal,
color_choice: ColorChoice::Auto,
#[cfg(feature = "svg")]
update_mode: UpdateMode::from_env(),
#[cfg(feature = "svg")]
template: Template::default(),
}
}
#[must_use]
pub fn with_match_kind(mut self, kind: MatchKind) -> Self {
self.match_kind = kind;
self
}
#[must_use]
pub fn with_color_choice(mut self, color_choice: ColorChoice) -> Self {
self.color_choice = color_choice;
self
}
#[must_use]
pub fn with_output(mut self, output: TestOutputConfig) -> Self {
self.output = output;
self
}
#[cfg(feature = "svg")]
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
#[must_use]
pub fn with_template(mut self, template: Template) -> Self {
self.template = template;
self
}
#[cfg(feature = "svg")]
#[cfg_attr(docsrs, doc(cfg(feature = "svg")))]
#[must_use]
pub fn with_update_mode(mut self, update_mode: UpdateMode) -> Self {
self.update_mode = update_mode;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum MatchKind {
TextOnly,
Precise,
}
#[derive(Debug, Clone)]
pub struct TestStats {
matches: Vec<Option<MatchKind>>,
}
impl TestStats {
pub fn passed(&self, match_level: MatchKind) -> usize {
self.matches
.iter()
.filter(|&&kind| kind >= Some(match_level))
.count()
}
pub fn errors(&self, match_level: MatchKind) -> usize {
self.matches.len() - self.passed(match_level)
}
pub fn matches(&self) -> &[Option<MatchKind>] {
&self.matches
}
#[allow(clippy::missing_panics_doc)]
pub fn assert_no_errors(&self, match_level: MatchKind) {
assert_eq!(self.errors(match_level), 0, "There were test errors");
}
}