mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-10 20:05:50 +02:00
Now that commit hashes are stored in a pool and referenced by pointer by the commits, we can use those same pointers in the pipes.
606 lines
22 KiB
Go
606 lines
22 KiB
Go
package graph
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gookit/color"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation/authors"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
|
"github.com/samber/lo"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/xo/terminfo"
|
|
)
|
|
|
|
func TestRenderCommitGraph(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
commitOpts []models.NewCommitOpts
|
|
expectedOutput string
|
|
}{
|
|
{
|
|
name: "with some merges",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3"}},
|
|
{Hash: "3", Parents: []string{"4"}},
|
|
{Hash: "4", Parents: []string{"5", "7"}},
|
|
{Hash: "7", Parents: []string{"5"}},
|
|
{Hash: "5", Parents: []string{"8"}},
|
|
{Hash: "8", Parents: []string{"9"}},
|
|
{Hash: "9", Parents: []string{"A", "B"}},
|
|
{Hash: "B", Parents: []string{"D"}},
|
|
{Hash: "D", Parents: []string{"D"}},
|
|
{Hash: "A", Parents: []string{"E"}},
|
|
{Hash: "E", Parents: []string{"F"}},
|
|
{Hash: "F", Parents: []string{"D"}},
|
|
{Hash: "D", Parents: []string{"G"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ◯
|
|
3 ◯
|
|
4 ⏣─╮
|
|
7 │ ◯
|
|
5 ◯─╯
|
|
8 ◯
|
|
9 ⏣─╮
|
|
B │ ◯
|
|
D │ ◯
|
|
A ◯ │
|
|
E ◯ │
|
|
F ◯ │
|
|
D ◯─╯`,
|
|
},
|
|
{
|
|
name: "with a path that has room to move to the left",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3", "4"}},
|
|
{Hash: "4", Parents: []string{"3", "5"}},
|
|
{Hash: "3", Parents: []string{"5"}},
|
|
{Hash: "5", Parents: []string{"6"}},
|
|
{Hash: "6", Parents: []string{"7"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ⏣─╮
|
|
4 │ ⏣─╮
|
|
3 ◯─╯ │
|
|
5 ◯───╯
|
|
6 ◯`,
|
|
},
|
|
{
|
|
name: "with a new commit",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3", "4"}},
|
|
{Hash: "4", Parents: []string{"3", "5"}},
|
|
{Hash: "Z", Parents: []string{"Z"}},
|
|
{Hash: "3", Parents: []string{"5"}},
|
|
{Hash: "5", Parents: []string{"6"}},
|
|
{Hash: "6", Parents: []string{"7"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ⏣─╮
|
|
4 │ ⏣─╮
|
|
Z │ │ │ ◯
|
|
3 ◯─╯ │ │
|
|
5 ◯───╯ │
|
|
6 ◯ ╭───╯`,
|
|
},
|
|
{
|
|
name: "with a path that has room to move to the left and continues",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3", "4"}},
|
|
{Hash: "3", Parents: []string{"5", "4"}},
|
|
{Hash: "5", Parents: []string{"7", "8"}},
|
|
{Hash: "4", Parents: []string{"7"}},
|
|
{Hash: "7", Parents: []string{"11"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ⏣─╮
|
|
3 ⏣─│─╮
|
|
5 ⏣─│─│─╮
|
|
4 │ ◯─╯ │
|
|
7 ◯─╯ ╭─╯`,
|
|
},
|
|
{
|
|
name: "with a path that has room to move to the left and continues",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3", "4"}},
|
|
{Hash: "3", Parents: []string{"5", "4"}},
|
|
{Hash: "5", Parents: []string{"7", "8"}},
|
|
{Hash: "7", Parents: []string{"4", "A"}},
|
|
{Hash: "4", Parents: []string{"B"}},
|
|
{Hash: "B", Parents: []string{"C"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ⏣─╮
|
|
3 ⏣─│─╮
|
|
5 ⏣─│─│─╮
|
|
7 ⏣─│─│─│─╮
|
|
4 ◯─┴─╯ │ │
|
|
B ◯ ╭───╯ │`,
|
|
},
|
|
{
|
|
name: "with a path that has room to move to the left and continues",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2", "3"}},
|
|
{Hash: "3", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"4", "5"}},
|
|
{Hash: "4", Parents: []string{"6", "7"}},
|
|
{Hash: "6", Parents: []string{"8"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ⏣─╮
|
|
3 │ ◯
|
|
2 ⏣─│
|
|
4 ⏣─│─╮
|
|
6 ◯ │ │`,
|
|
},
|
|
{
|
|
name: "new merge path fills gap before continuing path on right",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2", "3", "4", "5"}},
|
|
{Hash: "4", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"A"}},
|
|
{Hash: "A", Parents: []string{"6", "B"}},
|
|
{Hash: "B", Parents: []string{"C"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ⏣─┬─┬─╮
|
|
4 │ │ ◯ │
|
|
2 ◯─│─╯ │
|
|
A ⏣─│─╮ │
|
|
B │ │ ◯ │`,
|
|
},
|
|
{
|
|
name: "with a path that has room to move to the left and continues",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3", "4"}},
|
|
{Hash: "3", Parents: []string{"5", "4"}},
|
|
{Hash: "5", Parents: []string{"7", "8"}},
|
|
{Hash: "7", Parents: []string{"4", "A"}},
|
|
{Hash: "4", Parents: []string{"B"}},
|
|
{Hash: "B", Parents: []string{"C"}},
|
|
{Hash: "C", Parents: []string{"D"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ⏣─╮
|
|
3 ⏣─│─╮
|
|
5 ⏣─│─│─╮
|
|
7 ⏣─│─│─│─╮
|
|
4 ◯─┴─╯ │ │
|
|
B ◯ ╭───╯ │
|
|
C ◯ │ ╭───╯`,
|
|
},
|
|
{
|
|
name: "with a path that has room to move to the left and continues",
|
|
commitOpts: []models.NewCommitOpts{
|
|
{Hash: "1", Parents: []string{"2"}},
|
|
{Hash: "2", Parents: []string{"3", "4"}},
|
|
{Hash: "3", Parents: []string{"5", "4"}},
|
|
{Hash: "5", Parents: []string{"7", "G"}},
|
|
{Hash: "7", Parents: []string{"8", "A"}},
|
|
{Hash: "8", Parents: []string{"4", "E"}},
|
|
{Hash: "4", Parents: []string{"B"}},
|
|
{Hash: "B", Parents: []string{"C"}},
|
|
{Hash: "C", Parents: []string{"D"}},
|
|
{Hash: "D", Parents: []string{"F"}},
|
|
},
|
|
expectedOutput: `
|
|
1 ◯
|
|
2 ⏣─╮
|
|
3 ⏣─│─╮
|
|
5 ⏣─│─│─╮
|
|
7 ⏣─│─│─│─╮
|
|
8 ⏣─│─│─│─│─╮
|
|
4 ◯─┴─╯ │ │ │
|
|
B ◯ ╭───╯ │ │
|
|
C ◯ │ ╭───╯ │
|
|
D ◯ │ │ ╭───╯`,
|
|
},
|
|
}
|
|
|
|
oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions)
|
|
defer color.ForceSetColorLevel(oldColorLevel)
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
hashPool := &utils.StringPool{}
|
|
|
|
getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault }
|
|
commits := lo.Map(test.commitOpts,
|
|
func(opts models.NewCommitOpts, _ int) *models.Commit { return models.NewCommit(hashPool, opts) })
|
|
lines := RenderCommitGraph(commits, hashPool.Add("blah"), getStyle)
|
|
|
|
trimmedExpectedOutput := ""
|
|
for _, line := range strings.Split(strings.TrimPrefix(test.expectedOutput, "\n"), "\n") {
|
|
trimmedExpectedOutput += strings.TrimSpace(line) + "\n"
|
|
}
|
|
|
|
t.Log("\nexpected: \n" + trimmedExpectedOutput)
|
|
|
|
output := ""
|
|
for i, line := range lines {
|
|
description := test.commitOpts[i].Hash
|
|
output += strings.TrimSpace(description+" "+utils.Decolorise(line)) + "\n"
|
|
}
|
|
t.Log("\nactual: \n" + output)
|
|
|
|
assert.Equal(t,
|
|
trimmedExpectedOutput,
|
|
output)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRenderPipeSet(t *testing.T) {
|
|
cyan := style.FgCyan
|
|
red := style.FgRed
|
|
green := style.FgGreen
|
|
// blue := style.FgBlue
|
|
yellow := style.FgYellow
|
|
magenta := style.FgMagenta
|
|
nothing := style.Nothing
|
|
|
|
hashPool := &utils.StringPool{}
|
|
pool := func(s string) *string { return hashPool.Add(s) }
|
|
|
|
tests := []struct {
|
|
name string
|
|
pipes []*Pipe
|
|
commit *models.Commit
|
|
prevCommit *models.Commit
|
|
expectedStr string
|
|
expectedStyles []style.TextStyle
|
|
}{
|
|
{
|
|
name: "single cell",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: cyan},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: green},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
|
|
expectedStr: "◯",
|
|
expectedStyles: []style.TextStyle{green},
|
|
},
|
|
{
|
|
name: "single cell, selected",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("selected"), kind: TERMINATES, style: cyan},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("c"), kind: STARTS, style: green},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
|
|
expectedStr: "◯",
|
|
expectedStyles: []style.TextStyle{highlightStyle},
|
|
},
|
|
{
|
|
name: "terminating hook and starting hook, selected",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("selected"), kind: TERMINATES, style: cyan},
|
|
{fromPos: 1, toPos: 0, fromHash: pool("c"), toHash: pool("selected"), kind: TERMINATES, style: yellow},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("d"), kind: STARTS, style: green},
|
|
{fromPos: 0, toPos: 1, fromHash: pool("selected"), toHash: pool("e"), kind: STARTS, style: green},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
|
|
expectedStr: "⏣─╮",
|
|
expectedStyles: []style.TextStyle{
|
|
highlightStyle, highlightStyle, highlightStyle,
|
|
},
|
|
},
|
|
{
|
|
name: "terminating hook and starting hook, prioritise the terminating one",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: red},
|
|
{fromPos: 1, toPos: 0, fromHash: pool("c"), toHash: pool("b"), kind: TERMINATES, style: magenta},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("d"), kind: STARTS, style: green},
|
|
{fromPos: 0, toPos: 1, fromHash: pool("b"), toHash: pool("e"), kind: STARTS, style: green},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a"}),
|
|
expectedStr: "⏣─│",
|
|
expectedStyles: []style.TextStyle{
|
|
green, green, magenta,
|
|
},
|
|
},
|
|
{
|
|
name: "starting and terminating pipe sharing some space",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b2"), kind: CONTINUES, style: magenta},
|
|
{fromPos: 3, toPos: 0, fromHash: pool("e1"), toHash: pool("a2"), kind: TERMINATES, style: green},
|
|
{fromPos: 0, toPos: 2, fromHash: pool("a2"), toHash: pool("c3"), kind: STARTS, style: yellow},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
|
|
expectedStr: "⏣─│─┬─╯",
|
|
expectedStyles: []style.TextStyle{
|
|
yellow, yellow, magenta, yellow, yellow, green, green,
|
|
},
|
|
},
|
|
{
|
|
name: "starting and terminating pipe sharing some space, with selection",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("selected"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b2"), kind: CONTINUES, style: magenta},
|
|
{fromPos: 3, toPos: 0, fromHash: pool("e1"), toHash: pool("selected"), kind: TERMINATES, style: green},
|
|
{fromPos: 0, toPos: 2, fromHash: pool("selected"), toHash: pool("c3"), kind: STARTS, style: yellow},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
|
|
expectedStr: "⏣───╮ ╯",
|
|
expectedStyles: []style.TextStyle{
|
|
highlightStyle, highlightStyle, highlightStyle, highlightStyle, highlightStyle, nothing, green,
|
|
},
|
|
},
|
|
{
|
|
name: "many terminating pipes",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
{fromPos: 1, toPos: 0, fromHash: pool("b1"), toHash: pool("a2"), kind: TERMINATES, style: magenta},
|
|
{fromPos: 2, toPos: 0, fromHash: pool("c1"), toHash: pool("a2"), kind: TERMINATES, style: green},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
|
|
expectedStr: "◯─┴─╯",
|
|
expectedStyles: []style.TextStyle{
|
|
yellow, magenta, magenta, green, green,
|
|
},
|
|
},
|
|
{
|
|
name: "starting pipe passing through",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
{fromPos: 0, toPos: 3, fromHash: pool("a2"), toHash: pool("d3"), kind: STARTS, style: yellow},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("b3"), kind: CONTINUES, style: magenta},
|
|
{fromPos: 2, toPos: 2, fromHash: pool("c1"), toHash: pool("c3"), kind: CONTINUES, style: green},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
|
|
expectedStr: "⏣─│─│─╮",
|
|
expectedStyles: []style.TextStyle{
|
|
yellow, yellow, magenta, yellow, green, yellow, yellow,
|
|
},
|
|
},
|
|
{
|
|
name: "starting and terminating path crossing continuing path",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
{fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: yellow},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("b1"), toHash: pool("a2"), kind: CONTINUES, style: green},
|
|
{fromPos: 2, toPos: 0, fromHash: pool("c1"), toHash: pool("a2"), kind: TERMINATES, style: magenta},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
|
|
expectedStr: "⏣─│─╯",
|
|
expectedStyles: []style.TextStyle{
|
|
yellow, yellow, green, magenta, magenta,
|
|
},
|
|
},
|
|
{
|
|
name: "another clash of starting and terminating paths",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
{fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: yellow},
|
|
{fromPos: 2, toPos: 2, fromHash: pool("c1"), toHash: pool("c3"), kind: CONTINUES, style: green},
|
|
{fromPos: 3, toPos: 0, fromHash: pool("d1"), toHash: pool("a2"), kind: TERMINATES, style: magenta},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a1"}),
|
|
expectedStr: "⏣─┬─│─╯",
|
|
expectedStyles: []style.TextStyle{
|
|
yellow, yellow, yellow, magenta, green, magenta, magenta,
|
|
},
|
|
},
|
|
{
|
|
name: "commit whose previous commit is selected",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: yellow},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
|
|
expectedStr: "◯",
|
|
expectedStyles: []style.TextStyle{
|
|
yellow,
|
|
},
|
|
},
|
|
{
|
|
name: "commit whose previous commit is selected and is a merge commit",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("selected"), toHash: pool("b3"), kind: CONTINUES, style: red},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
|
|
expectedStr: "◯ │",
|
|
expectedStyles: []style.TextStyle{
|
|
highlightStyle, nothing, highlightStyle,
|
|
},
|
|
},
|
|
{
|
|
name: "commit whose previous commit is selected and is a merge commit, with continuing pipe inbetween",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("z1"), toHash: pool("z3"), kind: CONTINUES, style: green},
|
|
{fromPos: 2, toPos: 2, fromHash: pool("selected"), toHash: pool("b3"), kind: CONTINUES, style: red},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
|
|
expectedStr: "◯ │ │",
|
|
expectedStyles: []style.TextStyle{
|
|
highlightStyle, nothing, green, nothing, highlightStyle,
|
|
},
|
|
},
|
|
{
|
|
name: "when previous commit is selected, not a merge commit, and spawns a continuing pipe",
|
|
pipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a1"), toHash: pool("a2"), kind: TERMINATES, style: red},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a2"), toHash: pool("a3"), kind: STARTS, style: green},
|
|
{fromPos: 0, toPos: 1, fromHash: pool("a2"), toHash: pool("b3"), kind: STARTS, style: green},
|
|
{fromPos: 1, toPos: 0, fromHash: pool("selected"), toHash: pool("a2"), kind: TERMINATES, style: yellow},
|
|
},
|
|
prevCommit: models.NewCommit(hashPool, models.NewCommitOpts{Hash: "selected"}),
|
|
expectedStr: "⏣─╯",
|
|
expectedStyles: []style.TextStyle{
|
|
highlightStyle, highlightStyle, highlightStyle,
|
|
},
|
|
},
|
|
}
|
|
|
|
oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions)
|
|
defer color.ForceSetColorLevel(oldColorLevel)
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
actualStr := renderPipeSet(test.pipes, pool("selected"), test.prevCommit)
|
|
t.Log("actual cells:")
|
|
t.Log(actualStr)
|
|
expectedStr := ""
|
|
if len([]rune(test.expectedStr)) != len(test.expectedStyles) {
|
|
t.Fatalf("Error in test setup: you have %d characters in the expected output (%s) but have specified %d styles", len([]rune(test.expectedStr)), test.expectedStr, len(test.expectedStyles))
|
|
}
|
|
for i, char := range []rune(test.expectedStr) {
|
|
expectedStr += test.expectedStyles[i].Sprint(string(char))
|
|
}
|
|
expectedStr += " "
|
|
t.Log("expected cells:")
|
|
t.Log(expectedStr)
|
|
|
|
assert.Equal(t, expectedStr, actualStr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetNextPipes(t *testing.T) {
|
|
hashPool := &utils.StringPool{}
|
|
pool := func(s string) *string { return hashPool.Add(s) }
|
|
|
|
tests := []struct {
|
|
prevPipes []*Pipe
|
|
commit *models.Commit
|
|
expected []*Pipe
|
|
}{
|
|
{
|
|
prevPipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: STARTS, style: style.FgDefault},
|
|
},
|
|
commit: models.NewCommit(hashPool, models.NewCommitOpts{
|
|
Hash: "b",
|
|
Parents: []string{"c"},
|
|
}),
|
|
expected: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: style.FgDefault},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: style.FgDefault},
|
|
},
|
|
},
|
|
{
|
|
prevPipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("b"), kind: TERMINATES, style: style.FgDefault},
|
|
{fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: STARTS, style: style.FgDefault},
|
|
{fromPos: 0, toPos: 1, fromHash: pool("b"), toHash: pool("d"), kind: STARTS, style: style.FgDefault},
|
|
},
|
|
commit: models.NewCommit(hashPool, models.NewCommitOpts{
|
|
Hash: "d",
|
|
Parents: []string{"e"},
|
|
}),
|
|
expected: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("b"), toHash: pool("c"), kind: CONTINUES, style: style.FgDefault},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("b"), toHash: pool("d"), kind: TERMINATES, style: style.FgDefault},
|
|
{fromPos: 1, toPos: 1, fromHash: pool("d"), toHash: pool("e"), kind: STARTS, style: style.FgDefault},
|
|
},
|
|
},
|
|
{
|
|
prevPipes: []*Pipe{
|
|
{fromPos: 0, toPos: 0, fromHash: pool("a"), toHash: pool("root"), kind: TERMINATES, style: style.FgDefault},
|
|
},
|
|
commit: models.NewCommit(hashPool, models.NewCommitOpts{
|
|
Hash: "root",
|
|
Parents: []string{},
|
|
}),
|
|
expected: []*Pipe{
|
|
{fromPos: 1, toPos: 1, fromHash: pool("root"), toHash: pool(models.EmptyTreeCommitHash), kind: STARTS, style: style.FgDefault},
|
|
},
|
|
},
|
|
}
|
|
|
|
oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions)
|
|
defer color.ForceSetColorLevel(oldColorLevel)
|
|
|
|
for _, test := range tests {
|
|
getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault }
|
|
pipes := getNextPipes(test.prevPipes, test.commit, getStyle)
|
|
// rendering cells so that it's easier to see what went wrong
|
|
actualStr := renderPipeSet(pipes, pool("selected"), nil)
|
|
expectedStr := renderPipeSet(test.expected, pool("selected"), nil)
|
|
t.Log("expected cells:")
|
|
t.Log(expectedStr)
|
|
t.Log("actual cells:")
|
|
t.Log(actualStr)
|
|
assert.EqualValues(t, test.expected, pipes)
|
|
}
|
|
}
|
|
|
|
func BenchmarkRenderCommitGraph(b *testing.B) {
|
|
oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions)
|
|
defer color.ForceSetColorLevel(oldColorLevel)
|
|
|
|
hashPool := &utils.StringPool{}
|
|
|
|
commits := generateCommits(hashPool, 50)
|
|
getStyle := func(commit *models.Commit) style.TextStyle {
|
|
return authors.AuthorStyle(commit.AuthorName)
|
|
}
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
RenderCommitGraph(commits, hashPool.Add("selected"), getStyle)
|
|
}
|
|
}
|
|
|
|
func generateCommits(hashPool *utils.StringPool, count int) []*models.Commit {
|
|
rnd := rand.New(rand.NewSource(1234))
|
|
pool := []*models.Commit{models.NewCommit(hashPool, models.NewCommitOpts{Hash: "a", AuthorName: "A"})}
|
|
commits := make([]*models.Commit, 0, count)
|
|
authorPool := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"}
|
|
for len(commits) < count {
|
|
currentCommitIdx := rnd.Intn(len(pool))
|
|
currentCommit := pool[currentCommitIdx]
|
|
pool = append(pool[0:currentCommitIdx], pool[currentCommitIdx+1:]...)
|
|
// I need to pick a random number of parents to add
|
|
parentCount := rnd.Intn(2) + 1
|
|
|
|
parentHashes := currentCommit.Parents()
|
|
for j := 0; j < parentCount; j++ {
|
|
reuseParent := rnd.Intn(6) != 1 && j <= len(pool)-1 && j != 0
|
|
var newParent *models.Commit
|
|
if reuseParent {
|
|
newParent = pool[j]
|
|
} else {
|
|
newParent = models.NewCommit(hashPool, models.NewCommitOpts{
|
|
Hash: fmt.Sprintf("%s%d", currentCommit.Hash(), j),
|
|
AuthorName: authorPool[rnd.Intn(len(authorPool))],
|
|
})
|
|
pool = append(pool, newParent)
|
|
}
|
|
parentHashes = append(parentHashes, newParent.Hash())
|
|
}
|
|
|
|
changedCommit := models.NewCommit(hashPool, models.NewCommitOpts{
|
|
Hash: currentCommit.Hash(),
|
|
AuthorName: currentCommit.AuthorName,
|
|
Parents: parentHashes,
|
|
})
|
|
commits = append(commits, changedCommit)
|
|
}
|
|
|
|
return commits
|
|
}
|