From 3a875b9edf35fcae4f10a612b29556d1f01efbfe Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:45:42 +1300 Subject: [PATCH 01/11] feat: support labeling snapshots --- snaps/clean.go | 17 ++++++++++++----- snaps/clean_test.go | 15 +++++++++++++++ snaps/config.go | 10 ++++++++++ snaps/config_test.go | 6 ++++++ snaps/matchJSON.go | 2 +- snaps/matchSnapshot.go | 2 +- snaps/matchYAML.go | 2 +- snaps/snapshot.go | 8 ++++++-- snaps/snapshot_test.go | 10 +++++----- 9 files changed, 57 insertions(+), 15 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index 3b4f5d4..7dda383 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -145,14 +145,21 @@ func getTestID(b []byte) (string, bool) { return "", false } - // needs to contain ' - ' - separator := bytes.Index(b, []byte(" - ")) - if separator == -1 { + // 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]) { + // 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 + } + + // needs to have a number after the first separator + if !isNumber(b[firstSeparator+3 : secondSeparator]) { return "", false } diff --git a/snaps/clean_test.go b/snaps/clean_test.go index d617bd9..36a0a0d 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -505,12 +505,27 @@ 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}, 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..d319cf7 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -42,7 +42,7 @@ type syncRegistry struct { // 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 { @@ -55,7 +55,11 @@ func (s *syncRegistry) getTestID(snapPath, testName string) string { c := s.running[snapPath][testName] s.Unlock() - return fmt.Sprintf("[%s - %d]", testName, c) + if label != "" { + label = " - " + label + } + + return fmt.Sprintf("[%s - %d%s]", testName, c, label) } // reset sets only the number of running registry for the given test to 0. diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index 8919bc7..24269c6 100644 --- a/snaps/snapshot_test.go +++ b/snaps/snapshot_test.go @@ -17,15 +17,15 @@ func TestSyncRegistry(t *testing.T) { wg.Add(1) go func() { - registry.getTestID("/file", "test") + registry.getTestID("/file", "test", "") wg.Done() }() } 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, registry.cleanup, registry.running) }) @@ -37,7 +37,7 @@ func TestSyncRegistry(t *testing.T) { wg.Add(1) go func() { - registry.getTestID("/file", "test") + registry.getTestID("/file", "test", "") wg.Done() }() } @@ -47,7 +47,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"]) }) From a85509b90916efa2410cf889f8bc9c0f36f6cf27 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:19:44 +1300 Subject: [PATCH 02/11] fix: improve clean handling --- snaps/clean.go | 22 +++++++++++----------- snaps/clean_test.go | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index 7dda383..c668eea 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -135,20 +135,20 @@ 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 at least one ' - ' seperator firstSeparator := bytes.Index(b, []byte(" - ")) if firstSeparator == -1 { - return "", false + return "", "", false } // if there is a label, there will be a second seperator @@ -160,10 +160,10 @@ func getTestID(b []byte) (string, bool) { // needs to have a number after the first separator if !isNumber(b[firstSeparator+3 : secondSeparator]) { - return "", false + return "", "", false } - return string(b[1 : len(b)-1]), true + return string(b[1 : len(b)-1]), string(b[1:secondSeparator]), true } func isNumber(b []byte) bool { @@ -266,14 +266,14 @@ func examineSnaps( for s.Scan() { b := s.Bytes() // Check if line is a test id - testID, match := getTestID(b) + testIDWithLabel, testIDWithoutLabel, match := getTestID(b) if !match { continue } - testIDs = append(testIDs, testID) + testIDs = append(testIDs, testIDWithLabel) - if !registeredTests.Has(testID) && !testSkipped(testID, runOnly) { - obsoleteTests = append(obsoleteTests, testID) + if !registeredTests.Has(testIDWithoutLabel) && !testSkipped(testIDWithoutLabel, runOnly) { + obsoleteTests = append(obsoleteTests, testIDWithoutLabel) needsUpdating = true removeSnapshot(s) @@ -284,7 +284,7 @@ func examineSnaps( line := s.Bytes() if bytes.Equal(line, endSequenceByteSlice) { - tests[testID] = data.String() + tests[testIDWithLabel] = data.String() data.Reset() break diff --git a/snaps/clean_test.go b/snaps/clean_test.go index 36a0a0d..c74c20a 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -550,7 +550,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) From f2bd9ac89863f084c5ec693e3bd20449eb1566c4 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:00:58 +1300 Subject: [PATCH 03/11] test: update "natural sort" case to include some labeled snapshots --- snaps/clean_test.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/snaps/clean_test.go b/snaps/clean_test.go index c74c20a..96098a3 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -562,11 +562,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{ @@ -575,8 +579,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) From f7400d044a4cf521bd58183cbc357abf7d83a887 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:31:39 +1300 Subject: [PATCH 04/11] fix: track test ids with and without labels for cleaning --- snaps/clean.go | 17 +++ snaps/clean_test.go | 167 ++++++++++++++++++++- snaps/snapshot.go | 14 +- snaps/testdata/mock-snap-1-labeled | 22 +++ snaps/testdata/mock-snap-1-labeled-renamed | 27 ++++ snaps/testdata/mock-snap-2-labeled | 20 +++ 6 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 snaps/testdata/mock-snap-1-labeled create mode 100644 snaps/testdata/mock-snap-1-labeled-renamed create mode 100644 snaps/testdata/mock-snap-2-labeled diff --git a/snaps/clean.go b/snaps/clean.go index c668eea..f1e65be 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -116,6 +116,7 @@ func Clean(m *testing.M, opts ...CleanOpts) (bool, error) { count, shouldClean, opt.Sort, + testsRegistry.testIds, ) if err != nil { return snapsDirty || filesDirty, err @@ -245,6 +246,7 @@ func examineSnaps( count int, shouldUpdate, sort bool, + testIdsMapping map[string]string, ) ([]string, bool, error) { obsoleteTests := []string{} tests := map[string]string{} @@ -272,6 +274,21 @@ func examineSnaps( } testIDs = append(testIDs, testIDWithLabel) + currentTestIdWithLabel, ok := testIdsMapping[testIDWithoutLabel] + + if !ok { + fmt.Printf("missing %s\n", testIDWithoutLabel) + } + + if currentTestIdWithLabel != testIDWithLabel { + fmt.Printf("%s != %s\n", currentTestIdWithLabel, testIDWithLabel) + obsoleteTests = append(obsoleteTests, testIDWithLabel) + needsUpdating = true + + removeSnapshot(s) + continue + } + if !registeredTests.Has(testIDWithoutLabel) && !testSkipped(testIDWithoutLabel, runOnly) { obsoleteTests = append(obsoleteTests, testIDWithoutLabel) needsUpdating = true diff --git a/snaps/clean_test.go b/snaps/clean_test.go index 96098a3..b0b7985 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -176,6 +176,22 @@ func TestExamineFiles(t *testing.T) { } func TestExamineSnaps(t *testing.T) { + // something // + testIdsMapping := 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", + } + t.Run("should report no obsolete snapshots", func(t *testing.T) { shouldUpdate, sort := false, false tests, dir1, dir2 := setupTempExamineFiles( @@ -188,7 +204,7 @@ func TestExamineSnaps(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) test.Equal(t, []string{}, obsolete) test.NoError(t, err) @@ -210,7 +226,7 @@ func TestExamineSnaps(t *testing.T) { // Removing the test entirely delete(tests[used[1]], "TestDir2_2/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -241,7 +257,7 @@ func TestExamineSnaps(t *testing.T) { 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(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -302,7 +318,7 @@ string hello world 2 2 1 filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) test.NoError(t, err) test.Equal(t, 0, len(obsolete)) @@ -338,7 +354,7 @@ string hello world 2 2 1 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(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) test.NoError(t, err) test.Equal(t, []string{ @@ -364,6 +380,147 @@ string hello world 2 2 1 ) } +func TestExamineSnaps_WithLabels(t *testing.T) { + // label added + // label changed + // label removed + + // something // + testIdsMapping := 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", + } + + t.Run("should report no obsolete snapshots", func(t *testing.T) { + shouldUpdate, sort := false, false + tests, 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(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + + 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") + tests, 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, testIdsMapping) + 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 + tests, 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"), + } + + // removing tests from the map means those tests are no longer used + delete(tests[used[0]], "TestDir1_3/TestSimple") + + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + 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{ diff --git a/snaps/snapshot.go b/snaps/snapshot.go index d319cf7..dedadfb 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -37,6 +37,9 @@ func handleError(t testingT, err any) { type syncRegistry struct { running map[string]map[string]int cleanup map[string]map[string]int + + testIds map[string]string + sync.Mutex } @@ -53,13 +56,19 @@ func (s *syncRegistry) getTestID(snapPath, testName, label string) string { s.running[snapPath][testName]++ s.cleanup[snapPath][testName]++ c := s.running[snapPath][testName] - s.Unlock() + + testIdWithoutLabel := fmt.Sprintf("%s - %d", testName, c) if label != "" { label = " - " + label } - return fmt.Sprintf("[%s - %d%s]", testName, c, label) + testIdWithLabel := testIdWithoutLabel + label + s.testIds[testIdWithoutLabel] = testIdWithLabel + + s.Unlock() + + return "[" + testIdWithLabel + "]" } // reset sets only the number of running registry for the given test to 0. @@ -73,6 +82,7 @@ func newRegistry() *syncRegistry { return &syncRegistry{ running: make(map[string]map[string]int), cleanup: make(map[string]map[string]int), + testIds: make(map[string]string), Mutex: sync.Mutex{}, } } 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 +--- From de2da31ae19095f8edfd9c12cf92d37534e8dff7 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:02:39 +1300 Subject: [PATCH 05/11] refactor: rename and remove debugging code --- snaps/clean.go | 11 +++-------- snaps/clean_test.go | 26 ++++++++++---------------- snaps/snapshot.go | 6 +++--- 3 files changed, 16 insertions(+), 27 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index f1e65be..5061104 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -116,7 +116,7 @@ func Clean(m *testing.M, opts ...CleanOpts) (bool, error) { count, shouldClean, opt.Sort, - testsRegistry.testIds, + testsRegistry.labeled, ) if err != nil { return snapsDirty || filesDirty, err @@ -246,7 +246,7 @@ func examineSnaps( count int, shouldUpdate, sort bool, - testIdsMapping map[string]string, + testIdLabelMappings map[string]string, ) ([]string, bool, error) { obsoleteTests := []string{} tests := map[string]string{} @@ -274,14 +274,9 @@ func examineSnaps( } testIDs = append(testIDs, testIDWithLabel) - currentTestIdWithLabel, ok := testIdsMapping[testIDWithoutLabel] - - if !ok { - fmt.Printf("missing %s\n", testIDWithoutLabel) - } + currentTestIdWithLabel, _ := testIdLabelMappings[testIDWithoutLabel] if currentTestIdWithLabel != testIDWithLabel { - fmt.Printf("%s != %s\n", currentTestIdWithLabel, testIDWithLabel) obsoleteTests = append(obsoleteTests, testIDWithLabel) needsUpdating = true diff --git a/snaps/clean_test.go b/snaps/clean_test.go index b0b7985..4d87bc6 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -176,8 +176,7 @@ func TestExamineFiles(t *testing.T) { } func TestExamineSnaps(t *testing.T) { - // something // - testIdsMapping := map[string]string{ + testIdLabelMappings := 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", @@ -204,7 +203,7 @@ func TestExamineSnaps(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) test.Equal(t, []string{}, obsolete) test.NoError(t, err) @@ -226,7 +225,7 @@ func TestExamineSnaps(t *testing.T) { // Removing the test entirely delete(tests[used[1]], "TestDir2_2/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -257,7 +256,7 @@ func TestExamineSnaps(t *testing.T) { delete(tests[used[0]], "TestDir1_3/TestSimple") delete(tests[used[1]], "TestDir2_1/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -318,7 +317,7 @@ string hello world 2 2 1 filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) test.NoError(t, err) test.Equal(t, 0, len(obsolete)) @@ -354,7 +353,7 @@ string hello world 2 2 1 delete(tests[used[0]], "TestDir1_3/TestSimple") delete(tests[used[1]], "TestDir2_1/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) test.NoError(t, err) test.Equal(t, []string{ @@ -381,12 +380,7 @@ string hello world 2 2 1 } func TestExamineSnaps_WithLabels(t *testing.T) { - // label added - // label changed - // label removed - - // something // - testIdsMapping := map[string]string{ + testIdLabelMappings := 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", @@ -410,7 +404,7 @@ func TestExamineSnaps_WithLabels(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) test.Equal(t, []string{}, obsolete) test.NoError(t, err) @@ -432,7 +426,7 @@ func TestExamineSnaps_WithLabels(t *testing.T) { // Removing the test entirely delete(tests[used[1]], "TestDir2_2/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -462,7 +456,7 @@ func TestExamineSnaps_WithLabels(t *testing.T) { // removing tests from the map means those tests are no longer used delete(tests[used[0]], "TestDir1_3/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdsMapping) + obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) diff --git a/snaps/snapshot.go b/snaps/snapshot.go index dedadfb..3c9fcda 100644 --- a/snaps/snapshot.go +++ b/snaps/snapshot.go @@ -38,7 +38,7 @@ type syncRegistry struct { running map[string]map[string]int cleanup map[string]map[string]int - testIds map[string]string + labeled map[string]string sync.Mutex } @@ -64,7 +64,7 @@ func (s *syncRegistry) getTestID(snapPath, testName, label string) string { } testIdWithLabel := testIdWithoutLabel + label - s.testIds[testIdWithoutLabel] = testIdWithLabel + s.labeled[testIdWithoutLabel] = testIdWithLabel s.Unlock() @@ -82,7 +82,7 @@ func newRegistry() *syncRegistry { return &syncRegistry{ running: make(map[string]map[string]int), cleanup: make(map[string]map[string]int), - testIds: make(map[string]string), + labeled: make(map[string]string), Mutex: sync.Mutex{}, } } From a62feef484cb64f5fff5aa118f4320545e394ddd Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:07:31 +1300 Subject: [PATCH 06/11] fix: ignore snapshots whose labels are missing --- snaps/clean.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index 5061104..169f86d 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -274,9 +274,9 @@ func examineSnaps( } testIDs = append(testIDs, testIDWithLabel) - currentTestIdWithLabel, _ := testIdLabelMappings[testIDWithoutLabel] + currentTestIdWithLabel, ok := testIdLabelMappings[testIDWithoutLabel] - if currentTestIdWithLabel != testIDWithLabel { + if ok && currentTestIdWithLabel != testIDWithLabel { obsoleteTests = append(obsoleteTests, testIDWithLabel) needsUpdating = true From 7c51ee3240c8e181710350f18bd13e5201a33c1e Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Fri, 20 Feb 2026 15:40:00 +1300 Subject: [PATCH 07/11] refactor: reformat --- snaps/clean.go | 3 +- snaps/clean_test.go | 96 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 88 insertions(+), 11 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index 169f86d..30b2598 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -284,7 +284,8 @@ func examineSnaps( continue } - if !registeredTests.Has(testIDWithoutLabel) && !testSkipped(testIDWithoutLabel, runOnly) { + if !registeredTests.Has(testIDWithoutLabel) && + !testSkipped(testIDWithoutLabel, runOnly) { obsoleteTests = append(obsoleteTests, testIDWithoutLabel) needsUpdating = true diff --git a/snaps/clean_test.go b/snaps/clean_test.go index 4d87bc6..cbe716a 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -203,7 +203,15 @@ func TestExamineSnaps(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) test.Equal(t, []string{}, obsolete) test.NoError(t, err) @@ -225,7 +233,15 @@ func TestExamineSnaps(t *testing.T) { // Removing the test entirely delete(tests[used[1]], "TestDir2_2/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -256,7 +272,15 @@ func TestExamineSnaps(t *testing.T) { delete(tests[used[0]], "TestDir1_3/TestSimple") delete(tests[used[1]], "TestDir2_1/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -317,7 +341,15 @@ string hello world 2 2 1 filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) test.NoError(t, err) test.Equal(t, 0, len(obsolete)) @@ -353,7 +385,15 @@ string hello world 2 2 1 delete(tests[used[0]], "TestDir1_3/TestSimple") delete(tests[used[1]], "TestDir2_1/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) test.NoError(t, err) test.Equal(t, []string{ @@ -404,7 +444,15 @@ func TestExamineSnaps_WithLabels(t *testing.T) { filepath.FromSlash(dir2 + "/test2.snap"), } - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) test.Equal(t, []string{}, obsolete) test.NoError(t, err) @@ -426,11 +474,27 @@ func TestExamineSnaps_WithLabels(t *testing.T) { // Removing the test entirely delete(tests[used[1]], "TestDir2_2/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) 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.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 @@ -456,7 +520,15 @@ func TestExamineSnaps_WithLabels(t *testing.T) { // removing tests from the map means those tests are no longer used delete(tests[used[0]], "TestDir1_3/TestSimple") - obsolete, isDirty, err := examineSnaps(tests, used, "", 1, shouldUpdate, sort, testIdLabelMappings) + obsolete, isDirty, err := examineSnaps( + tests, + used, + "", + 1, + shouldUpdate, + sort, + testIdLabelMappings, + ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -665,7 +737,11 @@ func TestGetTestID(t *testing.T) { {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: "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}, From 97d68c2b3adb52e376d9c2d4376b8e028a650d24 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:21:29 +1300 Subject: [PATCH 08/11] refactor: rename variables and add some comments --- snaps/clean.go | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index 30b2598..08797ab 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -268,25 +268,28 @@ func examineSnaps( for s.Scan() { b := s.Bytes() // Check if line is a test id - testIDWithLabel, testIDWithoutLabel, match := getTestID(b) + oldTestIDWithLabel, oldTestIDWithoutLabel, match := getTestID(b) if !match { continue } - testIDs = append(testIDs, testIDWithLabel) + testIDs = append(testIDs, oldTestIDWithLabel) - currentTestIdWithLabel, ok := testIdLabelMappings[testIDWithoutLabel] + // resolve the current full test snapshot id, based on the "old" one + currentTestIdWithLabel, ok := testIdLabelMappings[oldTestIDWithoutLabel] - if ok && currentTestIdWithLabel != testIDWithLabel { - obsoleteTests = append(obsoleteTests, testIDWithLabel) + // remove the old test snapshot if the label has been changed + if ok && currentTestIdWithLabel != oldTestIDWithLabel { + obsoleteTests = append(obsoleteTests, oldTestIDWithLabel) needsUpdating = true removeSnapshot(s) continue } - if !registeredTests.Has(testIDWithoutLabel) && - !testSkipped(testIDWithoutLabel, runOnly) { - obsoleteTests = append(obsoleteTests, testIDWithoutLabel) + // remove any test snapshots whose test no longer exists + if !registeredTests.Has(oldTestIDWithoutLabel) && + !testSkipped(oldTestIDWithoutLabel, runOnly) { + obsoleteTests = append(obsoleteTests, oldTestIDWithoutLabel) needsUpdating = true removeSnapshot(s) @@ -297,7 +300,7 @@ func examineSnaps( line := s.Bytes() if bytes.Equal(line, endSequenceByteSlice) { - tests[testIDWithLabel] = data.String() + tests[oldTestIDWithLabel] = data.String() data.Reset() break From cbbd84dacb70960eca71708490976da769d93817 Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:46:40 +1300 Subject: [PATCH 09/11] test: add cases involving labels --- snaps/snapshot_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/snaps/snapshot_test.go b/snaps/snapshot_test.go index 24269c6..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-v3 - 2 - labelled]", + registry.getTestID("/file", "test-v3", "labelled"), + ) test.Equal(t, registry.cleanup, registry.running) }) From ff4ea4498bdd4eab5196c9c8be6720e71021d85e Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:48:40 +1300 Subject: [PATCH 10/11] docs: document labeling support --- README.md | 2 ++ 1 file changed, 2 insertions(+) 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 From 49603d860cfbd62d131ba7d65541afd3ffcc504d Mon Sep 17 00:00:00 2001 From: Gareth Jones <3151613+G-Rath@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:55:00 +1300 Subject: [PATCH 11/11] refactor: remove the need for `testIdLabelMappings` when examining snaps --- snaps/clean.go | 22 ++--- snaps/clean_test.go | 191 +++++++++++++++++++++++++------------------- 2 files changed, 119 insertions(+), 94 deletions(-) diff --git a/snaps/clean.go b/snaps/clean.go index 08797ab..3937f54 100644 --- a/snaps/clean.go +++ b/snaps/clean.go @@ -110,13 +110,11 @@ func Clean(m *testing.M, opts ...CleanOpts) (bool, error) { shouldClean, ) obsoleteTests, snapsDirty, err := examineSnaps( - testsRegistry.cleanup, + testsRegistry.labeled, usedFiles, runOnly, - count, shouldClean, opt.Sort, - testsRegistry.labeled, ) if err != nil { return snapsDirty || filesDirty, err @@ -240,13 +238,11 @@ func examineFiles( } func examineSnaps( - registry map[string]map[string]int, + testIdLabelMappings map[string]string, used []string, runOnly string, - count int, shouldUpdate, sort bool, - testIdLabelMappings map[string]string, ) ([]string, bool, error) { obsoleteTests := []string{} tests := map[string]string{} @@ -262,7 +258,6 @@ func examineSnaps( var needsUpdating bool - registeredTests := occurrences(registry[snapPath], count, snapshotOccurrenceFMT) s := snapshotScanner(f) for s.Scan() { @@ -277,19 +272,18 @@ func examineSnaps( // resolve the current full test snapshot id, based on the "old" one currentTestIdWithLabel, ok := testIdLabelMappings[oldTestIDWithoutLabel] - // remove the old test snapshot if the label has been changed - if ok && currentTestIdWithLabel != oldTestIDWithLabel { - obsoleteTests = append(obsoleteTests, oldTestIDWithLabel) + // remove any test snapshots whose test no longer exists + if !ok && !testSkipped(oldTestIDWithoutLabel, runOnly) { + obsoleteTests = append(obsoleteTests, oldTestIDWithoutLabel) needsUpdating = true removeSnapshot(s) continue } - // remove any test snapshots whose test no longer exists - if !registeredTests.Has(oldTestIDWithoutLabel) && - !testSkipped(oldTestIDWithoutLabel, runOnly) { - obsoleteTests = append(obsoleteTests, oldTestIDWithoutLabel) + // remove the old test snapshot if the label has been changed + if ok && currentTestIdWithLabel != oldTestIDWithLabel { + obsoleteTests = append(obsoleteTests, oldTestIDWithLabel) needsUpdating = true removeSnapshot(s) diff --git a/snaps/clean_test.go b/snaps/clean_test.go index cbe716a..251efc5 100644 --- a/snaps/clean_test.go +++ b/snaps/clean_test.go @@ -176,24 +176,9 @@ func TestExamineFiles(t *testing.T) { } func TestExamineSnaps(t *testing.T) { - testIdLabelMappings := 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", - } - 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"), @@ -204,13 +189,24 @@ func TestExamineSnaps(t *testing.T) { } obsolete, isDirty, err := examineSnaps( - tests, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) test.Equal(t, []string{}, obsolete) @@ -222,25 +218,31 @@ 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, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -258,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"), @@ -268,18 +270,25 @@ 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, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -331,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, @@ -342,13 +351,24 @@ string hello world 2 2 1 } obsolete, isDirty, err := examineSnaps( - tests, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) test.NoError(t, err) @@ -371,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, @@ -381,18 +401,25 @@ 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, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) test.NoError(t, err) @@ -420,21 +447,9 @@ string hello world 2 2 1 } func TestExamineSnaps_WithLabels(t *testing.T) { - testIdLabelMappings := 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", - } - 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-labeled"), loadMockSnap(t, "mock-snap-2-labeled"), @@ -445,13 +460,21 @@ func TestExamineSnaps_WithLabels(t *testing.T) { } obsolete, isDirty, err := examineSnaps( - tests, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) test.Equal(t, []string{}, obsolete) @@ -463,25 +486,28 @@ func TestExamineSnaps_WithLabels(t *testing.T) { shouldUpdate, sort := false, false mockSnap1 := loadMockSnap(t, "mock-snap-1-labeled-renamed") mockSnap2 := loadMockSnap(t, "mock-snap-2-labeled") - 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, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1]) @@ -507,7 +533,7 @@ func TestExamineSnaps_WithLabels(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-labeled-renamed"), loadMockSnap(t, "mock-snap-2-labeled"), @@ -517,17 +543,22 @@ func TestExamineSnaps_WithLabels(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") - obsolete, isDirty, err := examineSnaps( - tests, + 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, "", - 1, shouldUpdate, sort, - testIdLabelMappings, ) content1 := test.GetFileContent(t, used[0]) content2 := test.GetFileContent(t, used[1])