package files_test import ( "os" "path/filepath" "testing" "github.com/ptdewey/shutter/internal/files" ) func TestSnapshotFileName(t *testing.T) { tests := []struct { input string expected string }{ {"Test My Function", "test_my_function"}, {"test_another_one", "test_another_one"}, {"Test Camel Case", "test_camel_case"}, {"Test With Numbers123", "test_with_numbers123"}, {"Test ABC", "test_abc"}, {"test", "test"}, {"TEST", "test"}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { result := files.SnapshotFileName(tt.input) if result != tt.expected { t.Errorf("SnapshotFileName(%s) = %s, want %s", tt.input, result, tt.expected) } }) } } func TestSerializeDeserialize(t *testing.T) { snap := &files.Snapshot{ Title: "Example Title", Test: "TestExample", FileName: "example_test.go", Version: "1.0.0", Content: "test content\nmultiline", } serialized := snap.Serialize() expected := "---\ntitle: Example Title\ntest_name: TestExample\nfile_name: example_test.go\nversion: 1.0.0\n---\ntest content\nmultiline" if serialized != expected { t.Errorf("Serialize():\nexpected:\n%s\n\ngot:\n%s", expected, serialized) } deserialized, err := files.Deserialize(serialized) if err != nil { t.Fatalf("Deserialize failed: %v", err) } if deserialized.Title != snap.Title { t.Errorf("Title mismatch: %s != %s", deserialized.Title, snap.Title) } if deserialized.Test != snap.Test { t.Errorf("Name mismatch: %s != %s", deserialized.Test, snap.Test) } if deserialized.FileName != snap.FileName { t.Errorf("FileName mismatch: %s != %s", deserialized.FileName, snap.FileName) } if deserialized.Version != snap.Version { t.Errorf("Version mismatch: %s != %s", deserialized.Version, snap.Version) } if deserialized.Content != snap.Content { t.Errorf("Content mismatch: %s != %s", deserialized.Content, snap.Content) } } func TestDeserializeInvalidFormat(t *testing.T) { tests := []struct { name string input string }{ {"missing separators", "no separators here"}, {"only one separator", "---\nno closing separator"}, {"empty string", ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _, err := files.Deserialize(tt.input) if err == nil { t.Error("expected error for invalid format") } }) } } func TestDeserializeValidFormats(t *testing.T) { tests := []struct { name string input string wantTitle string wantTest string wantVersion string wantContent string }{ { "simple", "---\ntitle: Simple Title\ntest_name: Test\nfile_path: /path\nfunc_name: \n---\ncontent", "Simple Title", "Test", "", "content", }, { "with version", "---\ntitle: With Version\ntest_name: Test\nfile_path: /path\nfunc_name: \nversion: 1.0.0\n---\ncontent", "With Version", "Test", "1.0.0", "content", }, { "multiline content", "---\ntitle: Multi Title\ntest_name: MyTest\nfile_path: /path\nfunc_name: \n---\nline1\nline2\nline3", "Multi Title", "MyTest", "", "line1\nline2\nline3", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { snap, err := files.Deserialize(tt.input) if err != nil { t.Fatalf("Deserialize failed: %v", err) } if snap.Title != tt.wantTitle { t.Errorf("Title = %s, want %s", snap.Title, tt.wantTitle) } if snap.Test != tt.wantTest { t.Errorf("Name = %s, want %s", snap.Test, tt.wantTest) } if snap.Version != tt.wantVersion { t.Errorf("Version = %s, want %s", snap.Version, tt.wantVersion) } if snap.Content != tt.wantContent { t.Errorf("Content = %s, want %s", snap.Content, tt.wantContent) } }) } } func TestSaveAndReadSnapshot(t *testing.T) { snap := &files.Snapshot{ Title: "Save Read Title", Test: "TestSaveRead", Content: "saved content", } if err := files.SaveSnapshot(snap, "test"); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } read, err := files.ReadSnapshot("Save Read Title", "test") if err != nil { t.Fatalf("ReadSnapshot failed: %v", err) } if read.Content != snap.Content { t.Errorf("Content mismatch: %s != %s", read.Content, snap.Content) } cleanupSnapshot(t, "Save Read Title", "test") } func TestReadSnapshotNotFound(t *testing.T) { _, err := files.ReadSnapshot("NonExistentTest", "nonexistent") if err == nil { t.Error("expected error for non-existent snapshot") } } func TestAcceptSnapshot(t *testing.T) { newSnap := &files.Snapshot{ Title: "Accept Title", Test: "TestAccept", Content: "new content to accept", } if err := files.SaveSnapshot(newSnap, "new"); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } if err := files.AcceptSnapshot("Accept Title"); err != nil { t.Fatalf("AcceptSnapshot failed: %v", err) } accepted, err := files.ReadSnapshot("Accept Title", "accepted") if err != nil { t.Fatalf("ReadSnapshot failed: %v", err) } if accepted.Content != newSnap.Content { t.Errorf("Content mismatch: %s != %s", accepted.Content, newSnap.Content) } _, err = files.ReadSnapshot("Accept Title", "new") if err == nil { t.Error("expected error: .new file should be deleted after accept") } cleanupSnapshot(t, "Accept Title", "accepted") } func TestRejectSnapshot(t *testing.T) { snap := &files.Snapshot{ Title: "Reject Title", Test: "TestReject", Content: "content to reject", } if err := files.SaveSnapshot(snap, "new"); err != nil { t.Fatalf("SaveSnapshot failed: %v", err) } if err := files.RejectSnapshot("Reject Title"); err != nil { t.Fatalf("RejectSnapshot failed: %v", err) } _, err := files.ReadSnapshot("Reject Title", "new") if err == nil { t.Error("expected error: .new file should be deleted after reject") } } func cleanupSnapshot(t *testing.T, testName, state string) { t.Helper() root, err := os.Getwd() if err != nil { t.Logf("cleanup: failed to get cwd: %v", err) return } for root != "/" && root != "" { if _, err := os.Stat(filepath.Join(root, "go.mod")); err == nil { break } root = filepath.Dir(root) } fileName := files.SnapshotFileName(testName) + "." + state filePath := filepath.Join(root, "__snapshots__", fileName) _ = os.Remove(filePath) } func TestRecursiveSnapshots(t *testing.T) { // This test verifies that ListNewSnapshots finds snapshots recursively snapshots, err := files.ListNewSnapshots() if err != nil { t.Fatalf("ListNewSnapshots failed: %v", err) } t.Logf("Found %d snapshots", len(snapshots)) for _, snap := range snapshots { t.Logf(" - Title: %s, Path: %s", snap.Title, snap.Path) } // Just verify it doesn't error - we can't make assumptions about which // snapshots exist since this depends on the test environment if err != nil { t.Errorf("Error listing snapshots: %v", err) } } func TestListNewSnapshotsNested(t *testing.T) { // Run inside a fresh tempdir with its own go.mod so findProjectRoot // scopes the scan to this test. tmp := t.TempDir() if err := os.WriteFile(filepath.Join(tmp, "go.mod"), []byte("module test\n"), 0644); err != nil { t.Fatalf("write go.mod: %v", err) } origCwd, err := os.Getwd() if err != nil { t.Fatalf("getwd: %v", err) } if err := os.Chdir(tmp); err != nil { t.Fatalf("chdir: %v", err) } t.Cleanup(func() { _ = os.Chdir(origCwd) }) // Create a nested .snap.new under __snapshots__/sub/ nestedDir := filepath.Join(tmp, "__snapshots__", "sub") if err := os.MkdirAll(nestedDir, 0755); err != nil { t.Fatalf("mkdirall: %v", err) } nestedPath := filepath.Join(nestedDir, "leaf.snap.new") if err := os.WriteFile(nestedPath, []byte("---\ntitle: sub/leaf\n---\nbody"), 0644); err != nil { t.Fatalf("write nested: %v", err) } // Also a flat one at the top level of __snapshots__/ flatPath := filepath.Join(tmp, "__snapshots__", "flat.snap.new") if err := os.WriteFile(flatPath, []byte("---\ntitle: flat\n---\nbody"), 0644); err != nil { t.Fatalf("write flat: %v", err) } snapshots, err := files.ListNewSnapshots() if err != nil { t.Fatalf("ListNewSnapshots: %v", err) } titles := map[string]string{} for _, s := range snapshots { titles[s.Title] = s.Path } if got, ok := titles["sub/leaf"]; !ok || got != nestedPath { t.Errorf("expected nested title 'sub/leaf' at %s, got titles=%v", nestedPath, titles) } if got, ok := titles["flat"]; !ok || got != flatPath { t.Errorf("expected flat title 'flat' at %s, got titles=%v", flatPath, titles) } }