diff --git a/insta/src/snapshot.rs b/insta/src/snapshot.rs index a1e1e2b7..9ff600b0 100644 --- a/insta/src/snapshot.rs +++ b/insta/src/snapshot.rs @@ -6,7 +6,11 @@ use std::io::{BufRead, BufReader, Write}; use std::path::{Path, PathBuf}; use std::time::{SystemTime, UNIX_EPOCH}; -use crate::content::{self, json, yaml, Content}; +use crate::{ + content::{self, json, yaml, Content}, + elog, + utils::style, +}; lazy_static::lazy_static! { static ref RUN_ID: String = { @@ -268,7 +272,7 @@ impl MetaData { } } -/// A helper to work with stored snapshots. +/// A helper to work with file snapshots. #[derive(Debug, Clone)] pub struct Snapshot { module_name: String, @@ -321,7 +325,7 @@ impl Snapshot { } } } - crate::elog!("A snapshot uses an old snapshot format; please update it to the new format with `cargo insta test --force-update-snapshots --accept`.\n\nSnapshot is at: {}", p.to_string_lossy()); + elog!("A snapshot uses an old snapshot format; please update it to the new format with `cargo insta test --force-update-snapshots --accept`.\n\nSnapshot is at: {}", p.to_string_lossy()); rv }; @@ -520,7 +524,8 @@ impl SnapshotContents { SnapshotContents(get_inline_snapshot_value(value)) } - /// Returns the snapshot contents as string with surrounding whitespace removed. + /// Returns the snapshot contents as a normalized string (for example, + /// removing surrounding whitespace) pub fn as_str(&self) -> &str { let out = self.0.trim_start_matches(['\r', '\n']).trim_end(); // Old inline snapshots have `---` at the start, so this strips that if @@ -532,15 +537,36 @@ impl SnapshotContents { } } - /// Returns the snapshot contents as string without any trimming. + /// Returns the snapshot contents as string without any normalization pub fn as_str_exact(&self) -> &str { self.0.as_str() } + /// Matches another snapshot without any normalization pub fn matches_fully(&self, other: &SnapshotContents) -> bool { self.as_str_exact() == other.as_str_exact() } + /// Snapshot matches based on the latest format. + pub fn matches_latest(&self, other: &SnapshotContents) -> bool { + self.as_str() == other.as_str() + } + + pub fn matches_legacy(&self, other: &SnapshotContents) -> bool { + fn as_str_legacy(sc: &SnapshotContents) -> &str { + let out = sc.as_str(); + // Legacy inline snapshots have `---` at the start, so this strips that if + // it exists. + match out.strip_prefix("---\n") { + Some(old_snapshot) => old_snapshot, + None => out, + } + } + as_str_legacy(self) == as_str_legacy(other) + } + + /// Create the literal string to write inline, adding `#` to escape the + /// value, indentation, delimiters pub fn to_inline(&self, indentation: usize) -> String { let contents = &self.0; let mut out = String::new(); @@ -570,8 +596,9 @@ impl SnapshotContents { out.extend( contents .lines() - // newline needs to be at the start, since we don't want the end - // finishing with a newline - the closing suffix should be on the same line + // Adds an additional newline at the start of multiline + // string (not sure this is the clearest way of representing + // it, but it works...) .map(|l| { format!( "\n{:width$}{l}", @@ -580,7 +607,8 @@ impl SnapshotContents { l = l ) }) - // `lines` removes the final line ending - add back + // `lines` removes the final line ending — add back. Include + // indentation so the closing delimited aligns with the full string. .chain(Some(format!("\n{:width$}", "", width = indentation))), ); } else { @@ -625,7 +653,15 @@ impl From for String { impl PartialEq for SnapshotContents { fn eq(&self, other: &Self) -> bool { - self.as_str() == other.as_str() + // Ideally match on current rules, but otherwise fall back to legacy rules + if self.matches_latest(other) { + true + } else if self.matches_legacy(other) { + elog!("{} {}\n{}",style("Snapshot passes but is a legacy format. Please run `cargo insta test --force-update-snapshots --accept` to update to a newer format.").yellow().bold(),"Snapshot contents:", self.as_str()); + true + } else { + false + } } } @@ -719,7 +755,7 @@ fn get_inline_snapshot_value(frozen_value: &str) -> String { // (the only call site) if frozen_value.trim_start().starts_with('⋮') { - crate::elog!("A snapshot uses an old snapshot format; please update it to the new format with `cargo insta test --force-update-snapshots --accept`.\n\nSnapshot is at: {}", frozen_value); + elog!("A snapshot uses an old snapshot format; please update it to the new format with `cargo insta test --force-update-snapshots --accept`.\n\nSnapshot is at: {}", frozen_value); // legacy format - retain so old snapshots still work let mut buf = String::new();