diff --git a/README.md b/README.md index fa24869..bc14438 100644 --- a/README.md +++ b/README.md @@ -325,6 +325,7 @@ Every subsequent test run will compare the actual value against the inline snaps - `Width`: The maximum width in characters before wrapping json output (default: 80) - `Indent`: The indentation string to use for nested structures (default: 1 spaces) - `SortKeys`: Whether to sort json object keys alphabetically (default: true) +- a label to add to the end of snapshot ids, to help when reviewing `snaps.Label("stdout")` - a custom serializer function for non-structured snapshots `snaps.Serializer(func(any) string {...})` - a helper serializer function `snaps.Raw()` that uses `fmt.Sprint` to serialize the value as is without any formatting or indentation. @@ -336,6 +337,7 @@ t.Run("snapshot tests", func(t *testing.T) { snaps.Dir("my_dir"), snaps.Filename("json_file"), snaps.Ext(".json"), + snaps.Label("JSON"), snaps.Update(false), snaps.Serializer(func(v any) string { // custom serializer logic diff --git a/snaps/clean.go b/snaps/clean.go index 3b4f5d4..3937f54 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -110,10 +110,9 @@ func Clean(m *testing.M, opts ...CleanOpts) (bool, error) { shouldClean, ) obsoleteTests, snapsDirty, err := examineSnaps( - testsRegistry.cleanup, + testsRegistry.labeled, usedFiles, runOnly, - count, shouldClean, opt.Sort, ) @@ -135,28 +134,35 @@ func Clean(m *testing.M, opts ...CleanOpts) (bool, error) { } // getTestID will return the testID if the line is in the form of [Test... - number] -func getTestID(b []byte) (string, bool) { +func getTestID(b []byte) (string, string, bool) { if len(b) == 0 { - return "", false + return "", "", false } // needs to start with [Test and end with ] if !bytes.HasPrefix(b, []byte("[Test")) || b[len(b)-1] != ']' { - return "", false + return "", "", false } - // needs to contain ' - ' - separator := bytes.Index(b, []byte(" - ")) - if separator == -1 { - return "", false + // needs to contain at least one ' - ' seperator + firstSeparator := bytes.Index(b, []byte(" - ")) + if firstSeparator == -1 { + return "", "", false } - // needs to have a number after the separator - if !isNumber(b[separator+3 : len(b)-1]) { - return "", false + // if there is a label, there will be a second seperator + secondSeparator := bytes.LastIndex(b, []byte(" - ")) + + if secondSeparator == -1 || secondSeparator == firstSeparator { + secondSeparator = len(b) - 1 } - return string(b[1 : len(b)-1]), true + // needs to have a number after the first separator + if !isNumber(b[firstSeparator+3 : secondSeparator]) { + return "", "", false + } + + return string(b[1 : len(b)-1]), string(b[1:secondSeparator]), true } func isNumber(b []byte) bool { @@ -232,10 +238,9 @@ func examineFiles( } func examineSnaps( - registry map[string]map[string]int, + testIdLabelMappings map[string]string, used []string, runOnly string, - count int, shouldUpdate, sort bool, ) ([]string, bool, error) { @@ -253,20 +258,32 @@ func examineSnaps( var needsUpdating bool - registeredTests := occurrences(registry[snapPath], count, snapshotOccurrenceFMT) s := snapshotScanner(f) for s.Scan() { b := s.Bytes() // Check if line is a test id - testID, match := getTestID(b) + oldTestIDWithLabel, oldTestIDWithoutLabel, match := getTestID(b) if !match { continue } - testIDs = append(testIDs, testID) + testIDs = append(testIDs, oldTestIDWithLabel) + + // resolve the current full test snapshot id, based on the "old" one + currentTestIdWithLabel, ok := testIdLabelMappings[oldTestIDWithoutLabel] + + // remove any test snapshots whose test no longer exists + if !ok && !testSkipped(oldTestIDWithoutLabel, runOnly) { + obsoleteTests = append(obsoleteTests, oldTestIDWithoutLabel) + needsUpdating = true + + removeSnapshot(s) + continue + } - if !registeredTests.Has(testID) && !testSkipped(testID, runOnly) { - obsoleteTests = append(obsoleteTests, testID) + // remove the old test snapshot if the label has been changed + if ok && currentTestIdWithLabel != oldTestIDWithLabel { + obsoleteTests = append(obsoleteTests, oldTestIDWithLabel) needsUpdating = true removeSnapshot(s) @@ -277,7 +294,7 @@ func examineSnaps( line := s.Bytes() if bytes.Equal(line, endSequenceByteSlice) { - tests[testID] = data.String() + tests[oldTestIDWithLabel] = data.String() data.Reset() break diff --git a/snaps/clean_test.go b/snaps/clean_test.go index d617bd9..251efc5 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -178,7 +178,7 @@ func TestExamineFiles(t *testing.T) { func TestExamineSnaps(t *testing.T) { t.Run("should report no obsolete snapshots", func(t *testing.T) { shouldUpdate, sort := false, false - tests, dir1, dir2 := setupTempExamineFiles( + _, dir1, dir2 := setupTempExamineFiles( t, loadMockSnap(t, "mock-snap-1"), loadMockSnap(t, "mock-snap-2"), @@ -188,7 +188,26 @@ func TestExamineSnaps(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps( + map[string]string{ + "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1", + "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1", + "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1", + "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2", + "TestCat - 1": "TestCat - 1", + "TestAlpha - 2": "TestAlpha - 2", + "TestBeta - 1": "TestBeta - 1", + "TestAlpha - 1": "TestAlpha - 1", + }, + used, + "", + shouldUpdate, + sort, + ) test.Equal(t, []string{}, obsolete) test.NoError(t, err) @@ -199,18 +218,32 @@ func TestExamineSnaps(t *testing.T) { shouldUpdate, sort := false, false mockSnap1 := loadMockSnap(t, "mock-snap-1") mockSnap2 := loadMockSnap(t, "mock-snap-2") - tests, dir1, dir2 := setupTempExamineFiles(t, mockSnap1, mockSnap2) + _, dir1, dir2 := setupTempExamineFiles(t, mockSnap1, mockSnap2) used := []string{ filepath.FromSlash(dir1 + "/test1.snap"), filepath.FromSlash(dir2 + "/test2.snap"), } - // Reducing test occurrence to 1 meaning the second test was removed ( testid - 2 ) - tests[used[0]]["TestDir1_3/TestSimple"] = 1 - // Removing the test entirely - delete(tests[used[1]], "TestDir2_2/TestSimple") - - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps( + map[string]string{ + "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1", + // "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1", + // "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1", + "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2", + "TestCat - 1": "TestCat - 1", + "TestAlpha - 2": "TestAlpha - 2", + "TestBeta - 1": "TestBeta - 1", + "TestAlpha - 1": "TestAlpha - 1", + }, + used, + "", + shouldUpdate, + sort, + ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -227,7 +260,7 @@ func TestExamineSnaps(t *testing.T) { t.Run("should update the obsolete snap files", func(t *testing.T) { shouldUpdate, sort := true, false - tests, dir1, dir2 := setupTempExamineFiles( + _, dir1, dir2 := setupTempExamineFiles( t, loadMockSnap(t, "mock-snap-1"), loadMockSnap(t, "mock-snap-2"), @@ -237,11 +270,26 @@ func TestExamineSnaps(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - // removing tests from the map means those tests are no longer used - delete(tests[used[0]], "TestDir1_3/TestSimple") - delete(tests[used[1]], "TestDir2_1/TestSimple") - - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps( + map[string]string{ + // "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1", + // "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1", + "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + // "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1", + // "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + // "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2", + "TestCat - 1": "TestCat - 1", + "TestAlpha - 2": "TestAlpha - 2", + "TestBeta - 1": "TestBeta - 1", + "TestAlpha - 1": "TestAlpha - 1", + }, + used, + "", + shouldUpdate, + sort, + ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -292,7 +340,7 @@ string hello world 2 2 1 mockSnap2 := loadMockSnap(t, "mock-snap-sort-2") expectedMockSnap1 := loadMockSnap(t, "mock-snap-sort-1-sorted") expectedMockSnap2 := loadMockSnap(t, "mock-snap-sort-2-sorted") - tests, dir1, dir2 := setupTempExamineFiles( + _, dir1, dir2 := setupTempExamineFiles( t, mockSnap1, mockSnap2, @@ -302,7 +350,26 @@ string hello world 2 2 1 filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps( + map[string]string{ + "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1", + "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1", + "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1", + "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2", + "TestCat - 1": "TestCat - 1", + "TestAlpha - 2": "TestAlpha - 2", + "TestBeta - 1": "TestBeta - 1", + "TestAlpha - 1": "TestAlpha - 1", + }, + used, + "", + shouldUpdate, + sort, + ) test.NoError(t, err) test.Equal(t, 0, len(obsolete)) @@ -324,7 +391,7 @@ string hello world 2 2 1 shouldUpdate, sort := false, true mockSnap1 := loadMockSnap(t, "mock-snap-sort-1-sorted") mockSnap2 := loadMockSnap(t, "mock-snap-sort-2-sorted") - tests, dir1, dir2 := setupTempExamineFiles( + _, dir1, dir2 := setupTempExamineFiles( t, mockSnap1, mockSnap2, @@ -334,11 +401,26 @@ string hello world 2 2 1 filepath.FromSlash(dir2 + "/test2.snap"), } - // removing tests from the map means those tests are no longer used - delete(tests[used[0]], "TestDir1_3/TestSimple") - delete(tests[used[1]], "TestDir2_1/TestSimple") - - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps( + map[string]string{ + // "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1", + // "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1", + "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + // "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1", + // "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + // "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2", + "TestCat - 1": "TestCat - 1", + "TestAlpha - 2": "TestAlpha - 2", + "TestBeta - 1": "TestBeta - 1", + "TestAlpha - 1": "TestAlpha - 1", + }, + used, + "", + shouldUpdate, + sort, + ) test.NoError(t, err) test.Equal(t, []string{ @@ -364,6 +446,178 @@ string hello world 2 2 1 ) } +func TestExamineSnaps_WithLabels(t *testing.T) { + t.Run("should report no obsolete snapshots", func(t *testing.T) { + shouldUpdate, sort := false, false + _, dir1, dir2 := setupTempExamineFiles( + t, + loadMockSnap(t, "mock-snap-1-labeled"), + loadMockSnap(t, "mock-snap-2-labeled"), + ) + used := []string{ + filepath.FromSlash(dir1 + "/test1.snap"), + filepath.FromSlash(dir2 + "/test2.snap"), + } + + obsolete, isDirty, err := examineSnaps( + map[string]string{ + "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1 - my snapshot", + "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1 - this is another snapshot", + + "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1 - stdout", + "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2 - stderr", + }, + used, + "", + shouldUpdate, + sort, + ) + + test.Equal(t, []string{}, obsolete) + test.NoError(t, err) + test.False(t, isDirty) + }) + + t.Run("should report two obsolete snapshots and not change content", func(t *testing.T) { + shouldUpdate, sort := false, false + mockSnap1 := loadMockSnap(t, "mock-snap-1-labeled-renamed") + mockSnap2 := loadMockSnap(t, "mock-snap-2-labeled") + _, dir1, dir2 := setupTempExamineFiles(t, mockSnap1, mockSnap2) + used := []string{ + filepath.FromSlash(dir1 + "/test1.snap"), + filepath.FromSlash(dir2 + "/test2.snap"), + } + + obsolete, isDirty, err := examineSnaps( + map[string]string{ + "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1 - my snapshot", + // "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1 - this is another snapshot", + + // "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1 - stdout", + "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2 - stderr", + }, + used, + "", + shouldUpdate, + sort, + ) + content1 := test.GetFileContent(t, used[0]) + content2 := test.GetFileContent(t, used[1]) + + test.Equal( + t, + []string{ + "TestDir1_2/TestSimple - 1", + "TestDir1_3/TestSimple - 2", + "TestDir2_2/TestSimple - 1", + }, + obsolete, + ) + test.NoError(t, err) + + // Content of snaps is not changed + test.Equal(t, mockSnap1, []byte(content1)) + test.Equal(t, mockSnap2, []byte(content2)) + + // And thus we are dirty since the contents do need changing + test.True(t, isDirty) + }) + + t.Run("should update the obsolete snap files", func(t *testing.T) { + shouldUpdate, sort := true, false + _, dir1, dir2 := setupTempExamineFiles( + t, + loadMockSnap(t, "mock-snap-1-labeled-renamed"), + loadMockSnap(t, "mock-snap-2-labeled"), + ) + used := []string{ + filepath.FromSlash(dir1 + "/test1.snap"), + filepath.FromSlash(dir2 + "/test2.snap"), + } + + obsolete, isDirty, err := examineSnaps( + map[string]string{ + // "TestDir1_3/TestSimple - 1": "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1": "TestDir1_2/TestSimple - 1 - my snapshot", + // "TestDir1_3/TestSimple - 2": "TestDir1_3/TestSimple - 2", + "TestDir1_1/TestSimple - 1": "TestDir1_1/TestSimple - 1 - this is another snapshot", + + "TestDir2_2/TestSimple - 1": "TestDir2_2/TestSimple - 1", + "TestDir2_1/TestSimple - 1": "TestDir2_1/TestSimple - 1 - stdout", + "TestDir2_1/TestSimple - 3": "TestDir2_1/TestSimple - 3", + "TestDir2_1/TestSimple - 2": "TestDir2_1/TestSimple - 2 - stderr", + }, + used, + "", + shouldUpdate, + sort, + ) + content1 := test.GetFileContent(t, used[0]) + content2 := test.GetFileContent(t, used[1]) + + // !!unsorted + expected1 := ` +[TestDir1_2/TestSimple - 1 - my snapshot] +int(10) +string hello world 1 2 1 +--- + +[TestDir1_1/TestSimple - 1 - this is another snapshot] + +int(1) + +string hello world 1 1 1 + +--- +` + expected2 := ` +[TestDir2_2/TestSimple - 1] +int(1000) +string hello world 2 2 1 +--- + +[TestDir2_1/TestSimple - 1 - stdout] +int(1) +string hello world 2 1 1 +--- + +[TestDir2_1/TestSimple - 3] +int(100) +string hello world 2 1 3 +--- + +[TestDir2_1/TestSimple - 2 - stderr] +int(10) +string hello world 2 1 2 +--- +` + + test.Equal(t, []string{ + "TestDir1_3/TestSimple - 1", + "TestDir1_2/TestSimple - 1", + "TestDir1_3/TestSimple - 2", + }, + obsolete, + ) + test.NoError(t, err) + + // Content of snaps have been updated + test.Equal(t, expected1, content1) + test.Equal(t, expected2, content2) + + // And thus we are not dirty + test.False(t, isDirty) + }) +} + func TestOccurrences(t *testing.T) { t.Run("when count 1", func(t *testing.T) { tests := map[string]int{ @@ -505,12 +759,31 @@ func TestGetTestID(t *testing.T) { valid bool }{ {"[Test/something - 10]", "Test/something - 10", true}, + {"[Test/something - 10 - my label]", "Test/something - 10 - my label", true}, {input: "[Test/something - 100231231dsada]", expectedID: "", valid: false}, {input: "[Test/something - 100231231 ]", expectedID: "", valid: false}, {input: "[Test/something -100231231 ]", expectedID: "", valid: false}, {input: "[Test/something- 100231231]", expectedID: "", valid: false}, {input: "[Test/something - a ]", expectedID: "", valid: false}, + {input: "[Test/something - 100231231dsada - my label]", expectedID: "", valid: false}, + // todo: decide if this should actually be considered valid + // (if not, we should probably always string.Trim labels) + { + input: "[Test/something - 100231231 - my label ]", + expectedID: "Test/something - 100231231 - my label ", + valid: true, + }, + {input: "[Test/something -100231231 - my label ]", expectedID: "", valid: false}, + {input: "[Test/something - 100231231 -my label]", expectedID: "", valid: false}, + {input: "[Test/something - 100231231-my label]", expectedID: "", valid: false}, + {input: "[Test/something - 100231231- my label]", expectedID: "", valid: false}, + {input: "[Test/something- 100231231 - my label]", expectedID: "", valid: false}, + {input: "[Test/something - a ]", expectedID: "", valid: false}, + {input: "[Test/something - a]", expectedID: "", valid: false}, + {input: "[Test/something - a - my label]", expectedID: "", valid: false}, + {input: "[Test/something - a - my label ]", expectedID: "", valid: false}, {"[Test123 - Some Test]", "", false}, + {"[Test123 - 1 - Some Test]", "Test123 - 1 - Some Test", true}, {"", "", false}, {"Invalid input", "", false}, {"[Test - Missing Closing Bracket", "", false}, @@ -535,7 +808,7 @@ func TestGetTestID(t *testing.T) { // indexing beyond the capacity will cause test to panic b := make([]byte, 0, len(tc.input)) b = append(b, []byte(tc.input)...) - id, ok := getTestID(b) + id, _, ok := getTestID(b) test.Equal(t, tc.valid, ok) test.Equal(t, tc.expectedID, id) @@ -547,11 +820,15 @@ func TestNaturalSort(t *testing.T) { t.Run("should sort in descending order", func(t *testing.T) { items := []string{ "[TestExample/Test_Case_1#74 - 1]", + "[TestExample/Test_Case_2#01 - 1 - b]", "[TestExample/Test_Case_1#05 - 1]", "[TestExample/Test_Case_1#09 - 1]", + "[TestExample/Test_Case_2#01 - 2 - a]", + "[TestExample/Test_Case_1#74 - 1 - my label]", "[TestExample - 1]", "[TestExample/Test_Case_1#71 - 1]", - "[TestExample/Test_Case_1#100 - 1]", + "[TestExample/Test_Case_2#01 - 3 - c]", + "[TestExample/Test_Case_1#100 - 1 - another label]", "[TestExample/Test_Case_1#7 - 1]", } expected := []string{ @@ -560,8 +837,12 @@ func TestNaturalSort(t *testing.T) { "[TestExample/Test_Case_1#7 - 1]", "[TestExample/Test_Case_1#09 - 1]", "[TestExample/Test_Case_1#71 - 1]", + "[TestExample/Test_Case_1#74 - 1 - my label]", "[TestExample/Test_Case_1#74 - 1]", - "[TestExample/Test_Case_1#100 - 1]", + "[TestExample/Test_Case_1#100 - 1 - another label]", + "[TestExample/Test_Case_2#01 - 1 - b]", + "[TestExample/Test_Case_2#01 - 2 - a]", + "[TestExample/Test_Case_2#01 - 3 - c]", } slices.SortFunc(items, naturalSort) diff --git a/snaps/config.go b/snaps/config.go index 5cb2058..e95cc2f 100644 --- a/snaps/config.go +++ b/snaps/config.go @@ -14,6 +14,7 @@ type Config struct { filename string snapsDir string extension string + label string update *bool json *JSONConfig serializer func(any) string @@ -105,6 +106,15 @@ func Ext(ext string) func(*Config) { } } +// Specify snapshot label, to make it easier to find and review +// +// default: empty +func Label(label string) func(*Config) { + return func(c *Config) { + c.label = label + } +} + // Specify json format configuration // // default: see defaultPrettyJSONOptions for default json config diff --git a/snaps/config_test.go b/snaps/config_test.go index fd697df..1449ad0 100644 --- a/snaps/config_test.go +++ b/snaps/config_test.go @@ -14,6 +14,7 @@ func TestWithConfig(t *testing.T) { test.Equal(t, "__snapshots__", c.snapsDir) test.Equal(t, "", c.filename) test.Equal(t, "", c.extension) + test.Equal(t, "", c.label) test.Nil(t, c.update) test.Nil(t, c.json) test.Nil(t, c.serializer) @@ -34,6 +35,11 @@ func TestWithConfig(t *testing.T) { test.Equal(t, ".txt", c.extension) }) + t.Run("Label", func(t *testing.T) { + c := WithConfig(Label("my_label")) + test.Equal(t, "my_label", c.label) + }) + t.Run("Update", func(t *testing.T) { c := WithConfig(Update(true)) test.Equal(t, true, *c.update) diff --git a/snaps/matchJSON.go b/snaps/matchJSON.go index d62f358..6952d6a 100644 --- a/snaps/matchJSON.go +++ b/snaps/matchJSON.go @@ -64,7 +64,7 @@ func matchJSON(c *Config, t testingT, input any, matchers ...match.JSONMatcher) t.Helper() snapPath, snapPathRel := snapshotPath(c, t.Name(), false) - testID := testsRegistry.getTestID(snapPath, t.Name()) + testID := testsRegistry.getTestID(snapPath, t.Name(), c.label) t.Cleanup(func() { testsRegistry.reset(snapPath, t.Name()) }) diff --git a/snaps/matchSnapshot.go b/snaps/matchSnapshot.go index 8c7ddac..3135808 100644 --- a/snaps/matchSnapshot.go +++ b/snaps/matchSnapshot.go @@ -55,7 +55,7 @@ func matchSnapshot(c *Config, t testingT, values ...any) { } snapPath, snapPathRel := snapshotPath(c, t.Name(), false) - testID := testsRegistry.getTestID(snapPath, t.Name()) + testID := testsRegistry.getTestID(snapPath, t.Name(), c.label) t.Cleanup(func() { testsRegistry.reset(snapPath, t.Name()) }) diff --git a/snaps/matchYAML.go b/snaps/matchYAML.go index 697312a..1c7b0b1 100644 --- a/snaps/matchYAML.go +++ b/snaps/matchYAML.go @@ -59,7 +59,7 @@ func matchYAML(c *Config, t testingT, input any, matchers ...match.YAMLMatcher) t.Helper() snapPath, snapPathRel := snapshotPath(c, t.Name(), false) - testID := testsRegistry.getTestID(snapPath, t.Name()) + testID := testsRegistry.getTestID(snapPath, t.Name(), c.label) t.Cleanup(func() { testsRegistry.reset(snapPath, t.Name()) }) diff --git a/snaps/snapshot.go b/snaps/snapshot.go index 235e598..3c9fcda 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -37,12 +37,15 @@ func handleError(t testingT, err any) { type syncRegistry struct { running map[string]map[string]int cleanup map[string]map[string]int + + labeled map[string]string + sync.Mutex } // Returns the id of the test in the snapshot // Form [ - ] -func (s *syncRegistry) getTestID(snapPath, testName string) string { +func (s *syncRegistry) getTestID(snapPath, testName, label string) string { s.Lock() if _, exists := s.running[snapPath]; !exists { @@ -53,9 +56,19 @@ func (s *syncRegistry) getTestID(snapPath, testName string) string { s.running[snapPath][testName]++ s.cleanup[snapPath][testName]++ c := s.running[snapPath][testName] + + testIdWithoutLabel := fmt.Sprintf("%s - %d", testName, c) + + if label != "" { + label = " - " + label + } + + testIdWithLabel := testIdWithoutLabel + label + s.labeled[testIdWithoutLabel] = testIdWithLabel + s.Unlock() - return fmt.Sprintf("[%s - %d]", testName, c) + return "[" + testIdWithLabel + "]" } // reset sets only the number of running registry for the given test to 0. @@ -69,6 +82,7 @@ func newRegistry() *syncRegistry { return &syncRegistry{ running: make(map[string]map[string]int), cleanup: make(map[string]map[string]int), + labeled: make(map[string]string), Mutex: sync.Mutex{}, } } diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index 8919bc7..70e814c 100644 --- a/snaps/snapshot_test.go +++ b/snaps/snapshot_test.go @@ -17,15 +17,28 @@ func TestSyncRegistry(t *testing.T) { wg.Add(1) go func() { - registry.getTestID("/file", "test") + label := "" + + if i == 3 { + label = "my snap" + } + + registry.getTestID("/file", "test", label) wg.Done() }() } + registry.getTestID("/file", "test-v3", "") + wg.Wait() - test.Equal(t, "[test - 6]", registry.getTestID("/file", "test")) - test.Equal(t, "[test-v2 - 1]", registry.getTestID("/file", "test-v2")) + test.Equal(t, "[test - 6]", registry.getTestID("/file", "test", "")) + test.Equal(t, "[test-v2 - 1]", registry.getTestID("/file", "test-v2", "")) + test.Equal( + t, + "[test-v3 - 2 - labelled]", + registry.getTestID("/file", "test-v3", "labelled"), + ) test.Equal(t, registry.cleanup, registry.running) }) @@ -37,7 +50,7 @@ func TestSyncRegistry(t *testing.T) { wg.Add(1) go func() { - registry.getTestID("/file", "test") + registry.getTestID("/file", "test", "") wg.Done() }() } @@ -47,7 +60,7 @@ func TestSyncRegistry(t *testing.T) { registry.reset("/file", "test") // running registry start from 0 again - test.Equal(t, "[test - 1]", registry.getTestID("/file", "test")) + test.Equal(t, "[test - 1]", registry.getTestID("/file", "test", "")) // cleanup registry still has 101 test.Equal(t, 101, registry.cleanup["/file"]["test"]) }) diff --git a/snaps/testdata/mock-snap-1-labeled b/snaps/testdata/mock-snap-1-labeled new file mode 100644 index 0000000..ceb2173 --- /dev/null +++ b/snaps/testdata/mock-snap-1-labeled @@ -0,0 +1,22 @@ +[TestDir1_3/TestSimple - 1] +int(100) +string hello world 1 3 1 +--- + +[TestDir1_2/TestSimple - 1 - my snapshot] +int(10) +string hello world 1 2 1 +--- + +[TestDir1_3/TestSimple - 2] +int(1000) +string hello world 1 3 2 +--- + +[TestDir1_1/TestSimple - 1 - this is another snapshot] + +int(1) + +string hello world 1 1 1 + +--- diff --git a/snaps/testdata/mock-snap-1-labeled-renamed b/snaps/testdata/mock-snap-1-labeled-renamed new file mode 100644 index 0000000..708c015 --- /dev/null +++ b/snaps/testdata/mock-snap-1-labeled-renamed @@ -0,0 +1,27 @@ +[TestDir1_3/TestSimple - 1] +int(100) +string hello world 1 3 1 +--- + +[TestDir1_2/TestSimple - 1] +int(10) +string hello world 1 2 1 +--- + +[TestDir1_2/TestSimple - 1 - my snapshot] +int(10) +string hello world 1 2 1 +--- + +[TestDir1_3/TestSimple - 2] +int(1000) +string hello world 1 3 2 +--- + +[TestDir1_1/TestSimple - 1 - this is another snapshot] + +int(1) + +string hello world 1 1 1 + +--- diff --git a/snaps/testdata/mock-snap-2-labeled b/snaps/testdata/mock-snap-2-labeled new file mode 100644 index 0000000..062f6d4 --- /dev/null +++ b/snaps/testdata/mock-snap-2-labeled @@ -0,0 +1,20 @@ + +[TestDir2_2/TestSimple - 1] +int(1000) +string hello world 2 2 1 +--- + +[TestDir2_1/TestSimple - 1 - stdout] +int(1) +string hello world 2 1 1 +--- + +[TestDir2_1/TestSimple - 3] +int(100) +string hello world 2 1 3 +--- + +[TestDir2_1/TestSimple - 2 - stderr] +int(10) +string hello world 2 1 2 +---