Skip to content

Commit

Permalink
feat: support matchStandaloneSnapshot (#102)
Browse files Browse the repository at this point in the history
  • Loading branch information
gkampitakis committed Jul 28, 2024
1 parent 91ca918 commit 2fadb85
Show file tree
Hide file tree
Showing 18 changed files with 766 additions and 151 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -195,12 +196,36 @@ match.Type[string]("user.info").

You can see more [examples](./examples/matchJSON_test.go#L96).

## MatchStandaloneSnapshot

`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, "<html><body><h1>Hello World</h1></body></html>")
}
```

`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

`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
Expand All @@ -210,6 +235,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)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

<!DOCTYPE html>
<html>
<body>

<h1>My First Heading</h1>

<p>My first paragraph.</p>

</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>Hello World</div>
1 change: 1 addition & 0 deletions examples/__snapshots__/my_standalone_snap_1.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world-0
1 change: 1 addition & 0 deletions examples/__snapshots__/my_standalone_snap_2.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world-1
1 change: 1 addition & 0 deletions examples/__snapshots__/my_standalone_snap_3.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world-2
1 change: 1 addition & 0 deletions examples/__snapshots__/my_standalone_snap_4.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world-3
40 changes: 40 additions & 0 deletions examples/matchStandaloneSnapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package examples

import (
"testing"

"github.com/gkampitakis/go-snaps/snaps"
)

func TestMatchStandaloneSnapshot(t *testing.T) {
t.Run("should create html snapshots", func(t *testing.T) {
snaps := snaps.WithConfig(
snaps.Ext(".html"),
)

snaps.MatchStandaloneSnapshot(t, `
<!DOCTYPE html>
<html>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
</body>
</html>
`)
snaps.MatchStandaloneSnapshot(t, "<div>Hello World</div>")
})

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")
})
}
8 changes: 8 additions & 0 deletions internal/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
76 changes: 40 additions & 36 deletions snaps/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import (
"github.com/maruel/natural"
)

// Matches [ Test... - number ] testIDs
var (
testEvents = newTestEvents()
)
var testEvents = newTestEvents()

const (
erred uint8 = iota
Expand Down Expand Up @@ -84,8 +81,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,
Expand Down Expand Up @@ -145,16 +152,13 @@ func isNumber(b []byte) bool {
return true
}

/*
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,
runOnly string,
shouldUpdate bool,
) (obsolete, used []string) {
Expand All @@ -164,13 +168,17 @@ 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
}

Expand All @@ -180,6 +188,13 @@ func examineFiles(
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
}
Expand Down Expand Up @@ -220,7 +235,7 @@ func examineSnaps(

var hasDiffs bool

registeredTests := occurrences(registry[snapPath], count)
registeredTests := occurrences(registry[snapPath], count, snapshotOccurrenceFMT)
s := snapshotScanner(f)

for s.Scan() {
Expand Down Expand Up @@ -389,38 +404,27 @@ 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))
}

/*
Builds a Set with all snapshot ids registered inside a snap file
Form: testname - number id
tests have the form
map[filepath]: map[testname]: <number of snapshots>
e.g
./path/__snapshots__/add_test.snap map[TestAdd] 3
will result to
func standaloneOccurrenceFMT(s string, i int) string {
return fmt.Sprintf(s, i)
}

TestAdd - 1
TestAdd - 2
TestAdd - 3
func snapshotOccurrenceFMT(s string, i int) string {
return fmt.Sprintf("%s - %d", s, i)
}

as it means there are 3 snapshots created inside TestAdd
*/
func occurrences(tests map[string]int, count int) set {
// 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 {
// divide a test's counter by count (how many times the go test suite ran)
// this gives us how many snapshots were created in a single test run.
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
Expand Down
Loading

0 comments on commit 2fadb85

Please sign in to comment.