Files
lazygit/pkg/config/diff_renderer_config_manager_test.go
T
Stefan HallerandClaude Opus 5.5 d38248d851 Tell diff renderers whether the terminal is dark or light
Renderers like delta and difftastic pick their colors for either a dark
or a light background, and they can't find out which one the terminal
has. Lazygit runs them with TERM=dumb, in a pty that doesn't answer
their queries. So the colors come from the config, and when the
terminal switches between dark and light, the diff keeps the ones it
has.

Add {{colorScheme}} to the commands of diff renderers. It is 'dark' or
'light', going by gui.colorScheme, or by the terminal if that is
'auto'. It can be passed to delta as --{{colorScheme}} and to
difftastic as --background={{colorScheme}}; other renderers can choose
between options with a template expression. When the terminal switches
between dark and light, render the diff again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 08:09:45 +02:00

249 lines
8.7 KiB
Go

package config
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/stretchr/testify/assert"
)
func TestCurrentDiffRendererName(t *testing.T) {
tr := i18n.EnglishTranslationSet()
scenarios := []struct {
name string
diffRendererConfig DiffRendererConfig
expected string
}{
{
name: "explicit name takes precedence over the command",
diffRendererConfig: DiffRendererConfig{Name: "delta side-by-side", Command: "delta --side-by-side"},
expected: "delta side-by-side",
},
{
name: "derived from the first word of the stdinFilter command",
diffRendererConfig: DiffRendererConfig{Command: "delta --side-by-side"},
expected: "delta",
},
{
name: "surrounding whitespace in the command is ignored",
diffRendererConfig: DiffRendererConfig{Command: " diff-so-fancy "},
expected: "diff-so-fancy",
},
{
name: "derived from the first word of the extDiff command",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --color=always"},
expected: "difft",
},
{
name: "no name can be derived for external diff",
diffRendererConfig: DiffRendererConfig{Type: "extDiff"},
expected: tr.ExternalDiffDiffRendererName,
},
{
name: "derived from first argument of rawGit args",
diffRendererConfig: DiffRendererConfig{Type: "rawGit", Args: []string{"--color-words"}},
expected: "--color-words",
},
{
name: "no name can be derived for raw diff",
diffRendererConfig: DiffRendererConfig{Type: "rawGit"},
expected: tr.DefaultDiffRendererName,
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
userConfig := &UserConfig{}
userConfig.Git.DiffRenderers = []DiffRendererConfig{s.diffRendererConfig}
config := NewDiffRendererConfigManager(func() *UserConfig { return userConfig })
assert.Equal(t, s.expected, config.CurrentDiffRendererName(tr))
})
}
}
func TestGetStdinFilterCommand(t *testing.T) {
scenarios := []struct {
name string
diffRendererConfig DiffRendererConfig
width int
lightBackground bool
expected string
expectedError string
}{
{
name: "a command without template variables is passed through",
diffRendererConfig: DiffRendererConfig{Command: "delta --paging=never"},
width: 120,
expected: "delta --paging=never",
},
{
name: "the width the diff is rendered at",
diffRendererConfig: DiffRendererConfig{Command: "delta --width={{width}}"},
width: 120,
expected: "delta --width=120",
},
{
name: "the width of one side of a side-by-side rendering",
diffRendererConfig: DiffRendererConfig{Command: "ydiff -p cat -w {{columnWidth}}"},
width: 120,
expected: "ydiff -p cat -w 54",
},
{
name: "a template variable can also be written with a leading dot",
diffRendererConfig: DiffRendererConfig{Command: "delta --width={{.width}}"},
width: 120,
expected: "delta --width=120",
},
{
name: "the color scheme on a dark background",
diffRendererConfig: DiffRendererConfig{Command: "delta --{{colorScheme}}"},
width: 120,
expected: "delta --dark",
},
{
name: "the color scheme on a light background",
diffRendererConfig: DiffRendererConfig{Command: "delta --{{colorScheme}}"},
width: 120,
lightBackground: true,
expected: "delta --light",
},
{
name: "the command can choose between options by the color scheme",
diffRendererConfig: DiffRendererConfig{Command: `delta --syntax-theme={{if eq .colorScheme "light"}}GitHub{{else}}Dracula{{end}}`},
width: 120,
lightBackground: true,
expected: "delta --syntax-theme=GitHub",
},
{
name: "the command can use template expressions",
diffRendererConfig: DiffRendererConfig{Command: "delta{{if gt .width 100}} --side-by-side{{end}}"},
width: 120,
expected: "delta --side-by-side",
},
{
name: "an unknown template variable is an error",
diffRendererConfig: DiffRendererConfig{Command: "delta --width={{.widht}}"},
width: 120,
expectedError: "can't use the command 'delta --width={{.widht}}'",
},
{
name: "an unknown template variable without a leading dot is an error too",
diffRendererConfig: DiffRendererConfig{Command: "delta --width={{widht}}"},
width: 120,
expectedError: "can't use the command 'delta --width={{widht}}'",
},
{
name: "nothing is returned for a renderer of another type",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --width={{width}}"},
width: 120,
expected: "",
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
userConfig := &UserConfig{}
userConfig.Git.DiffRenderers = []DiffRendererConfig{s.diffRendererConfig}
config := NewDiffRendererConfigManager(func() *UserConfig { return userConfig })
command, err := config.GetStdinFilterCommand(DiffRendererValues{Width: s.width, LightBackground: s.lightBackground})
if s.expectedError != "" {
assert.ErrorContains(t, err, s.expectedError)
} else {
assert.NoError(t, err)
assert.Equal(t, s.expected, command)
}
})
}
}
func TestGetExternalDiffCommand(t *testing.T) {
scenarios := []struct {
name string
diffRendererConfig DiffRendererConfig
expected string
expectedError string
}{
{
name: "a command without template variables is passed through",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --color=always"},
expected: "difft --color=always",
},
{
name: "the width the diff is rendered at",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --width={{width}}"},
expected: "difft --width=120",
},
{
name: "the color scheme",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --background={{colorScheme}}"},
expected: "difft --background=dark",
},
{
name: "the width alongside the diff context size",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --width={{width}} --context={{diffContext}}"},
expected: "difft --width=120 --context=3",
},
{
name: "a variable of stdin filters is an error",
diffRendererConfig: DiffRendererConfig{Type: "extDiff", Command: "difft --width={{columnWidth}}"},
expectedError: "can't use the command 'difft --width={{columnWidth}}'",
},
{
name: "nothing is returned for a renderer of another type",
diffRendererConfig: DiffRendererConfig{Command: "delta --width={{width}}"},
expected: "",
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
userConfig := &UserConfig{}
userConfig.Git.DiffRenderers = []DiffRendererConfig{s.diffRendererConfig}
config := NewDiffRendererConfigManager(func() *UserConfig { return userConfig })
command, err := config.GetExternalDiffCommand(DiffRendererValues{Width: 120, DiffContext: 3})
if s.expectedError != "" {
assert.ErrorContains(t, err, s.expectedError)
} else {
assert.NoError(t, err)
assert.Equal(t, s.expected, command)
}
})
}
}
func TestCurrentDiffRendererNameWithoutDiffRenderers(t *testing.T) {
config := NewDiffRendererConfigManager(func() *UserConfig { return &UserConfig{} })
tr := i18n.EnglishTranslationSet()
assert.Equal(t, tr.DefaultDiffRendererName, config.CurrentDiffRendererName(tr))
}
func TestCycleDiffRenderers(t *testing.T) {
userConfig := &UserConfig{}
userConfig.Git.DiffRenderers = []DiffRendererConfig{{Name: "a"}, {Name: "b"}, {Name: "c"}}
config := NewDiffRendererConfigManager(func() *UserConfig { return userConfig })
currentIndex := func() int {
index, _ := config.CurrentDiffRendererIndex()
return index
}
assert.Equal(t, 0, currentIndex())
config.CycleDiffRenderers()
assert.Equal(t, 1, currentIndex())
config.CycleDiffRenderers()
assert.Equal(t, 2, currentIndex())
config.CycleDiffRenderers()
assert.Equal(t, 0, currentIndex(), "cycling forward past the last diff renderer wraps to the first")
config.CycleDiffRenderersBackward()
assert.Equal(t, 2, currentIndex(), "cycling backward past the first diff renderer wraps to the last")
config.CycleDiffRenderersBackward()
assert.Equal(t, 1, currentIndex())
}