This repository has been archived on 2026-10-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
submarine/internal/submodule/git/parse_test.go
T
Simon FrankenandClaude Opus 5.5 19c6f17eb2 Preview uncommitted changes in the detail pane
For a dirty submodule, list the changed files so the user can remember
what they were doing there:

  Changes   5 files · 2M 1A 1D 1R · +37 −12
            ──────────────────────────────────
            M  render.go  src/render/  +20 −4  3d ago
            ?  scratch.txt                     5m ago

- Status.Changes: files changed against HEAD (staged and unstaged
  combined) with kind, line counts and modification time; sorted by kind,
  then by size; capped at 200 with totals over all files.
- Git provider: parse `status --porcelain=v2 -z` and one
  `diff HEAD --numstat -z -M`, only for dirty submodules.
- Detail pane: flat list with a colored status letter per file, like IDE
  source-control views; counts and age in compact columns; up to 10 files,
  every kind keeps a line, the rest is summarized as "n more".
- Fixture gains libs/work with every kind of change; mock data extended.
- Docs: F24, change preview in the UX guide, D17.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:55:46 +02:00

139 lines
4.4 KiB
Go

package git
import (
"slices"
"strings"
"testing"
"submarine/internal/submodule"
)
func TestParseGitmodules(t *testing.T) {
out := "submodule.src/core.path src/core\n" +
"submodule.src/core.url ../core.git\n" +
"submodule.lib.v2.path libs/v2\n" +
"submodule.lib.v2.branch develop\n" +
"submodule.lib.v2.url git@example.com:lib.git\n" +
"submodule.nopath.url x\n"
want := []gitmodule{
{name: "src/core", path: "src/core", url: "../core.git"},
{name: "lib.v2", path: "libs/v2", url: "git@example.com:lib.git", branch: "develop"},
}
if got := parseGitmodules(out); !slices.Equal(got, want) {
t.Errorf("parseGitmodules() = %+v, want %+v", got, want)
}
if got := parseGitmodules(""); got != nil {
t.Errorf("parseGitmodules(\"\") = %+v, want nil", got)
}
}
func z(recs ...string) string { return strings.Join(recs, "\x00") + "\x00" }
func TestParseStatus(t *testing.T) {
out := z(
"1 .M N... 100644 100644 100644 a b src/my file.go",
"1 A. N... 000000 100644 100644 a b new.go",
"1 AD N... 000000 100644 000000 a b gone.go",
"1 D. N... 100644 000000 000000 a b old.go",
"1 .M S.M. 160000 160000 160000 a b libs/nested",
"2 R. N... 100644 100644 100644 a b R100 dir/ü new.go", "dir/old.go",
"u UU N... 100644 100644 100644 100644 a b c conflict.go",
"? scratch dir/",
"! ignored.o",
)
want := []entry{
{path: "src/my file.go", kind: submodule.ChangeModified, tracked: true},
{path: "new.go", kind: submodule.ChangeAdded, tracked: true},
{path: "old.go", kind: submodule.ChangeRemoved, tracked: true},
{path: "libs/nested", kind: submodule.ChangeModified, tracked: true, submodule: true},
{path: "dir/ü new.go", orig: "dir/old.go", kind: submodule.ChangeRenamed, tracked: true},
{path: "conflict.go", kind: submodule.ChangeConflicted, tracked: true},
{path: "scratch dir/", kind: submodule.ChangeUntracked},
}
if got := parseStatus(out); !slices.Equal(got, want) {
t.Errorf("parseStatus() =\n%+v\nwant\n%+v", got, want)
}
if got := parseStatus(""); got != nil {
t.Errorf("parseStatus(\"\") = %+v, want nil", got)
}
}
func TestWorktreeOf(t *testing.T) {
untracked := entry{kind: submodule.ChangeUntracked}
modified := entry{kind: submodule.ChangeModified, tracked: true}
conflict := entry{kind: submodule.ChangeConflicted, tracked: true}
tests := []struct {
es []entry
want submodule.Worktree
}{
{nil, submodule.WorktreeClean},
{[]entry{untracked}, submodule.WorktreeUntracked},
{[]entry{untracked, modified}, submodule.WorktreeModified},
{[]entry{modified, untracked, conflict}, submodule.WorktreeConflict},
}
for _, tt := range tests {
if got := worktreeOf(tt.es); got != tt.want {
t.Errorf("worktreeOf(%+v) = %v, want %v", tt.es, got, tt.want)
}
}
}
func TestParseNumstat(t *testing.T) {
out := z("3\t1\tREADME.md", "-\t-\timg/logo.png", "0\t0\t", "old name.go", "new name.go", "5\t0\tsrc/a b.go")
want := map[string]numstat{
"README.md": {added: 3, removed: 1},
"img/logo.png": {binary: true},
"new name.go": {},
"src/a b.go": {added: 5},
}
got := parseNumstat(out)
if len(got) != len(want) {
t.Fatalf("parseNumstat() = %+v, want %+v", got, want)
}
for k, w := range want {
if got[k] != w {
t.Errorf("parseNumstat()[%q] = %+v, want %+v", k, got[k], w)
}
}
}
func TestParseLeftRight(t *testing.T) {
if l, r, err := parseLeftRight("3\t1"); err != nil || l != 3 || r != 1 {
t.Errorf("parseLeftRight = %d, %d, %v; want 3, 1, nil", l, r, err)
}
if _, _, err := parseLeftRight("3"); err == nil {
t.Error("parseLeftRight(\"3\") should fail")
}
}
func TestParseCommits(t *testing.T) {
out := "abc1234\x1fFix it\x1fAda\x1f1700000000\x1e\n" +
"def5678\x1fAdd\x1fBob\x1f1700000100\x1e\n" +
"malformed\x1e\n"
got := parseCommits(out)
want := []string{"abc1234", "def5678"}
if len(got) != len(want) {
t.Fatalf("parseCommits() = %+v, want SHAs %v", got, want)
}
for i, c := range got {
if c.SHA != want[i] {
t.Errorf("commit %d: SHA %q, want %q", i, c.SHA, want[i])
}
}
if got[0].Subject != "Fix it" || got[0].Author != "Ada" || got[0].When.Unix() != 1700000000 {
t.Errorf("parseCommits()[0] = %+v", got[0])
}
}
func TestParseGitlink(t *testing.T) {
if got := parseGitlink("160000 0123abc 0\tsrc/core"); got != "0123abc" {
t.Errorf("ls-files: got %q", got)
}
if got := parseGitlink("160000 commit 0123abc\tsrc/core"); got != "0123abc" {
t.Errorf("ls-tree: got %q", got)
}
if got := parseGitlink("100644 0123abc 0\tREADME"); got != "" {
t.Errorf("blob: got %q", got)
}
}