diff --git a/internal/test/test.go b/internal/test/test.go index d3215f7..4301908 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -47,7 +47,7 @@ func (m MockTestingT) Log(args ...interface{}) { } // Equal asserts expected and received have deep equality -func Equal(t *testing.T, expected, received interface{}) { +func Equal[A any](t *testing.T, expected, received A) { t.Helper() if !reflect.DeepEqual(expected, received) { t.Errorf("\n[expected]: %v\n[received]: %v", expected, received) @@ -77,6 +77,20 @@ func CreateTempFile(t *testing.T, data string) string { return path } +// GetFileContent returns the contents of a file +// +// it errors if file doesn't exist +func GetFileContent(t *testing.T, name string) string { + t.Helper() + + content, err := os.ReadFile(name) + if err != nil { + t.Error(err) + } + + return string(content) +} + func True(t *testing.T, val bool) { t.Helper() @@ -94,9 +108,17 @@ func False(t *testing.T, val bool) { } func Nil(t *testing.T, val interface{}) { + t.Helper() v := reflect.ValueOf(val) if val != nil && !v.IsNil() { t.Errorf("expected nil but got %v", val) } } + +func NoError(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Errorf("expected no error but got %s", err) + } +} diff --git a/snaps/__snapshots__/diff_test.snap b/snaps/__snapshots__/diff_test.snap index fde80b6..541d285 100755 --- a/snaps/__snapshots__/diff_test.snap +++ b/snaps/__snapshots__/diff_test.snap @@ -1,18 +1,22 @@ -[TestDiff/should_print_header_consistently - 1] +[TestDiff/should_build_diff_report_consistently - 1] - Snapshot - 20 + Received + 10000 - +mock-diff +at snap/path:10 + --- -[TestDiff/should_print_header_consistently - 2] +[TestDiff/should_build_diff_report_consistently - 2] - Snapshot - 10000 + Received + 20 - +mock-diff +at snap/path:20 + --- [TestDiff/with_color/should_apply_highlights_on_single_line_diff - 1] @@ -20,7 +24,9 @@ - Snapshot - 20 + Received + 20 -- abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd+ abcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcf +- abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd+ abcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcf +at snap/path:10 + --- [TestDiff/with_color/multiline_diff - 1] @@ -48,7 +54,9 @@ + Sed lorem felis, condimentum eget vehicula non, sagittis sit amet diam.  + Vivamus ut sapien at erat imperdiet suscipit id a lectus. + Another Line added. - + +at snap/path:10 + --- [TestDiff/no_color/should_apply_highlights_on_single_line_diff - 1] @@ -59,6 +67,8 @@ - abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd + abcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcfabcf +at snap/path:10 + --- [TestDiff/no_color/multiline_diff - 1] @@ -87,4 +97,14 @@ + Vivamus ut sapien at erat imperdiet suscipit id a lectus. + Another Line added. +at snap/path:20 + +--- + +[TestDiff/should_not_print_snapshot_line_if_not_provided - 1] + +- Snapshot - 2 ++ Received + 10 + +there is a diff here --- diff --git a/snaps/clean_test.go b/snaps/clean_test.go index 2262fc6..dbec908 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -114,17 +114,6 @@ func setupTempExamineFiles(t *testing.T) (map[string]map[string]int, string, str return tests, dir1, dir2 } -func getFileContent(t *testing.T, name string) string { - t.Helper() - - content, err := os.ReadFile(name) - if err != nil { - t.Error(err) - } - - return string(content) -} - func TestExamineFiles(t *testing.T) { t.Run("should parse files", func(t *testing.T) { tests, dir1, dir2 := setupTempExamineFiles(t) @@ -182,7 +171,7 @@ func TestExamineSnaps(t *testing.T) { obsolete, err := examineSnaps(tests, used, "", shouldUpdate) test.Equal(t, []string{}, obsolete) - test.Nil(t, err) + test.NoError(t, err) }) t.Run("should report two obsolete snapshots and not change content", func(t *testing.T) { @@ -199,11 +188,11 @@ func TestExamineSnaps(t *testing.T) { delete(tests[used[1]], "TestDir2_2/TestSimple") obsolete, err := examineSnaps(tests, used, "", shouldUpdate) - content1 := getFileContent(t, used[0]) - content2 := getFileContent(t, used[1]) + content1 := test.GetFileContent(t, used[0]) + content2 := test.GetFileContent(t, used[1]) test.Equal(t, []string{"TestDir1_3/TestSimple - 2", "TestDir2_2/TestSimple - 1"}, obsolete) - test.Nil(t, err) + test.NoError(t, err) // Content of snaps is not changed test.Equal(t, mockSnap1, content1) @@ -222,8 +211,8 @@ func TestExamineSnaps(t *testing.T) { delete(tests[used[1]], "TestDir2_1/TestSimple") obsolete, err := examineSnaps(tests, used, "", shouldUpdate) - content1 := getFileContent(t, used[0]) - content2 := getFileContent(t, used[1]) + content1 := test.GetFileContent(t, used[0]) + content2 := test.GetFileContent(t, used[1]) expected1 := ` [TestDir1_1/TestSimple - 1] @@ -255,7 +244,7 @@ string hello world 2 2 1 }, obsolete, ) - test.Nil(t, err) + test.NoError(t, err) // Content of snaps is not changed test.Equal(t, expected1, content1) diff --git a/snaps/diff.go b/snaps/diff.go index 3e90580..37684e0 100644 --- a/snaps/diff.go +++ b/snaps/diff.go @@ -1,6 +1,7 @@ package snaps import ( + "bytes" "fmt" "io" "strconv" @@ -72,7 +73,8 @@ func getUnifiedDiff(a, b string) (string, int, int) { received := strings.Join(aLines[i1:i2], "") if shouldPrintHighlights(expected, received) { - i, d := singlelineDiff(&s, received, expected) + diff, i, d := singlelineDiff(received, expected) + s.WriteString(diff) inserted += i deleted += d @@ -113,23 +115,6 @@ func getUnifiedDiff(a, b string) (string, int, int) { return s.String(), inserted, deleted } -// header of a diff report -// -// e.g. -// - Snapshot - 10 -// - Received - 2 -func header(inserted, deleted int) string { - var s strings.Builder - iPadding, dPadding := intPadding(inserted, deleted) - - s.WriteString("\n") - colors.FprintDelete(&s, fmt.Sprintf("Snapshot %s- %d\n", dPadding, deleted)) - colors.FprintInsert(&s, fmt.Sprintf("Received %s+ %d\n", iPadding, inserted)) - s.WriteString("\n") - - return s.String() -} - func printRange(w io.Writer, opcodes []difflib.OpCode) { first, last := opcodes[0], opcodes[len(opcodes)-1] range1 := difflib.FormatRangeUnified(first.I1, last.I2) @@ -161,57 +146,87 @@ func intPadding(inserted, deleted int) (string, string) { return strings.Repeat(" ", -diff), "" } -func singlelineDiff(s *strings.Builder, expected, received string) (int, int) { +func singlelineDiff(expected, received string) (string, int, int) { diffs := dmp.DiffCleanupSemantic( dmp.DiffMain(expected, received, false), ) if len(diffs) == 1 && diffs[0].Type == diffEqual { - // -1 means no diffs - return -1, 0 + return "", -1, -1 } var inserted, deleted int - var i strings.Builder + a := &bytes.Buffer{} + b := &bytes.Buffer{} - colors.FprintBg(s, colors.RedBg, colors.Reddiff, "- ") - colors.FprintBg(&i, colors.GreenBG, colors.Greendiff, "+ ") + colors.FprintBg(a, colors.RedBg, colors.Reddiff, "- ") + colors.FprintBg(b, colors.GreenBG, colors.Greendiff, "+ ") for _, diff := range diffs { switch diff.Type { case diffDelete: deleted++ - colors.FprintDeleteBold(s, diff.Text) + colors.FprintDeleteBold(a, diff.Text) case diffInsert: inserted++ - colors.FprintInsertBold(&i, diff.Text) + colors.FprintInsertBold(b, diff.Text) case diffEqual: - colors.FprintBg(s, colors.RedBg, colors.Reddiff, diff.Text) - colors.FprintBg(&i, colors.GreenBG, colors.Greendiff, diff.Text) + colors.FprintBg(a, colors.RedBg, colors.Reddiff, diff.Text) + colors.FprintBg(b, colors.GreenBG, colors.Greendiff, diff.Text) } } - s.WriteString(i.String()) + a.Write(b.Bytes()) - return inserted, deleted + return a.String(), inserted, deleted } -func prettyDiff(expected, received string) string { - if expected == received { +/* +buildDiffReport creates a report with diffs it contains a header the diff body and a footer + +header of a diff report + + e.g. + - Snapshot - 10 + - Received + 2 + +body contains the diffs + +footer contains the relative path of snapshot + + e.g. at ../__snapshots__/example_test.snap:25 +*/ +func buildDiffReport(inserted, deleted int, diff, name string, line int) string { + if diff == "" { return "" } + var s strings.Builder + s.Grow(len(diff)) - if shouldPrintHighlights(expected, received) { - var diff strings.Builder - if i, d := singlelineDiff(&diff, expected, received); i != -1 { - return header(i, d) + diff.String() - } + iPadding, dPadding := intPadding(inserted, deleted) - return "" + s.WriteString("\n") + colors.FprintDelete(&s, fmt.Sprintf("Snapshot %s- %d\n", dPadding, deleted)) + colors.FprintInsert(&s, fmt.Sprintf("Received %s+ %d\n", iPadding, inserted)) + s.WriteString("\n") + + s.WriteString(diff) + + if name != "" { + colors.Fprint(&s, colors.Dim, fmt.Sprintf("\nat %s:%d\n", name, line)) } - if diff, i, d := getUnifiedDiff(expected, received); diff != "" { - return header(i, d) + diff + return s.String() +} + +func prettyDiff(expected, received, name string, line int) string { + if expected == received { + return "" + } + differ := getUnifiedDiff + if shouldPrintHighlights(expected, received) { + differ = singlelineDiff } - return "" + diff, i, d := differ(expected, received) + return buildDiffReport(i, d, diff, name, line) } diff --git a/snaps/diff_test.go b/snaps/diff_test.go index 3f32dce..0969122 100644 --- a/snaps/diff_test.go +++ b/snaps/diff_test.go @@ -72,9 +72,11 @@ func TestDiff(t *testing.T) { t.Run("single line", func(t *testing.T) { expected, received := "Hello World\n", "Hello World\n" - if diff := prettyDiff(expected, received); diff != "" { - t.Errorf("found diff between same string %s", diff) - } + diff, deleted, inserted := singlelineDiff(expected, received) + test.Equal(t, "", diff) + test.Equal(t, -1, deleted) + test.Equal(t, -1, inserted) + test.Equal(t, "", prettyDiff(expected, received, "", -1)) }) t.Run("multiline", func(t *testing.T) { @@ -83,15 +85,23 @@ func TestDiff(t *testing.T) { ` received := expected - if diff := prettyDiff(expected, received); diff != "" { + if diff := prettyDiff(expected, received, "", -1); diff != "" { t.Errorf("found diff between same string %s", diff) } }) }) - t.Run("should print header consistently", func(t *testing.T) { - MatchSnapshot(t, header(10000, 20)) - MatchSnapshot(t, header(20, 10000)) + t.Run("should build diff report consistently", func(t *testing.T) { + MatchSnapshot(t, buildDiffReport(10000, 20, "mock-diff", "snap/path", 10)) + MatchSnapshot(t, buildDiffReport(20, 10000, "mock-diff", "snap/path", 20)) + }) + + t.Run("should not print diff report if no diffs", func(t *testing.T) { + test.Equal(t, "", buildDiffReport(0, 0, "", "", -1)) + }) + + t.Run("should not print snapshot line if not provided", func(t *testing.T) { + MatchSnapshot(t, buildDiffReport(10, 2, "there is a diff here", "", -1)) }) t.Run("with color", func(t *testing.T) { @@ -99,11 +109,11 @@ func TestDiff(t *testing.T) { a := strings.Repeat("abcd", 20) b := strings.Repeat("abcf", 20) - MatchSnapshot(t, prettyDiff(a, b)) + MatchSnapshot(t, prettyDiff(a, b, "snap/path", 10)) }) t.Run("multiline diff", func(t *testing.T) { - MatchSnapshot(t, prettyDiff(a, b)) + MatchSnapshot(t, prettyDiff(a, b, "snap/path", 10)) }) }) @@ -117,11 +127,12 @@ func TestDiff(t *testing.T) { a := strings.Repeat("abcd", 20) b := strings.Repeat("abcf", 20) - MatchSnapshot(t, prettyDiff(a, b)) + d := prettyDiff(a, b, "snap/path", 10) + MatchSnapshot(t, d) }) t.Run("multiline diff", func(t *testing.T) { - MatchSnapshot(t, prettyDiff(a, b)) + MatchSnapshot(t, prettyDiff(a, b, "snap/path", 20)) }) }) } diff --git a/snaps/matchJSON.go b/snaps/matchJSON.go index 35537cf..0a32f83 100644 --- a/snaps/matchJSON.go +++ b/snaps/matchJSON.go @@ -63,7 +63,7 @@ func MatchJSON(t testingT, input interface{}, matchers ...match.JSONMatcher) { func matchJSON(c *config, t testingT, input interface{}, matchers ...match.JSONMatcher) { t.Helper() - dir, snapPath := snapDirAndName(c) + snapPath, snapPathRel := snapshotPath(c) testID := testsRegistry.getTestID(t.Name(), snapPath) j, err := validateJSON(input) @@ -95,14 +95,14 @@ func matchJSON(c *config, t testingT, input interface{}, matchers ...match.JSONM } snapshot := takeJSONSnapshot(j) - prevSnapshot, err := getPrevSnapshot(testID, snapPath) + prevSnapshot, line, err := getPrevSnapshot(testID, snapPath) if errors.Is(err, errSnapNotFound) { if isCI { handleError(t, err) return } - err := addNewSnapshot(testID, snapshot, dir, snapPath) + err := addNewSnapshot(testID, snapshot, snapPath) if err != nil { handleError(t, err) return @@ -117,7 +117,7 @@ func matchJSON(c *config, t testingT, input interface{}, matchers ...match.JSONM return } - diff := prettyDiff(prevSnapshot, snapshot) + diff := prettyDiff(prevSnapshot, snapshot, snapPathRel, line) if diff == "" { testEvents.register(passed) return diff --git a/snaps/matchJSON_test.go b/snaps/matchJSON_test.go index 596b81f..3ace3fd 100644 --- a/snaps/matchJSON_test.go +++ b/snaps/matchJSON_test.go @@ -57,13 +57,15 @@ func TestMatchJSON(t *testing.T) { MockError: func(args ...interface{}) { test.NotCalled(t) }, - MockLog: func(args ...interface{}) { test.Equal(t, addedMsg, args[0]) }, + MockLog: func(args ...interface{}) { test.Equal(t, addedMsg, args[0].(string)) }, } MatchJSON(mockT, tc.input) - snap, err := getPrevSnapshot("[mock-name - 1]", snapPath) - test.Nil(t, err) + snap, line, err := getPrevSnapshot("[mock-name - 1]", snapPath) + + test.NoError(t, err) + test.Equal(t, 2, line) test.Equal(t, expected, snap) test.Equal(t, 1, testEvents.items[added]) }) @@ -126,7 +128,7 @@ func TestMatchJSON(t *testing.T) { MockError: func(args ...interface{}) { test.NotCalled(t) }, - MockLog: func(args ...interface{}) { test.Equal(t, addedMsg, args[0]) }, + MockLog: func(args ...interface{}) { test.Equal(t, addedMsg, args[0].(string)) }, } c1 := func(val interface{}) (interface{}, error) { @@ -188,7 +190,7 @@ func TestMatchJSON(t *testing.T) { return "mock-name" }, MockError: func(args ...interface{}) { - test.Equal(t, errSnapNotFound, args[0]) + test.Equal(t, errSnapNotFound, args[0].(error)) }, MockLog: func(args ...interface{}) { test.NotCalled(t) @@ -204,8 +206,8 @@ func TestMatchJSON(t *testing.T) { snapPath := setupSnapshot(t, jsonFilename, false, true) printerExpectedCalls := []func(received interface{}){ - func(received interface{}) { test.Equal(t, addedMsg, received) }, - func(received interface{}) { test.Equal(t, updatedMsg, received) }, + func(received interface{}) { test.Equal(t, addedMsg, received.(string)) }, + func(received interface{}) { test.Equal(t, updatedMsg, received.(string)) }, } mockT := test.MockTestingT{ MockHelper: func() {}, @@ -236,7 +238,7 @@ func TestMatchJSON(t *testing.T) { test.Equal( t, "\n[mock-name - 1]\n{\n \"value\": \"bye world\"\n}\n---\n", - snapshotFile(t, snapPath), + test.GetFileContent(t, snapPath), ) test.Equal(t, 1, testEvents.items[updated]) }) diff --git a/snaps/matchSnapshot.go b/snaps/matchSnapshot.go index cc4fc2d..96e91d9 100644 --- a/snaps/matchSnapshot.go +++ b/snaps/matchSnapshot.go @@ -53,17 +53,17 @@ func matchSnapshot(c *config, t testingT, values ...interface{}) { return } - dir, snapPath := snapDirAndName(c) + snapPath, snapPathRel := snapshotPath(c) testID := testsRegistry.getTestID(t.Name(), snapPath) snapshot := takeSnapshot(values) - prevSnapshot, err := getPrevSnapshot(testID, snapPath) + prevSnapshot, line, err := getPrevSnapshot(testID, snapPath) if errors.Is(err, errSnapNotFound) { if isCI { handleError(t, err) return } - err := addNewSnapshot(testID, snapshot, dir, snapPath) + err := addNewSnapshot(testID, snapshot, snapPath) if err != nil { handleError(t, err) return @@ -78,7 +78,12 @@ func matchSnapshot(c *config, t testingT, values ...interface{}) { return } - diff := prettyDiff(unescapeEndChars(prevSnapshot), unescapeEndChars(snapshot)) + diff := prettyDiff( + unescapeEndChars(prevSnapshot), + unescapeEndChars(snapshot), + snapPathRel, + line, + ) if diff == "" { testEvents.register(passed) return diff --git a/snaps/matchSnapshot_test.go b/snaps/matchSnapshot_test.go index 19863a5..8a9ae29 100644 --- a/snaps/matchSnapshot_test.go +++ b/snaps/matchSnapshot_test.go @@ -71,12 +71,16 @@ func TestMatchSnapshot(t *testing.T) { MockError: func(args ...interface{}) { test.NotCalled(t) }, - MockLog: func(args ...interface{}) { test.Equal(t, addedMsg, args[0]) }, + MockLog: func(args ...interface{}) { test.Equal(t, addedMsg, args[0].(string)) }, } MatchSnapshot(mockT, 10, "hello world") - test.Equal(t, "\n[mock-name - 1]\nint(10)\nhello world\n---\n", snapshotFile(t, snapPath)) + test.Equal( + t, + "\n[mock-name - 1]\nint(10)\nhello world\n---\n", + test.GetFileContent(t, snapPath), + ) test.Equal(t, 1, testEvents.items[added]) }) @@ -89,7 +93,7 @@ func TestMatchSnapshot(t *testing.T) { return "mock-name" }, MockError: func(args ...interface{}) { - test.Equal(t, errSnapNotFound, args[0]) + test.Equal(t, errSnapNotFound, args[0].(error)) }, MockLog: func(args ...interface{}) { test.NotCalled(t) @@ -105,7 +109,7 @@ func TestMatchSnapshot(t *testing.T) { setupSnapshot(t, fileName, false) printerExpectedCalls := []func(received interface{}){ - func(received interface{}) { test.Equal(t, addedMsg, received) }, + func(received interface{}) { test.Equal(t, addedMsg, received.(string)) }, func(received interface{}) { test.NotCalled(t) }, } mockT := test.MockTestingT{ @@ -117,9 +121,12 @@ func TestMatchSnapshot(t *testing.T) { expected := "\n\x1b[38;5;52m\x1b[48;5;225m- Snapshot - 2\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m" + "+ Received + 2\x1b[0m\n\n\x1b[38;5;52m\x1b[48;5;225m- int(10)\x1b[0m\n\x1b[38;5;52m\x1b[48;5;225m" + "- hello world\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m+ int(100)\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m" + - "+ bye world\x1b[0m\n \x1b[2m↵\n\x1b[0m" + "+ bye world\x1b[0m\n \x1b[2m↵\n\x1b[0m\x1b[2m\nat " + filepath.FromSlash( + "../__snapshots__/matchSnapshot_test.snap:2", + ) + + "\n\x1b[0m" - test.Equal(t, expected, args[0]) + test.Equal(t, expected, args[0].(string)) }, MockLog: func(args ...interface{}) { printerExpectedCalls[0](args[0]) @@ -145,8 +152,8 @@ func TestMatchSnapshot(t *testing.T) { snapPath := setupSnapshot(t, fileName, false, true) printerExpectedCalls := []func(received interface{}){ - func(received interface{}) { test.Equal(t, addedMsg, received) }, - func(received interface{}) { test.Equal(t, updatedMsg, received) }, + func(received interface{}) { test.Equal(t, addedMsg, received.(string)) }, + func(received interface{}) { test.Equal(t, updatedMsg, received.(string)) }, } mockT := test.MockTestingT{ MockHelper: func() {}, @@ -174,7 +181,11 @@ func TestMatchSnapshot(t *testing.T) { // Second call with different params MatchSnapshot(mockT, 100, "bye world") - test.Equal(t, "\n[mock-name - 1]\nint(100)\nbye world\n---\n", snapshotFile(t, snapPath)) + test.Equal( + t, + "\n[mock-name - 1]\nint(100)\nbye world\n---\n", + test.GetFileContent(t, snapPath), + ) test.Equal(t, 1, testEvents.items[updated]) }) @@ -185,7 +196,7 @@ func TestMatchSnapshot(t *testing.T) { test.Equal( t, colors.Sprint(colors.Yellow, "[warning] MatchSnapshot call without params\n"), - args[0], + args[0].(string), ) }, } @@ -197,7 +208,7 @@ func TestMatchSnapshot(t *testing.T) { setupSnapshot(t, fileName, false) printerExpectedCalls := []func(received interface{}){ - func(received interface{}) { test.Equal(t, addedMsg, received) }, + func(received interface{}) { test.Equal(t, addedMsg, received.(string)) }, func(received interface{}) { test.NotCalled(t) }, } mockT := test.MockTestingT{ @@ -210,9 +221,12 @@ func TestMatchSnapshot(t *testing.T) { "+ Received + 3\x1b[0m\n\n\x1b[38;5;52m\x1b[48;5;225m- int(10)\x1b[0m\n\x1b[38;5;52m\x1b[48;5;225m" + "- hello world----\x1b[0m\n\x1b[38;5;52m\x1b[48;5;225m- ---\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m" + "+ int(100)\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m+ bye world----\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m" + - "+ --\x1b[0m\n \x1b[2m↵\n\x1b[0m" + "+ --\x1b[0m\n \x1b[2m↵\n\x1b[0m\x1b[2m\nat " + filepath.FromSlash( + "../__snapshots__/matchSnapshot_test.snap:2", + ) + + "\n\x1b[0m" - test.Equal(t, expected, args[0]) + test.Equal(t, expected, args[0].(string)) }, MockLog: func(args ...interface{}) { printerExpectedCalls[0](args[0]) diff --git a/snaps/skip_test.go b/snaps/skip_test.go index 8d86b4c..6da47a9 100644 --- a/snaps/skip_test.go +++ b/snaps/skip_test.go @@ -24,7 +24,7 @@ func TestSkip(t *testing.T) { return "mock-test" }, MockLog: func(args ...interface{}) { - test.Equal(t, skippedMsg, args[0]) + test.Equal(t, skippedMsg, args[0].(string)) }, } Skip(mockT, 1, 2, 3, 4, 5) @@ -47,7 +47,7 @@ func TestSkip(t *testing.T) { return "mock-test" }, MockLog: func(args ...interface{}) { - test.Equal(t, skippedMsg, args[0]) + test.Equal(t, skippedMsg, args[0].(string)) }, } Skipf(mockT, "mock", 1, 2, 3, 4, 5) @@ -67,7 +67,7 @@ func TestSkip(t *testing.T) { return "mock-test" }, MockLog: func(args ...interface{}) { - test.Equal(t, skippedMsg, args[0]) + test.Equal(t, skippedMsg, args[0].(string)) }, } SkipNow(mockT) @@ -87,7 +87,7 @@ func TestSkip(t *testing.T) { return "mock-test" }, MockLog: func(args ...interface{}) { - test.Equal(t, skippedMsg, args[0]) + test.Equal(t, skippedMsg, args[0].(string)) }, } @@ -139,7 +139,7 @@ func TestSkip(t *testing.T) { return "TestMock/Skip" }, MockLog: func(args ...interface{}) { - test.Equal(t, skippedMsg, args[0]) + test.Equal(t, skippedMsg, args[0].(string)) }, } // This is for populating skippedTests.values and following the normal flow @@ -167,7 +167,7 @@ func TestSkip(t *testing.T) { return "Test" }, MockLog: func(args ...interface{}) { - test.Equal(t, skippedMsg, args[0]) + test.Equal(t, skippedMsg, args[0].(string)) }, } // This is for populating skippedTests.values and following the normal flow diff --git a/snaps/snapshot.go b/snaps/snapshot.go index 5a29487..824f4c8 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -103,18 +103,24 @@ func newRegistry() *syncRegistry { } } -func getPrevSnapshot(testID, snapPath string) (string, error) { +// getPrevSnapshot scans file searching for a snapshot matching the given testID and returns +// the snapshot with the line where is located inside the file. +// +// If not found returns errSnapNotFound error. +func getPrevSnapshot(testID, snapPath string) (string, int, error) { f, err := os.ReadFile(snapPath) if err != nil { - return "", errSnapNotFound + return "", -1, errSnapNotFound } + lineNumber := 1 tid := []byte(testID) s := bufio.NewScanner(bytes.NewReader(f)) for s.Scan() { l := s.Bytes() if !bytes.Equal(l, tid) { + lineNumber++ continue } var snapshot strings.Builder @@ -123,18 +129,18 @@ func getPrevSnapshot(testID, snapPath string) (string, error) { line := s.Bytes() if bytes.Equal(line, endSequenceByteSlice) { - return snapshot.String(), nil + return snapshot.String(), lineNumber, nil } snapshot.Write(line) snapshot.WriteByte('\n') } } - return "", errSnapNotFound + return "", -1, errSnapNotFound } -func addNewSnapshot(testID, snapshot, dir, snapPath string) error { - if err := os.MkdirAll(dir, os.ModePerm); err != nil { +func addNewSnapshot(testID, snapshot, snapPath string) error { + if err := os.MkdirAll(filepath.Dir(snapPath), os.ModePerm); err != nil { return err } @@ -203,16 +209,18 @@ func removeSnapshot(s *bufio.Scanner) { } /* -Returns the dir for snapshots +Returns the path for snapshots - if no config provided returns the directory where tests are running - if snapsDir is relative path just gets appended to directory where tests are running - if snapsDir is absolute path then we are returning this path -Returns the filename +and for the filename - if no config provided we use the test file name with `.snap` extension - if filename provided we return the filename with `.snap` extension + +Returns the relative path of the caller and the snapshot path. */ -func snapDirAndName(c *config) (string, string) { +func snapshotPath(c *config) (string, string) { // skips current func, the wrapper match* and the exported Match* func callerPath := baseCaller(3) @@ -226,8 +234,10 @@ func snapDirAndName(c *config) (string, string) { base := filepath.Base(callerPath) filename = strings.TrimSuffix(base, filepath.Ext(base)) } + snapPath := filepath.Join(dir, filename+snapsExt) + snapPathRel, _ := filepath.Rel(callerPath, snapPath) - return dir, filepath.Join(dir, filename+snapsExt) + return snapPath, snapPathRel } func unescapeEndChars(s string) string { diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index a189e3a..fafafe2 100644 --- a/snaps/snapshot_test.go +++ b/snaps/snapshot_test.go @@ -1,7 +1,6 @@ package snaps import ( - "os" "path/filepath" "sync" "testing" @@ -9,16 +8,6 @@ import ( "github.com/gkampitakis/go-snaps/internal/test" ) -func snapshotFile(t *testing.T, name string) string { - t.Helper() - f, err := os.ReadFile(name) - if err != nil { - t.Error(err) - } - - return string(f) -} - func TestTestID(t *testing.T) { t.Run("should increment id on each call [concurrent safe]", func(t *testing.T) { wg := sync.WaitGroup{} @@ -42,18 +31,20 @@ func TestTestID(t *testing.T) { func TestGetPrevSnapshot(t *testing.T) { t.Run("should return errSnapNotFound", func(t *testing.T) { - snap, err := getPrevSnapshot("", "") + snap, line, err := getPrevSnapshot("", "") test.Equal(t, "", snap) - test.Equal(t, err, errSnapNotFound) + test.Equal(t, -1, line) + test.Equal(t, errSnapNotFound, err) }) t.Run("should return errSnapNotFound if no match found", func(t *testing.T) { fileData := "[testid]\ntest\n---\n" path := test.CreateTempFile(t, fileData) - snap, err := getPrevSnapshot("nonexistentid", path) + snap, line, err := getPrevSnapshot("nonexistentid", path) test.Equal(t, "", snap) + test.Equal(t, -1, line) test.Equal(t, errSnapNotFound, err) }) @@ -62,6 +53,7 @@ func TestGetPrevSnapshot(t *testing.T) { testID string fileData string snap string + line int err error }{ { @@ -69,6 +61,7 @@ func TestGetPrevSnapshot(t *testing.T) { testID: "my-test", fileData: "", snap: "", + line: -1, err: errSnapNotFound, }, { @@ -76,6 +69,7 @@ func TestGetPrevSnapshot(t *testing.T) { testID: "my-test", fileData: "mysnapshot", snap: "", + line: -1, err: errSnapNotFound, }, { @@ -83,18 +77,21 @@ func TestGetPrevSnapshot(t *testing.T) { testID: "[my-test - 1]", fileData: "[my-test - 1]\nmysnapshot\n---\n", snap: "mysnapshot\n", + line: 1, }, { description: "should ignore regex in testID and match correct snap", testID: "[.*]", fileData: "\n[my-test]\nwrong snap\n---\n\n[.*]\nmysnapshot\n---\n", snap: "mysnapshot\n", + line: 6, }, { description: "should ignore end chars (---) inside snapshot", testID: "[mock-test 1]", fileData: "\n[mock-test 1]\nmysnapshot\n---moredata\n---\n", snap: "mysnapshot\n---moredata\n", + line: 2, }, } { s := scenario @@ -102,53 +99,52 @@ func TestGetPrevSnapshot(t *testing.T) { t.Parallel() path := test.CreateTempFile(t, s.fileData) - snap, err := getPrevSnapshot(s.testID, path) + snap, line, err := getPrevSnapshot(s.testID, path) test.Equal(t, s.err, err) + test.Equal(t, s.line, line) test.Equal(t, s.snap, snap) }) } } func TestAddNewSnapshot(t *testing.T) { - dir := filepath.Join(t.TempDir(), "__snapshots__") - name := filepath.Join(dir, "mock-test.snap") - err := addNewSnapshot("[mock-id]", "my-snap\n", dir, name) + snapPath := filepath.Join(t.TempDir(), "__snapshots__/mock-test.snap") - test.Nil(t, err) - test.Equal(t, "\n[mock-id]\nmy-snap\n---\n", snapshotFile(t, name)) + test.NoError(t, addNewSnapshot("[mock-id]", "my-snap\n", snapPath)) + test.Equal(t, "\n[mock-id]\nmy-snap\n---\n", test.GetFileContent(t, snapPath)) } func TestSnapPathAndFile(t *testing.T) { t.Run("should return default path and file", func(t *testing.T) { var ( - dir string - name string + snapPath string + snapPathRel string ) func() { // This is for emulating being called from a func so we can find the correct file // of the caller func() { - dir, name = snapDirAndName(&defaultConfig) + snapPath, snapPathRel = snapshotPath(&defaultConfig) }() }() - test.Contains(t, dir, filepath.FromSlash("/snaps/__snapshots__")) - test.Contains(t, name, filepath.FromSlash("/snaps/__snapshots__/snapshot_test.snap")) + test.Contains(t, snapPath, filepath.FromSlash("/snaps/__snapshots__")) + test.Contains(t, snapPathRel, filepath.FromSlash("../__snapshots__/snapshot_test.snap")) }) t.Run("should return path and file from config", func(t *testing.T) { var ( - dir string - name string + snapPath string + snapPathRel string ) func() { // This is for emulating being called from a func so we can find the correct file // of the caller func() { - dir, name = snapDirAndName(&config{ + snapPath, snapPathRel = snapshotPath(&config{ filename: "my_file", snapsDir: "my_snapshot_dir", }) @@ -156,29 +152,29 @@ func TestSnapPathAndFile(t *testing.T) { }() // returns the current file's path /snaps/* - test.Contains(t, dir, filepath.FromSlash("/snaps/my_snapshot_dir")) - test.Contains(t, name, filepath.FromSlash("/snaps/my_snapshot_dir/my_file.snap")) + test.Contains(t, snapPath, filepath.FromSlash("/snaps/my_snapshot_dir")) + test.Contains(t, snapPathRel, filepath.FromSlash("../my_snapshot_dir/my_file.snap")) }) t.Run("should return absolute path", func(t *testing.T) { var ( - dir string - name string + snapPath string + snapPathRel string ) func() { // This is for emulating being called from a func so we can find the correct file // of the caller func() { - dir, name = snapDirAndName(&config{ + snapPath, snapPathRel = snapshotPath(&config{ filename: "my_file", snapsDir: "/path_to/my_snapshot_dir", }) }() }() - test.Contains(t, dir, filepath.FromSlash("/path_to/my_snapshot_dir")) - test.Contains(t, name, filepath.FromSlash("/path_to/my_snapshot_dir/my_file.snap")) + test.Contains(t, snapPath, filepath.FromSlash("/path_to/my_snapshot_dir")) + test.Contains(t, snapPathRel, filepath.FromSlash("/path_to/my_snapshot_dir/my_file.snap")) }) } @@ -203,31 +199,25 @@ string hello world 1 3 2 ` snapPath := test.CreateTempFile(t, mockSnap) newSnapshot := "int(1250)\nstring new value\n" - err := updateSnapshot("[Test_3/TestSimple - 1]", newSnapshot, snapPath) - snap, _ := os.ReadFile(snapPath) - test.Nil(t, err) - test.Equal(t, updatedSnap, string(snap)) + test.NoError(t, updateSnapshot("[Test_3/TestSimple - 1]", newSnapshot, snapPath)) + test.Equal(t, updatedSnap, test.GetFileContent(t, snapPath)) } func TestEscapeEndChars(t *testing.T) { t.Run("should escape end chars inside data", func(t *testing.T) { - dir := filepath.Join(t.TempDir(), "__snapshots__") - name := filepath.Join(dir, "mock-test.snap") + snapPath := filepath.Join(t.TempDir(), "__snapshots__/mock-test.snap") snapshot := takeSnapshot([]interface{}{"my-snap", endSequence}) - err := addNewSnapshot("[mock-id]", snapshot, dir, name) - test.Nil(t, err) - test.Equal(t, "\n[mock-id]\nmy-snap\n/-/-/-/\n---\n", snapshotFile(t, name)) + test.NoError(t, addNewSnapshot("[mock-id]", snapshot, snapPath)) + test.Equal(t, "\n[mock-id]\nmy-snap\n/-/-/-/\n---\n", test.GetFileContent(t, snapPath)) }) t.Run("should not escape --- if not end chars", func(t *testing.T) { - dir := filepath.Join(t.TempDir(), "__snapshots__") - name := filepath.Join(dir, "mock-test.snap") + snapPath := filepath.Join(t.TempDir(), "__snapshots__/mock-test.snap") snapshot := takeSnapshot([]interface{}{"my-snap---", endSequence}) - err := addNewSnapshot("[mock-id]", snapshot, dir, name) - test.Nil(t, err) - test.Equal(t, "\n[mock-id]\nmy-snap---\n/-/-/-/\n---\n", snapshotFile(t, name)) + test.NoError(t, addNewSnapshot("[mock-id]", snapshot, snapPath)) + test.Equal(t, "\n[mock-id]\nmy-snap---\n/-/-/-/\n---\n", test.GetFileContent(t, snapPath)) }) }