From 47ae0bf706e25ae4e5aad105365f362c80d5cb0b Mon Sep 17 00:00:00 2001 From: Georgios Kampitakis Date: Sat, 20 Jul 2024 13:28:08 +0100 Subject: [PATCH 1/4] initial commit --- internal/test/test.go | 8 ++ snaps/clean.go | 40 ++++++-- snaps/clean_test.go | 10 +- snaps/matchJSON.go | 2 +- snaps/matchSnapshot.go | 10 +- snaps/matchStandaloneSnapshot.go | 80 ++++++++++++++++ snaps/snapshot.go | 104 ++++++++++++++++++--- snaps/snapshot_test.go | 151 ++++++++++++++++++++++--------- 8 files changed, 332 insertions(+), 73 deletions(-) create mode 100644 snaps/matchStandaloneSnapshot.go diff --git a/internal/test/test.go b/internal/test/test.go index 1e4016d..263b7e1 100644 --- a/internal/test/test.go +++ b/internal/test/test.go @@ -126,6 +126,14 @@ func Contains(t *testing.T, s, substr string) { } } +func HasSuffix(t *testing.T, s, suffix string) { + t.Helper() + + if !strings.HasSuffix(s, suffix) { + t.Errorf("\n [expected] %s to have suffix %s", s, suffix) + } +} + func CreateTempFile(t *testing.T, data string) string { dir := t.TempDir() path := filepath.Join(dir, "mock.file") diff --git a/snaps/clean.go b/snaps/clean.go index 1090667..4861f1c 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -84,8 +84,18 @@ func Clean(m *testing.M, opts ...CleanOpts) { _ = m runOnly := flag.Lookup("test.run").Value.String() count, _ := strconv.Atoi(flag.Lookup("test.count").Value.String()) + registeredStandaloneTests := occurrences( + standaloneTestsRegistry.cleanup, + count, + standaloneOccurrenceFMT, + ) - obsoleteFiles, usedFiles := examineFiles(testsRegistry.cleanup, runOnly, shouldClean && !isCI) + obsoleteFiles, usedFiles := examineFiles( + testsRegistry.cleanup, + registeredStandaloneTests, + runOnly, + shouldClean && !isCI, + ) obsoleteTests, err := examineSnaps( testsRegistry.cleanup, usedFiles, @@ -146,6 +156,7 @@ func isNumber(b []byte) bool { } /* +// TODO: update docs Map containing the occurrences is checked against the filesystem. If a file exists but is not registered in the map we check if the file is @@ -155,6 +166,7 @@ and we mark it as one. */ func examineFiles( registry map[string]map[string]int, + registeredStandaloneTests set, runOnly string, shouldUpdate bool, ) (obsolete, used []string) { @@ -164,18 +176,23 @@ func examineFiles( uniqueDirs[filepath.Dir(snapPaths)] = struct{}{} } + for snapPaths := range registeredStandaloneTests { + uniqueDirs[filepath.Dir(snapPaths)] = struct{}{} + } + for dir := range uniqueDirs { dirContents, _ := os.ReadDir(dir) for _, content := range dirContents { // this is a sanity check shouldn't have dirs inside the snapshot dirs // and only delete any `.snap` files - if content.IsDir() || filepath.Ext(content.Name()) != snapsExt { + if content.IsDir() || !strings.Contains(content.Name(), snapsExt) { continue } snapPath := filepath.Join(dir, content.Name()) - if _, called := registry[snapPath]; called { + if _, called := registry[snapPath]; called || + registeredStandaloneTests.Has(snapPath) { used = append(used, snapPath) continue } @@ -220,7 +237,7 @@ func examineSnaps( var hasDiffs bool - registeredTests := occurrences(registry[snapPath], count) + registeredTests := occurrences(registry[snapPath], count, snapshotOccurrenceFMT) s := snapshotScanner(f) for s.Scan() { @@ -389,7 +406,16 @@ func printEvent(w io.Writer, color, symbol, verb string, events int) { colors.Fprint(w, color, fmt.Sprintf("%s%v %s %s\n", symbol, events, subject, verb)) } +func standaloneOccurrenceFMT(s string, i int) string { + return fmt.Sprintf(s, i) +} + +func snapshotOccurrenceFMT(s string, i int) string { + return fmt.Sprintf("%s - %d", s, i) +} + /* +// TODO: update docs Builds a Set with all snapshot ids registered inside a snap file Form: testname - number id @@ -409,7 +435,7 @@ e.g as it means there are 3 snapshots created inside TestAdd */ -func occurrences(tests map[string]int, count int) set { +func occurrences(tests map[string]int, count int, formatter func(string, int) string) set { result := make(set, len(tests)) for testID, counter := range tests { // divide a test's counter by count (how many times the go test suite ran) @@ -417,10 +443,10 @@ func occurrences(tests map[string]int, count int) set { counter = counter / count if counter > 1 { for i := 1; i <= counter; i++ { - result[fmt.Sprintf("%s - %d", testID, i)] = struct{}{} + result[formatter(testID, i)] = struct{}{} } } - result[fmt.Sprintf("%s - %d", testID, counter)] = struct{}{} + result[formatter(testID, counter)] = struct{}{} } return result diff --git a/snaps/clean_test.go b/snaps/clean_test.go index f9c6143..2fc93d8 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -92,7 +92,7 @@ func TestExamineFiles(t *testing.T) { loadMockSnap(t, "mock-snap-1"), loadMockSnap(t, "mock-snap-2"), ) - obsolete, used := examineFiles(tests, "", false) + obsolete, used := examineFiles(tests, set{}, "", false) obsoleteExpected := []string{ filepath.FromSlash(dir1 + "/obsolete1.snap"), @@ -113,14 +113,14 @@ func TestExamineFiles(t *testing.T) { test.Equal(t, usedExpected, used) }) - t.Run("should remove outdate files", func(t *testing.T) { + t.Run("should remove outdated files", func(t *testing.T) { shouldUpdate := true tests, dir1, dir2 := setupTempExamineFiles( t, loadMockSnap(t, "mock-snap-1"), loadMockSnap(t, "mock-snap-2"), ) - examineFiles(tests, "", shouldUpdate) + examineFiles(tests, set{}, "", shouldUpdate) if _, err := os.Stat(filepath.FromSlash(dir1 + "/obsolete1.snap")); !errors.Is( err, @@ -329,7 +329,7 @@ func TestOccurrences(t *testing.T) { "divide - 2": {}, } - test.Equal(t, expected, occurrences(tests, 1)) + test.Equal(t, expected, occurrences(tests, 1, snapshotOccurrenceFMT)) }) t.Run("when count 3", func(t *testing.T) { @@ -350,7 +350,7 @@ func TestOccurrences(t *testing.T) { "divide - 3": {}, } - test.Equal(t, expected, occurrences(tests, 3)) + test.Equal(t, expected, occurrences(tests, 3, snapshotOccurrenceFMT)) }) } diff --git a/snaps/matchJSON.go b/snaps/matchJSON.go index 9fd5814..0eea31f 100644 --- a/snaps/matchJSON.go +++ b/snaps/matchJSON.go @@ -63,7 +63,7 @@ func MatchJSON(t testingT, input any, matchers ...match.JSONMatcher) { func matchJSON(c *config, t testingT, input any, matchers ...match.JSONMatcher) { t.Helper() - snapPath, snapPathRel := snapshotPath(c) + snapPath, snapPathRel := snapshotPath(c, t.Name(), false) testID := testsRegistry.getTestID(snapPath, t.Name()) t.Cleanup(func() { testsRegistry.reset(snapPath, t.Name()) diff --git a/snaps/matchSnapshot.go b/snaps/matchSnapshot.go index b381e68..5a66ce4 100644 --- a/snaps/matchSnapshot.go +++ b/snaps/matchSnapshot.go @@ -54,7 +54,7 @@ func matchSnapshot(c *config, t testingT, values ...any) { return } - snapPath, snapPathRel := snapshotPath(c) + snapPath, snapPathRel := snapshotPath(c, t.Name(), false) testID := testsRegistry.getTestID(snapPath, t.Name()) t.Cleanup(func() { testsRegistry.reset(snapPath, t.Name()) @@ -109,11 +109,11 @@ func matchSnapshot(c *config, t testingT, values ...any) { } func takeSnapshot(objects []any) string { - var snapshot string + snapshots := make([]string, len(objects)) - for i := 0; i < len(objects); i++ { - snapshot += pretty.Sprint(objects[i]) + "\n" + for i, object := range objects { + snapshots[i] = pretty.Sprint(object) } - return strings.TrimSuffix(escapeEndChars(snapshot), "\n") + return escapeEndChars(strings.Join(snapshots, "\n")) } diff --git a/snaps/matchStandaloneSnapshot.go b/snaps/matchStandaloneSnapshot.go new file mode 100644 index 0000000..2e58be9 --- /dev/null +++ b/snaps/matchStandaloneSnapshot.go @@ -0,0 +1,80 @@ +package snaps + +import ( + "errors" + + "github.com/kr/pretty" +) + +func (c *config) MatchStandaloneSnapshot(t testingT, value any) { + t.Helper() + + matchStandaloneSnapshot(c, t, value) +} + +func MatchStandaloneSnapshot(t testingT, value any) { + t.Helper() + + matchStandaloneSnapshot(&defaultConfig, t, value) +} + +func matchStandaloneSnapshot(c *config, t testingT, value any) { + t.Helper() + + genericPathSnap, genericSnapPathRel := snapshotPath(c, t.Name(), true) + snapPath, snapPathRel := standaloneTestsRegistry.getTestID(genericPathSnap, genericSnapPathRel) + t.Cleanup(func() { + standaloneTestsRegistry.reset(genericPathSnap) + }) + + snapshot := takeStandaloneSnapshot(value) + prevSnapshot, err := getPrevStandaloneSnapshot(snapPath) + if errors.Is(err, errSnapNotFound) { + if isCI { + handleError(t, err) + return + } + + err := upsertStandaloneSnapshot(snapshot, snapPath) + if err != nil { + handleError(t, err) + return + } + + t.Log(addedMsg) + testEvents.register(added) + return + } + if err != nil { + handleError(t, err) + return + } + + diff := prettyDiff( + prevSnapshot, + snapshot, + snapPathRel, + 0, + ) + if diff == "" { + testEvents.register(passed) + return + } + + if !shouldUpdate(nil) { + handleError(t, diff) + return + } + + if err = upsertStandaloneSnapshot(snapshot, snapPath); err != nil { + handleError(t, err) + return + } + + t.Log(updatedMsg) + testEvents.register(updated) +} + +func takeStandaloneSnapshot(data any) string { + return pretty.Sprint(data) +} diff --git a/snaps/snapshot.go b/snaps/snapshot.go index 05c588f..319b19e 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -14,9 +14,10 @@ import ( ) var ( - testsRegistry = newRegistry() - _m = sync.RWMutex{} - endSequenceByteSlice = []byte(endSequence) + testsRegistry = newRegistry() + standaloneTestsRegistry = newStandaloneRegistry() + _m = sync.RWMutex{} + endSequenceByteSlice = []byte(endSequence) ) var ( @@ -25,9 +26,10 @@ var ( ) type config struct { - filename string - snapsDir string - update *bool + filename string + snapsDir string + extension string + update *bool } // Update determines whether to update snapshots or not @@ -43,7 +45,7 @@ func Update(u bool) func(*config) { // // default: __snapshots__ // -// this doesn't change the file extension +// this doesn't change the file extension see `snap.Ext` func Filename(name string) func(*config) { return func(c *config) { c.filename = name @@ -61,6 +63,18 @@ func Dir(dir string) func(*config) { } } +// Specify file name extension +// +// default: .snap +// +// Note: even if you specify a different extension the file still contain .snap +// e.g. if you specify .txt the file will be .snap.txt +func Ext(ext string) func(*config) { + return func(c *config) { + c.extension = ext + } +} + // Create snaps with configuration // // e.g snaps.WithConfig(snaps.Filename("my_test")).MatchSnapshot(t, "hello world") @@ -124,6 +138,37 @@ func newRegistry() *syncRegistry { } } +type syncStandaloneRegistry struct { + running map[string]int + cleanup map[string]int + sync.Mutex +} + +func newStandaloneRegistry() *syncStandaloneRegistry { + return &syncStandaloneRegistry{ + running: make(map[string]int), + cleanup: make(map[string]int), + Mutex: sync.Mutex{}, + } +} + +func (s *syncStandaloneRegistry) getTestID(snapPath, snapPathRel string) (string, string) { + s.Lock() + + s.running[snapPath]++ + s.cleanup[snapPath]++ + c := s.running[snapPath] + s.Unlock() + + return fmt.Sprintf(snapPath, c), fmt.Sprintf(snapPathRel, c) +} + +func (s *syncStandaloneRegistry) reset(snapPath string) { + s.Lock() + s.running[snapPath] = 0 + s.Unlock() +} + // getPrevSnapshot scans file searching for a snapshot matching the given testID and returns // the snapshot with the line where is located inside the file. // @@ -241,6 +286,23 @@ func removeSnapshot(s *bufio.Scanner) { } } +func upsertStandaloneSnapshot(snapshot, snapPath string) error { + if err := os.MkdirAll(filepath.Dir(snapPath), os.ModePerm); err != nil { + return err + } + + return os.WriteFile(snapPath, []byte(snapshot), os.ModePerm) +} + +func getPrevStandaloneSnapshot(snapPath string) (string, error) { + f, err := os.ReadFile(snapPath) + if err != nil { + return "", errSnapNotFound + } + + return string(f), nil +} + /* Returns the path for snapshots - if no config provided returns the directory where tests are running @@ -248,12 +310,18 @@ Returns the path for snapshots - if snapsDir is absolute path then we are returning this path 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 + - if extension provided we return the filename with `.snap` and the provided extension + + - if it's standalone snapshot we also append an integer (_%d) in the filename (even before `.snap`) + Returns the relative path of the caller and the snapshot path. */ -func snapshotPath(c *config) (string, string) { +func snapshotPath(c *config, tName string, isStandalone bool) (string, string) { // skips current func, the wrapper match* and the exported Match* func callerFilename := baseCaller(3) @@ -262,15 +330,29 @@ func snapshotPath(c *config) (string, string) { dir = filepath.Join(filepath.Dir(callerFilename), c.snapsDir) } + snapPath := filepath.Join(dir, constructFilename(c, callerFilename, tName, isStandalone)) + snapPathRel, _ := filepath.Rel(filepath.Dir(callerFilename), snapPath) + + return snapPath, snapPathRel +} + +func constructFilename(c *config, callerFilename, tName string, isStandalone bool) string { filename := c.filename if filename == "" { base := filepath.Base(callerFilename) filename = strings.TrimSuffix(base, filepath.Ext(base)) + + if isStandalone { + filename += fmt.Sprintf("_%s", strings.ReplaceAll(tName, "/", "_")) + } } - snapPath := filepath.Join(dir, filename+snapsExt) - snapPathRel, _ := filepath.Rel(filepath.Dir(callerFilename), snapPath) - return snapPath, snapPathRel + if isStandalone { + filename += "_%d" + } + filename += snapsExt + c.extension + + return filename } func unescapeEndChars(s string) string { diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index eee23d3..5c5b8a5 100644 --- a/snaps/snapshot_test.go +++ b/snaps/snapshot_test.go @@ -53,6 +53,16 @@ func TestSyncRegistry(t *testing.T) { }) } +func TestSyncStandaloneRegistry(t *testing.T) { + t.Run("should increment id on each call [concurrent safe]", func(t *testing.T) { + t.Log("to implement") + }) + + t.Run("should reset running registry", func(t *testing.T) { + t.Log("to implement") + }) +} + func TestGetPrevSnapshot(t *testing.T) { t.Run("should return errSnapNotFound", func(t *testing.T) { snap, line, err := getPrevSnapshot("", "") @@ -139,67 +149,120 @@ func TestAddNewSnapshot(t *testing.T) { 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 ( - snapPath string - snapPathRel string - ) - +func TestSnapshotPath(t *testing.T) { + snapshotPathWrapper := func(c *config, tName string, isStandalone bool) (snapPath, snapPathRel string) { + // This is for emulating being called from a func so we can find the correct file + // of the caller func() { - // This is for emulating being called from a func so we can find the correct file - // of the caller func() { - snapPath, snapPathRel = snapshotPath(&defaultConfig) + snapPath, snapPathRel = snapshotPath(c, tName, isStandalone) }() }() - test.Contains(t, snapPath, filepath.FromSlash("/snaps/__snapshots__")) - test.Contains(t, snapPathRel, filepath.FromSlash("__snapshots__/snapshot_test.snap")) + return + } + + t.Run("should return default path and file", func(t *testing.T) { + snapPath, snapPathRel := snapshotPathWrapper(&defaultConfig, "", false) + + test.HasSuffix(t, snapPath, filepath.FromSlash("/snaps/__snapshots__/snapshot_test.snap")) + test.Equal(t, filepath.FromSlash("__snapshots__/snapshot_test.snap"), snapPathRel) }) t.Run("should return path and file from config", func(t *testing.T) { - var ( - 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() { - snapPath, snapPathRel = snapshotPath(&config{ - filename: "my_file", - snapsDir: "my_snapshot_dir", - }) - }() - }() + snapPath, snapPathRel := snapshotPathWrapper(&config{ + filename: "my_file", + snapsDir: "my_snapshot_dir", + }, "", false) // returns the current file's path /snaps/* - test.Contains(t, snapPath, filepath.FromSlash("/snaps/my_snapshot_dir")) - test.Contains(t, snapPathRel, filepath.FromSlash("my_snapshot_dir/my_file.snap")) + test.HasSuffix(t, snapPath, filepath.FromSlash("/snaps/my_snapshot_dir/my_file.snap")) + test.Equal(t, filepath.FromSlash("my_snapshot_dir/my_file.snap"), snapPathRel) }) t.Run("should return absolute path", func(t *testing.T) { - var ( - snapPath string - snapPathRel string + snapPath, snapPathRel := snapshotPathWrapper(&config{ + filename: "my_file", + snapsDir: "/path_to/my_snapshot_dir", + }, "", false) + + test.HasSuffix(t, snapPath, filepath.FromSlash("/path_to/my_snapshot_dir/my_file.snap")) + // the depth depends on filesystem structure + test.HasSuffix( + t, + snapPathRel, + filepath.FromSlash("../../path_to/my_snapshot_dir/my_file.snap"), ) + }) - func() { - // This is for emulating being called from a func so we can find the correct file - // of the caller - func() { - snapPath, snapPathRel = snapshotPath(&config{ - filename: "my_file", - snapsDir: "/path_to/my_snapshot_dir", - }) - }() - }() + t.Run("should add extension to filename", func(t *testing.T) { + snapPath, snapPathRel := snapshotPathWrapper(&config{ + filename: "my_file", + snapsDir: "my_snapshot_dir", + extension: ".txt", + }, "", false) + + test.HasSuffix(t, snapPath, filepath.FromSlash("/snaps/my_snapshot_dir/my_file.snap.txt")) + test.Equal(t, filepath.FromSlash("my_snapshot_dir/my_file.snap.txt"), snapPathRel) + }) + + t.Run("should return standalone snapPath", func(t *testing.T) { + snapPath, snapPathRel := snapshotPathWrapper(&defaultConfig, "my_test", true) + + test.HasSuffix( + t, + snapPath, + filepath.FromSlash("/snaps/__snapshots__/snapshot_test_my_test_%d.snap"), + ) + test.Equal( + t, + filepath.FromSlash("__snapshots__/snapshot_test_my_test_%d.snap"), + snapPathRel, + ) + }) + + t.Run("should return standalone snapPath without '/'", func(t *testing.T) { + snapPath, snapPathRel := snapshotPathWrapper(&defaultConfig, "TestFunction/my_test", true) + + test.HasSuffix( + t, + snapPath, + filepath.FromSlash("/snaps/__snapshots__/snapshot_test_TestFunction_my_test_%d.snap"), + ) + test.Equal( + t, + filepath.FromSlash("__snapshots__/snapshot_test_TestFunction_my_test_%d.snap"), + snapPathRel, + ) + }) + + t.Run("should return standalone snapPath with overridden filename", func(t *testing.T) { + snapPath, snapPathRel := snapshotPathWrapper(&config{ + filename: "my_file", + snapsDir: "my_snapshot_dir", + }, "my_test", true) - 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")) + test.HasSuffix(t, snapPath, filepath.FromSlash("/snaps/my_snapshot_dir/my_file_%d.snap")) + test.Equal(t, filepath.FromSlash("my_snapshot_dir/my_file_%d.snap"), snapPathRel) }) + + t.Run( + "should return standalone snapPath with overridden filename and extension", + func(t *testing.T) { + snapPath, snapPathRel := snapshotPathWrapper(&config{ + filename: "my_file", + snapsDir: "my_snapshot_dir", + extension: ".txt", + }, "my_test", true) + + test.HasSuffix( + t, + snapPath, + filepath.FromSlash("/snaps/my_snapshot_dir/my_file_%d.snap.txt"), + ) + test.Equal(t, filepath.FromSlash("my_snapshot_dir/my_file_%d.snap.txt"), snapPathRel) + }, + ) } func TestUpdateSnapshot(t *testing.T) { From a34dc3145e8b7a95a20f8ed6b034af36b3fa83d2 Mon Sep 17 00:00:00 2001 From: Georgios Kampitakis Date: Sat, 20 Jul 2024 20:51:41 +0100 Subject: [PATCH 2/4] some more work --- README.md | 7 ++ examples/matchStandaloneSnapshot_test.go | 7 ++ snaps/clean.go | 50 +++------ snaps/clean_test.go | 128 ++++++++++++++++------- snaps/matchStandaloneSnapshot.go | 9 +- snaps/matchStandaloneSnapshot_test.go | 21 ++++ snaps/snapshot.go | 38 +++---- snaps/snapshot_test.go | 60 ++++++++++- 8 files changed, 217 insertions(+), 103 deletions(-) create mode 100644 examples/matchStandaloneSnapshot_test.go create mode 100644 snaps/matchStandaloneSnapshot_test.go diff --git a/README.md b/README.md index 01c2b87..523622b 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ - [match.Any](#matchany) - [match.Custom](#matchcustom) - [match.Type\[ExpectedType\]](#matchtype) +- [MatchStandaloneSnapshot](#matchstandalonesnapshot) - [Configuration](#configuration) - [Update Snapshots](#update-snapshots) - [Clean obsolete Snapshots](#clean-obsolete-snapshots) @@ -195,12 +196,17 @@ match.Type[string]("user.info"). You can see more [examples](./examples/matchJSON_test.go#L96). +## MatchStandaloneSnapshot + +TBD + ## Configuration `go-snaps` allows passing configuration for overriding - the directory where snapshots are stored, _relative or absolute path_ - the filename where snapshots are stored +- the snapshot file's extension (_regardless the extension the filename will include the `.snaps` inside the filename_) - programmatically control whether to update snapshots. _You can find an example usage at [examples](/examples/examples_test.go#13)_ ```go @@ -210,6 +216,7 @@ t.Run("snapshot tests", func(t *testing.T) { s := snaps.WithConfig( snaps.Dir("my_dir"), snaps.Filename("json_file"), + snaps.Ext(".json") snaps.Update(false) ) diff --git a/examples/matchStandaloneSnapshot_test.go b/examples/matchStandaloneSnapshot_test.go new file mode 100644 index 0000000..d7835b5 --- /dev/null +++ b/examples/matchStandaloneSnapshot_test.go @@ -0,0 +1,7 @@ +package examples + +import "testing" + +func TestMatchStandaloneSnapshot(t *testing.T) { + t.Log("to be implemented") +} diff --git a/snaps/clean.go b/snaps/clean.go index 4861f1c..73761d1 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -17,10 +17,7 @@ import ( "github.com/maruel/natural" ) -// Matches [ Test... - number ] testIDs -var ( - testEvents = newTestEvents() -) +var testEvents = newTestEvents() const ( erred uint8 = iota @@ -155,15 +152,10 @@ func isNumber(b []byte) bool { return true } -/* -// TODO: update docs -Map containing the occurrences is checked against the filesystem. - -If a file exists but is not registered in the map we check if the file is -skipped. (We do that by checking if the mod is imported and there is a call to -`MatchSnapshot`). If not skipped and not registered means it's an obsolete snap file -and we mark it as one. -*/ +// examineFiles traverses all the directories where snap tests where executed and checks +// if "orphan" snap files exist (files containing `.snap` in their name). +// +// If they do they are marked as obsolete and they are either deleted if `shouldUpdate=true` or printed on the console. func examineFiles( registry map[string]map[string]int, registeredStandaloneTests set, @@ -191,12 +183,18 @@ func examineFiles( } snapPath := filepath.Join(dir, content.Name()) - if _, called := registry[snapPath]; called || - registeredStandaloneTests.Has(snapPath) { + if _, called := registry[snapPath]; called { used = append(used, snapPath) continue } + // if it's a standalone snapshot we don't add it to used list + // as we don't need it for the next step, to examine individual snaps inside the file + // as it contains only one + if registeredStandaloneTests.Has(snapPath) { + continue + } + if isFileSkipped(dir, content.Name(), runOnly) { continue } @@ -414,27 +412,7 @@ func snapshotOccurrenceFMT(s string, i int) string { return fmt.Sprintf("%s - %d", s, i) } -/* -// TODO: update docs -Builds a Set with all snapshot ids registered inside a snap file -Form: testname - number id - -tests have the form - - map[filepath]: map[testname]: - -e.g - - ./path/__snapshots__/add_test.snap map[TestAdd] 3 - - will result to - - TestAdd - 1 - TestAdd - 2 - TestAdd - 3 - -as it means there are 3 snapshots created inside TestAdd -*/ +// Builds a Set with all snapshot ids registered. It uses the provider formatter to build keys. func occurrences(tests map[string]int, count int, formatter func(string, int) string) set { result := make(set, len(tests)) for testID, counter := range tests { diff --git a/snaps/clean_test.go b/snaps/clean_test.go index 2fc93d8..79da6ea 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -55,6 +55,30 @@ func setupTempExamineFiles( name: filepath.FromSlash(dir2 + "/should_not_delete.txt"), data: []byte{}, }, + { + name: filepath.FromSlash(dir1 + "TestSomething_my_test_1.snap"), + data: []byte{}, + }, + { + name: filepath.FromSlash(dir1 + "TestSomething_my_test_2.snap"), + data: []byte{}, + }, + { + name: filepath.FromSlash(dir1 + "TestSomething_my_test_3.snap"), + data: []byte{}, + }, + { + name: filepath.FromSlash(dir2 + "TestAnotherThing_my_test_1.snap"), + data: []byte{}, + }, + { + name: filepath.FromSlash(dir2 + "TestAnotherThing_my_simple_test_1.snap"), + data: []byte{}, + }, + { + name: filepath.FromSlash(dir2 + "TestAnotherThing_my_simple_test_2.snap"), + data: []byte{}, + }, } for _, file := range files { @@ -92,11 +116,18 @@ func TestExamineFiles(t *testing.T) { loadMockSnap(t, "mock-snap-1"), loadMockSnap(t, "mock-snap-2"), ) - obsolete, used := examineFiles(tests, set{}, "", false) + obsolete, used := examineFiles(tests, set{ + dir1 + "TestSomething_my_test_1.snap": struct{}{}, + dir2 + "TestAnotherThing_my_simple_test_1.snap": struct{}{}, + }, "", false) obsoleteExpected := []string{ filepath.FromSlash(dir1 + "/obsolete1.snap"), filepath.FromSlash(dir2 + "/obsolete2.snap"), + filepath.FromSlash(dir1 + "TestSomething_my_test_2.snap"), + filepath.FromSlash(dir1 + "TestSomething_my_test_3.snap"), + filepath.FromSlash(dir2 + "TestAnotherThing_my_test_1.snap"), + filepath.FromSlash(dir2 + "TestAnotherThing_my_simple_test_2.snap"), } usedExpected := []string{ filepath.FromSlash(dir1 + "/test1.snap"), @@ -120,20 +151,25 @@ func TestExamineFiles(t *testing.T) { loadMockSnap(t, "mock-snap-1"), loadMockSnap(t, "mock-snap-2"), ) - examineFiles(tests, set{}, "", shouldUpdate) - - if _, err := os.Stat(filepath.FromSlash(dir1 + "/obsolete1.snap")); !errors.Is( - err, - os.ErrNotExist, - ) { - t.Error("obsolete obsolete1.snap not removed") - } - - if _, err := os.Stat(filepath.FromSlash(dir2 + "/obsolete2.snap")); !errors.Is( - err, - os.ErrNotExist, - ) { - t.Error("obsolete obsolete2.snap not removed") + examineFiles(tests, set{ + dir1 + "TestSomething_my_test_1.snap": struct{}{}, + dir2 + "TestAnotherThing_my_simple_test_1.snap": struct{}{}, + }, "", shouldUpdate) + + for _, obsoleteFilename := range []string{ + dir1 + "/obsolete1.snap", + dir2 + "/obsolete2.snap", + dir1 + "TestSomething_my_test_2.snap", + dir1 + "TestSomething_my_test_3.snap", + dir2 + "TestAnotherThing_my_test_1.snap", + dir2 + "TestAnotherThing_my_simple_test_2.snap", + } { + if _, err := os.Stat(filepath.FromSlash(obsoleteFilename)); !errors.Is( + err, + os.ErrNotExist, + ) { + t.Errorf("obsolete file %s not removed", obsoleteFilename) + } } }) } @@ -315,42 +351,64 @@ string hello world 2 2 1 func TestOccurrences(t *testing.T) { t.Run("when count 1", func(t *testing.T) { tests := map[string]int{ - "add": 3, - "subtract": 1, - "divide": 2, + "add_%d": 3, + "subtract_%d": 1, + "divide_%d": 2, } expected := set{ - "add - 1": {}, - "add - 2": {}, - "add - 3": {}, - "subtract - 1": {}, - "divide - 1": {}, - "divide - 2": {}, + "add_%d - 1": {}, + "add_%d - 2": {}, + "add_%d - 3": {}, + "subtract_%d - 1": {}, + "divide_%d - 1": {}, + "divide_%d - 2": {}, + } + + expectedStandalone := set{ + "add_1": {}, + "add_2": {}, + "add_3": {}, + "subtract_1": {}, + "divide_1": {}, + "divide_2": {}, } test.Equal(t, expected, occurrences(tests, 1, snapshotOccurrenceFMT)) + test.Equal(t, expectedStandalone, occurrences(tests, 1, standaloneOccurrenceFMT)) }) t.Run("when count 3", func(t *testing.T) { tests := map[string]int{ - "add": 12, - "subtract": 3, - "divide": 9, + "add_%d": 12, + "subtract_%d": 3, + "divide_%d": 9, } expected := set{ - "add - 1": {}, - "add - 2": {}, - "add - 3": {}, - "add - 4": {}, - "subtract - 1": {}, - "divide - 1": {}, - "divide - 2": {}, - "divide - 3": {}, + "add_%d - 1": {}, + "add_%d - 2": {}, + "add_%d - 3": {}, + "add_%d - 4": {}, + "subtract_%d - 1": {}, + "divide_%d - 1": {}, + "divide_%d - 2": {}, + "divide_%d - 3": {}, + } + + expectedStandalone := set{ + "add_1": {}, + "add_2": {}, + "add_3": {}, + "add_4": {}, + "subtract_1": {}, + "divide_1": {}, + "divide_2": {}, + "divide_3": {}, } test.Equal(t, expected, occurrences(tests, 3, snapshotOccurrenceFMT)) + test.Equal(t, expectedStandalone, occurrences(tests, 3, standaloneOccurrenceFMT)) }) } diff --git a/snaps/matchStandaloneSnapshot.go b/snaps/matchStandaloneSnapshot.go index 2e58be9..fa2807d 100644 --- a/snaps/matchStandaloneSnapshot.go +++ b/snaps/matchStandaloneSnapshot.go @@ -27,7 +27,7 @@ func matchStandaloneSnapshot(c *config, t testingT, value any) { standaloneTestsRegistry.reset(genericPathSnap) }) - snapshot := takeStandaloneSnapshot(value) + snapshot := pretty.Sprint(value) prevSnapshot, err := getPrevStandaloneSnapshot(snapPath) if errors.Is(err, errSnapNotFound) { if isCI { @@ -54,7 +54,8 @@ func matchStandaloneSnapshot(c *config, t testingT, value any) { prevSnapshot, snapshot, snapPathRel, - 0, + // TODO: what should this be? or skipped entirely + 1, ) if diff == "" { testEvents.register(passed) @@ -74,7 +75,3 @@ func matchStandaloneSnapshot(c *config, t testingT, value any) { t.Log(updatedMsg) testEvents.register(updated) } - -func takeStandaloneSnapshot(data any) string { - return pretty.Sprint(data) -} diff --git a/snaps/matchStandaloneSnapshot_test.go b/snaps/matchStandaloneSnapshot_test.go new file mode 100644 index 0000000..48a3c55 --- /dev/null +++ b/snaps/matchStandaloneSnapshot_test.go @@ -0,0 +1,21 @@ +package snaps + +import "testing" + +func TestMatchStandaloneSnapshot(t *testing.T) { + t.Run("should create snapshot", func(t *testing.T) { + t.Log("to be implemented") + }) + + t.Run("if it's running on ci should skip", func(t *testing.T) { + t.Log("to be implemented") + }) + + t.Run("should return error when diff is found", func(t *testing.T) { + t.Log("to be implemented") + }) + + t.Run("should update snapshot", func(t *testing.T) { + t.Log("to be implemented") + }) +} diff --git a/snaps/snapshot.go b/snaps/snapshot.go index 319b19e..e68942c 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -94,11 +94,9 @@ func handleError(t testingT, err any) { testEvents.register(erred) } -/* -We track occurrence as in the same test we can run multiple snapshots -This also helps with keeping track with obsolete snaps -map[snap path]: map[testname]: -*/ +// We track occurrence as in the same test we can run multiple snapshots +// This also helps with keeping track with obsolete snaps +// map[snap path]: map[testname]: type syncRegistry struct { running map[string]map[string]int cleanup map[string]map[string]int @@ -303,24 +301,18 @@ func getPrevStandaloneSnapshot(snapPath string) (string, error) { return string(f), nil } -/* -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 - -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 - - - if extension provided we return the filename with `.snap` and the provided extension - - - if it's standalone snapshot we also append an integer (_%d) in the filename (even before `.snap`) - -Returns the relative path of the caller and the snapshot path. -*/ +// 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 +// +// 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 +// - if extension provided we return the filename with `.snap` and the provided extension +// - if it's standalone snapshot we also append an integer (_%d) in the filename (even before `.snap`) +// +// Returns the relative path of the caller and the snapshot path. func snapshotPath(c *config, tName string, isStandalone bool) (string, string) { // skips current func, the wrapper match* and the exported Match* func callerFilename := baseCaller(3) diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index 5c5b8a5..04e326c 100644 --- a/snaps/snapshot_test.go +++ b/snaps/snapshot_test.go @@ -55,11 +55,65 @@ func TestSyncRegistry(t *testing.T) { func TestSyncStandaloneRegistry(t *testing.T) { t.Run("should increment id on each call [concurrent safe]", func(t *testing.T) { - t.Log("to implement") + wg := sync.WaitGroup{} + registry := newStandaloneRegistry() + + for i := 0; i < 5; i++ { + wg.Add(1) + + go func() { + registry.getTestID("/file/my_file_%d.snap", "./__snapshots__/my_file_%d.snap") + wg.Done() + }() + } + + wg.Wait() + + snapPath, snapPathRel := registry.getTestID( + "/file/my_file_%d.snap", + "./__snapshots__/my_file_%d.snap", + ) + + test.Equal(t, "/file/my_file_6.snap", snapPath) + test.Equal(t, "./__snapshots__/my_file_6.snap", snapPathRel) + + snapPath, snapPathRel = registry.getTestID( + "/file/my_other_file_%d.snap", + "./__snapshots__/my_other_file_%d.snap", + ) + + test.Equal(t, "/file/my_other_file_1.snap", snapPath) + test.Equal(t, "./__snapshots__/my_other_file_1.snap", snapPathRel) + test.Equal(t, registry.cleanup, registry.running) }) t.Run("should reset running registry", func(t *testing.T) { - t.Log("to implement") + wg := sync.WaitGroup{} + registry := newStandaloneRegistry() + + for i := 0; i < 100; i++ { + wg.Add(1) + + go func() { + registry.getTestID("/file/my_file_%d.snap", "./__snapshots__/my_file_%d.snap") + wg.Done() + }() + } + + wg.Wait() + + registry.reset("/file/my_file_%d.snap") + + snapPath, snapPathRel := registry.getTestID( + "/file/my_file_%d.snap", + "./__snapshots__/my_file_%d.snap", + ) + + // running registry start from 0 again + test.Equal(t, "/file/my_file_1.snap", snapPath) + test.Equal(t, "./__snapshots__/my_file_1.snap", snapPathRel) + // cleanup registry still has 101 + test.Equal(t, 101, registry.cleanup["/file/my_file_%d.snap"]) }) } @@ -191,7 +245,7 @@ func TestSnapshotPath(t *testing.T) { test.HasSuffix( t, snapPathRel, - filepath.FromSlash("../../path_to/my_snapshot_dir/my_file.snap"), + filepath.FromSlash("path_to/my_snapshot_dir/my_file.snap"), ) }) From c8c7cd837a3deaa8d86d978f1a241837f77860e1 Mon Sep 17 00:00:00 2001 From: Georgios Kampitakis Date: Sun, 21 Jul 2024 21:16:32 +0100 Subject: [PATCH 3/4] add unit tests and some simple examples --- ...t_should_create_html_snapshots_1.snap.html | 11 ++ ...t_should_create_html_snapshots_2.snap.html | 1 + .../__snapshots__/my_standalone_snap_1.snap | 1 + .../__snapshots__/my_standalone_snap_2.snap | 1 + .../__snapshots__/my_standalone_snap_3.snap | 1 + .../__snapshots__/my_standalone_snap_4.snap | 1 + examples/matchStandaloneSnapshot_test.go | 37 +++- snaps/matchSnapshot_test.go | 3 +- snaps/matchStandaloneSnapshot.go | 2 +- snaps/matchStandaloneSnapshot_test.go | 166 +++++++++++++++++- 10 files changed, 214 insertions(+), 10 deletions(-) create mode 100755 examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html create mode 100755 examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html create mode 100755 examples/__snapshots__/my_standalone_snap_1.snap create mode 100755 examples/__snapshots__/my_standalone_snap_2.snap create mode 100755 examples/__snapshots__/my_standalone_snap_3.snap create mode 100755 examples/__snapshots__/my_standalone_snap_4.snap diff --git a/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html b/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html new file mode 100755 index 0000000..eb7386c --- /dev/null +++ b/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html @@ -0,0 +1,11 @@ + + + + + +

My First Heading

+ +

My first paragraph.

+ + + diff --git a/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html b/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html new file mode 100755 index 0000000..34fa97d --- /dev/null +++ b/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html @@ -0,0 +1 @@ +
Hello World
\ No newline at end of file diff --git a/examples/__snapshots__/my_standalone_snap_1.snap b/examples/__snapshots__/my_standalone_snap_1.snap new file mode 100755 index 0000000..8f2a542 --- /dev/null +++ b/examples/__snapshots__/my_standalone_snap_1.snap @@ -0,0 +1 @@ +hello world-0 \ No newline at end of file diff --git a/examples/__snapshots__/my_standalone_snap_2.snap b/examples/__snapshots__/my_standalone_snap_2.snap new file mode 100755 index 0000000..f842d2f --- /dev/null +++ b/examples/__snapshots__/my_standalone_snap_2.snap @@ -0,0 +1 @@ +hello world-1 \ No newline at end of file diff --git a/examples/__snapshots__/my_standalone_snap_3.snap b/examples/__snapshots__/my_standalone_snap_3.snap new file mode 100755 index 0000000..22aacf2 --- /dev/null +++ b/examples/__snapshots__/my_standalone_snap_3.snap @@ -0,0 +1 @@ +hello world-2 \ No newline at end of file diff --git a/examples/__snapshots__/my_standalone_snap_4.snap b/examples/__snapshots__/my_standalone_snap_4.snap new file mode 100755 index 0000000..854f9e6 --- /dev/null +++ b/examples/__snapshots__/my_standalone_snap_4.snap @@ -0,0 +1 @@ +hello world-3 \ No newline at end of file diff --git a/examples/matchStandaloneSnapshot_test.go b/examples/matchStandaloneSnapshot_test.go index d7835b5..bae6ef1 100644 --- a/examples/matchStandaloneSnapshot_test.go +++ b/examples/matchStandaloneSnapshot_test.go @@ -1,7 +1,40 @@ package examples -import "testing" +import ( + "testing" + + "github.com/gkampitakis/go-snaps/snaps" +) func TestMatchStandaloneSnapshot(t *testing.T) { - t.Log("to be implemented") + t.Run("should create html snapshots", func(t *testing.T) { + snaps := snaps.WithConfig( + snaps.Ext(".html"), + ) + + snaps.MatchStandaloneSnapshot(t, ` + + + + +

My First Heading

+ +

My first paragraph.

+ + + +`) + snaps.MatchStandaloneSnapshot(t, "
Hello World
") + }) + + t.Run("should create standalone snapshots with specified filename", func(t *testing.T) { + snaps := snaps.WithConfig( + snaps.Filename("my_standalone_snap"), + ) + + snaps.MatchStandaloneSnapshot(t, "hello world-0") + snaps.MatchStandaloneSnapshot(t, "hello world-1") + snaps.MatchStandaloneSnapshot(t, "hello world-2") + snaps.MatchStandaloneSnapshot(t, "hello world-3") + }) } diff --git a/snaps/matchSnapshot_test.go b/snaps/matchSnapshot_test.go index 87c3a9d..9e4605f 100644 --- a/snaps/matchSnapshot_test.go +++ b/snaps/matchSnapshot_test.go @@ -47,6 +47,7 @@ func setupSnapshot(t *testing.T, file string, ci bool, update ...bool) string { t.Cleanup(func() { os.Remove(snapPath) testsRegistry = newRegistry() + standaloneTestsRegistry = newStandaloneRegistry() testEvents = newTestEvents() isCI = ciinfo.IsCI updateVAR = updateVARPrev @@ -78,7 +79,7 @@ func TestMatchSnapshot(t *testing.T) { test.Equal(t, 1, testsRegistry.cleanup[snapPath]["mock-name"]) }) - t.Run("if it's running on ci should skip", func(t *testing.T) { + t.Run("if it's running on ci should skip creating snapshot", func(t *testing.T) { setupSnapshot(t, fileName, true) mockT := test.NewMockTestingT(t) diff --git a/snaps/matchStandaloneSnapshot.go b/snaps/matchStandaloneSnapshot.go index fa2807d..060936d 100644 --- a/snaps/matchStandaloneSnapshot.go +++ b/snaps/matchStandaloneSnapshot.go @@ -62,7 +62,7 @@ func matchStandaloneSnapshot(c *config, t testingT, value any) { return } - if !shouldUpdate(nil) { + if !shouldUpdate(c.update) { handleError(t, diff) return } diff --git a/snaps/matchStandaloneSnapshot_test.go b/snaps/matchStandaloneSnapshot_test.go index 48a3c55..cee9ee5 100644 --- a/snaps/matchStandaloneSnapshot_test.go +++ b/snaps/matchStandaloneSnapshot_test.go @@ -1,21 +1,175 @@ package snaps -import "testing" +import ( + "path/filepath" + "testing" + + "github.com/gkampitakis/go-snaps/internal/test" +) + +const standaloneFilename = "matchStandaloneSnapshot_test_mock-name_1.snap" func TestMatchStandaloneSnapshot(t *testing.T) { t.Run("should create snapshot", func(t *testing.T) { - t.Log("to be implemented") + snapPath := setupSnapshot(t, standaloneFilename, false) + mockT := test.NewMockTestingT(t) + mockT.MockLog = func(args ...any) { test.Equal(t, addedMsg, args[0].(string)) } + + MatchStandaloneSnapshot(mockT, "hello world") + + test.Equal(t, "hello world", test.GetFileContent(t, snapPath)) + test.Equal(t, 1, testEvents.items[added]) + // clean up function called + + registryKey := filepath.Join( + filepath.Dir(snapPath), + "matchStandaloneSnapshot_test_mock-name_%d.snap", + ) + test.Equal(t, 0, standaloneTestsRegistry.running[registryKey]) + test.Equal(t, 1, standaloneTestsRegistry.cleanup[registryKey]) + }) + + t.Run("should pass tests with no diff", func(t *testing.T) { + snapPath := setupSnapshot(t, standaloneFilename, false, false) + + printerExpectedCalls := []func(received any){ + func(received any) { test.Equal(t, addedMsg, received.(string)) }, + func(received any) { t.Error("should not be called 3rd time") }, + } + mockT := test.NewMockTestingT(t) + mockT.MockLog = func(args ...any) { + printerExpectedCalls[0](args[0]) + + // shift + printerExpectedCalls = printerExpectedCalls[1:] + } + + s := WithConfig(Update(true)) + // First call for creating the snapshot + s.MatchStandaloneSnapshot(mockT, "hello world") + test.Equal(t, 1, testEvents.items[added]) + + // Resetting registry to emulate the same MatchStandaloneSnapshot call + standaloneTestsRegistry = newStandaloneRegistry() + + // Second call with same params + s.MatchStandaloneSnapshot(mockT, "hello world") + + test.Equal(t, "hello world", test.GetFileContent(t, snapPath)) + test.Equal(t, 1, testEvents.items[passed]) }) - t.Run("if it's running on ci should skip", func(t *testing.T) { - t.Log("to be implemented") + t.Run("if it's running on ci should skip creating snapshot", func(t *testing.T) { + setupSnapshot(t, standaloneFilename, true) + + mockT := test.NewMockTestingT(t) + mockT.MockError = func(args ...any) { + test.Equal(t, errSnapNotFound, args[0].(error)) + } + + MatchStandaloneSnapshot(mockT, "hello world") + + test.Equal(t, 1, testEvents.items[erred]) }) t.Run("should return error when diff is found", func(t *testing.T) { - t.Log("to be implemented") + setupSnapshot(t, standaloneFilename, false) + + printerExpectedCalls := []func(received any){ + func(received any) { test.Equal(t, addedMsg, received.(string)) }, + func(received any) { t.Error("should not be called 2nd time") }, + } + mockT := test.NewMockTestingT(t) + mockT.MockError = func(args ...any) { + expected := "\n\x1b[38;5;52m\x1b[48;5;225m- Snapshot - 1\x1b[0m\n\x1b[38;5;22m\x1b[48;5;159m" + + "+ Received + 1\x1b[0m\n\n\x1b[48;5;225m\x1b[38;5;52m- \x1b[0m\x1b[48;5;127m\x1b[38;5;255m" + + "hello\x1b[0m\x1b[48;5;225m\x1b[38;5;52m world\x1b[0m\n\x1b[48;5;159m\x1b[38;5;22m" + + "+ \x1b[0m\x1b[48;5;23m\x1b[38;5;255mbye\x1b[0m\x1b[48;5;159m\x1b[38;5;22m world\x1b[0m\n\n\x1b[2m" + + "at " + filepath.FromSlash( + "__snapshots__/matchStandaloneSnapshot_test_mock-name_1.snap:1", + ) + "\n\x1b[0m" + + test.Equal(t, expected, args[0].(string)) + } + mockT.MockLog = func(args ...any) { + printerExpectedCalls[0](args[0]) + + // shift + printerExpectedCalls = printerExpectedCalls[1:] + } + + // First call for creating the snapshot + MatchStandaloneSnapshot(mockT, "hello world") + test.Equal(t, 1, testEvents.items[added]) + + // Resetting registry to emulate the same MatchStandaloneSnapshot call + standaloneTestsRegistry = newStandaloneRegistry() + + // Second call with different data + MatchStandaloneSnapshot(mockT, "bye world") + test.Equal(t, 1, testEvents.items[erred]) }) t.Run("should update snapshot", func(t *testing.T) { - t.Log("to be implemented") + t.Run("when 'updateVAR==true'", func(t *testing.T) { + snapPath := setupSnapshot(t, standaloneFilename, false, true) + + printerExpectedCalls := []func(received any){ + func(received any) { test.Equal(t, addedMsg, received.(string)) }, + func(received any) { test.Equal(t, updatedMsg, received.(string)) }, + func(received any) { t.Error("should not be called 3rd time") }, + } + mockT := test.NewMockTestingT(t) + mockT.MockLog = func(args ...any) { + printerExpectedCalls[0](args[0]) + + // shift + printerExpectedCalls = printerExpectedCalls[1:] + } + + // First call for creating the snapshot + MatchStandaloneSnapshot(mockT, "hello world") + test.Equal(t, 1, testEvents.items[added]) + + // Resetting registry to emulate the same MatchStandaloneSnapshot call + standaloneTestsRegistry = newStandaloneRegistry() + + // Second call with different params + MatchStandaloneSnapshot(mockT, "bye world") + + test.Equal(t, "bye world", test.GetFileContent(t, snapPath)) + test.Equal(t, 1, testEvents.items[updated]) + }) + + t.Run("when config update", func(t *testing.T) { + snapPath := setupSnapshot(t, standaloneFilename, false, false) + + printerExpectedCalls := []func(received any){ + func(received any) { test.Equal(t, addedMsg, received.(string)) }, + func(received any) { test.Equal(t, updatedMsg, received.(string)) }, + func(received any) { t.Error("should not be called 3rd time") }, + } + mockT := test.NewMockTestingT(t) + mockT.MockLog = func(args ...any) { + printerExpectedCalls[0](args[0]) + + // shift + printerExpectedCalls = printerExpectedCalls[1:] + } + + s := WithConfig(Update(true)) + // First call for creating the snapshot + s.MatchStandaloneSnapshot(mockT, "hello world") + test.Equal(t, 1, testEvents.items[added]) + + // Resetting registry to emulate the same MatchStandaloneSnapshot call + standaloneTestsRegistry = newStandaloneRegistry() + + // Second call with different params + s.MatchStandaloneSnapshot(mockT, "bye world") + + test.Equal(t, "bye world", test.GetFileContent(t, snapPath)) + test.Equal(t, 1, testEvents.items[updated]) + }) }) } From a0356e97b5faca984573ba85a341d7e2b105c19f Mon Sep 17 00:00:00 2001 From: Georgios Kampitakis Date: Sun, 21 Jul 2024 22:18:19 +0100 Subject: [PATCH 4/4] update readme.md and shorten filename --- README.md | 21 ++++++++++++++++++- ..._should_create_html_snapshots_1.snap.html} | 0 ..._should_create_html_snapshots_2.snap.html} | 0 snaps/matchStandaloneSnapshot.go | 21 ++++++++++++++++++- snaps/matchStandaloneSnapshot_test.go | 6 +++--- snaps/snapshot.go | 2 +- snaps/snapshot_test.go | 8 +++---- 7 files changed, 48 insertions(+), 10 deletions(-) rename examples/__snapshots__/{matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html => TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html} (100%) rename examples/__snapshots__/{matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html => TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html} (100%) diff --git a/README.md b/README.md index 523622b..a318cf8 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,26 @@ You can see more [examples](./examples/matchJSON_test.go#L96). ## MatchStandaloneSnapshot -TBD +`MatchStandaloneSnapshot` will create snapshots on separate files as opposed to `MatchSnapshot` which adds multiple snapshots inside the same file. + +_Combined with `snaps.Ext` you can have proper syntax highlighting and better readability_ + +```go +// test_simple.go + +func TestSimple(t *testing.T) { + snaps.MatchStandaloneSnapshot(t, "Hello World") + // or create an html snapshot file + snaps.WithConfig(snaps.Ext(".html")). + MatchStandaloneSnapshot(t, "

Hello World

") +} +``` + +`go-snaps` saves the snapshots in `__snapshots__` directory and the file +name is the test file name with extension `.snap`. + +So for example if your test is called `test_simple.go` when you run your tests, a snapshot file +will be created at `./__snapshots__/TestSimple_1.snaps`. ## Configuration diff --git a/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html b/examples/__snapshots__/TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html similarity index 100% rename from examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html rename to examples/__snapshots__/TestMatchStandaloneSnapshot_should_create_html_snapshots_1.snap.html diff --git a/examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html b/examples/__snapshots__/TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html similarity index 100% rename from examples/__snapshots__/matchStandaloneSnapshot_test_TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html rename to examples/__snapshots__/TestMatchStandaloneSnapshot_should_create_html_snapshots_2.snap.html diff --git a/snaps/matchStandaloneSnapshot.go b/snaps/matchStandaloneSnapshot.go index 060936d..02aab97 100644 --- a/snaps/matchStandaloneSnapshot.go +++ b/snaps/matchStandaloneSnapshot.go @@ -6,12 +6,32 @@ import ( "github.com/kr/pretty" ) +/* +MatchStandaloneSnapshot verifies the value matches the most recent snap file + + MatchStandaloneSnapshot(t, "Hello World") + +MatchStandaloneSnapshot creates one snapshot file per call. + +You can call MatchStandaloneSnapshot multiple times inside a test. +It will create multiple snapshot files at `__snapshots__` folder by default. +*/ func (c *config) MatchStandaloneSnapshot(t testingT, value any) { t.Helper() matchStandaloneSnapshot(c, t, value) } +/* +MatchStandaloneSnapshot verifies the value matches the most recent snap file + + MatchStandaloneSnapshot(t, "Hello World") + +MatchStandaloneSnapshot creates one snapshot file per call. + +You can call MatchStandaloneSnapshot multiple times inside a test. +It will create multiple snapshot files at `__snapshots__` folder by default. +*/ func MatchStandaloneSnapshot(t testingT, value any) { t.Helper() @@ -54,7 +74,6 @@ func matchStandaloneSnapshot(c *config, t testingT, value any) { prevSnapshot, snapshot, snapPathRel, - // TODO: what should this be? or skipped entirely 1, ) if diff == "" { diff --git a/snaps/matchStandaloneSnapshot_test.go b/snaps/matchStandaloneSnapshot_test.go index cee9ee5..5dd1c14 100644 --- a/snaps/matchStandaloneSnapshot_test.go +++ b/snaps/matchStandaloneSnapshot_test.go @@ -7,7 +7,7 @@ import ( "github.com/gkampitakis/go-snaps/internal/test" ) -const standaloneFilename = "matchStandaloneSnapshot_test_mock-name_1.snap" +const standaloneFilename = "mock-name_1.snap" func TestMatchStandaloneSnapshot(t *testing.T) { t.Run("should create snapshot", func(t *testing.T) { @@ -23,7 +23,7 @@ func TestMatchStandaloneSnapshot(t *testing.T) { registryKey := filepath.Join( filepath.Dir(snapPath), - "matchStandaloneSnapshot_test_mock-name_%d.snap", + "mock-name_%d.snap", ) test.Equal(t, 0, standaloneTestsRegistry.running[registryKey]) test.Equal(t, 1, standaloneTestsRegistry.cleanup[registryKey]) @@ -86,7 +86,7 @@ func TestMatchStandaloneSnapshot(t *testing.T) { "hello\x1b[0m\x1b[48;5;225m\x1b[38;5;52m world\x1b[0m\n\x1b[48;5;159m\x1b[38;5;22m" + "+ \x1b[0m\x1b[48;5;23m\x1b[38;5;255mbye\x1b[0m\x1b[48;5;159m\x1b[38;5;22m world\x1b[0m\n\n\x1b[2m" + "at " + filepath.FromSlash( - "__snapshots__/matchStandaloneSnapshot_test_mock-name_1.snap:1", + "__snapshots__/mock-name_1.snap:1", ) + "\n\x1b[0m" test.Equal(t, expected, args[0].(string)) diff --git a/snaps/snapshot.go b/snaps/snapshot.go index e68942c..1feeebc 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -335,7 +335,7 @@ func constructFilename(c *config, callerFilename, tName string, isStandalone boo filename = strings.TrimSuffix(base, filepath.Ext(base)) if isStandalone { - filename += fmt.Sprintf("_%s", strings.ReplaceAll(tName, "/", "_")) + filename = strings.ReplaceAll(tName, "/", "_") } } diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index 04e326c..9298ede 100644 --- a/snaps/snapshot_test.go +++ b/snaps/snapshot_test.go @@ -266,11 +266,11 @@ func TestSnapshotPath(t *testing.T) { test.HasSuffix( t, snapPath, - filepath.FromSlash("/snaps/__snapshots__/snapshot_test_my_test_%d.snap"), + filepath.FromSlash("/snaps/__snapshots__/my_test_%d.snap"), ) test.Equal( t, - filepath.FromSlash("__snapshots__/snapshot_test_my_test_%d.snap"), + filepath.FromSlash("__snapshots__/my_test_%d.snap"), snapPathRel, ) }) @@ -281,11 +281,11 @@ func TestSnapshotPath(t *testing.T) { test.HasSuffix( t, snapPath, - filepath.FromSlash("/snaps/__snapshots__/snapshot_test_TestFunction_my_test_%d.snap"), + filepath.FromSlash("/snaps/__snapshots__/TestFunction_my_test_%d.snap"), ) test.Equal( t, - filepath.FromSlash("__snapshots__/snapshot_test_TestFunction_my_test_%d.snap"), + filepath.FromSlash("__snapshots__/TestFunction_my_test_%d.snap"), snapPathRel, ) })