mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-27 10:12:44 -05:00
Now that quit accepts multiple keys, the historical quit-alt1 field is redundant: existing configs that set it should keep working without the user having to migrate, but the lazygit code shouldn't have to register the alt binding separately. Add a merge step that runs after the user config is loaded (and from NewDummyAppConfig, which the cheatsheet generator and integration tests go through) folding the alt value into the main key list. Mark QuitAlt1 deprecated so it disappears from the generated Config.md example, while staying in the JSON schema with a description so editors can still steer users toward the new form. Note that instead of marking the alt config as deprecated, we could have added a migrator that changes users' config files and gets rid of the alt config for good. I decided not to do that, because this would render the config file invalid for older versions of lazygit, which would then refuse to start; and that's annoying when bisecting bugs. We'll keep the deprecated configs in the code for a year or so, and then add the migrator. The next commit will fold the remaining ~15 -alt-style fields the same way; the helper is shaped to keep that mechanical.
251 lines
5.3 KiB
Go
251 lines
5.3 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestKeybindingUnmarshalYAML(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
input string
|
|
expected Keybinding
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "scalar string",
|
|
input: `q`,
|
|
expected: Keybinding{"q"},
|
|
},
|
|
{
|
|
name: "scalar with special characters",
|
|
input: `<esc>`,
|
|
expected: Keybinding{"<esc>"},
|
|
},
|
|
{
|
|
name: "sequence with one element",
|
|
input: `[q]`,
|
|
expected: Keybinding{"q"},
|
|
},
|
|
{
|
|
name: "sequence with multiple elements",
|
|
input: `["q", "<esc>"]`,
|
|
expected: Keybinding{"q", "<esc>"},
|
|
},
|
|
{
|
|
name: "empty sequence",
|
|
input: `[]`,
|
|
expected: Keybinding{},
|
|
},
|
|
{
|
|
name: "scalar <disabled> decodes to empty",
|
|
input: `<disabled>`,
|
|
expected: Keybinding{},
|
|
},
|
|
{
|
|
name: "scalar empty string decodes to empty",
|
|
input: `""`,
|
|
expected: Keybinding{},
|
|
},
|
|
{
|
|
name: "<disabled> entries are filtered out of a sequence",
|
|
input: `["q", "<disabled>", "<esc>"]`,
|
|
expected: Keybinding{"q", "<esc>"},
|
|
},
|
|
{
|
|
name: "mapping is rejected",
|
|
input: `{key: q}`,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
var k Keybinding
|
|
err := yaml.Unmarshal([]byte(s.input), &k)
|
|
if s.wantErr {
|
|
assert.Error(t, err)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, s.expected, k)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeybindingMarshalYAML(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
input Keybinding
|
|
expected string
|
|
}{
|
|
{
|
|
name: "single key emits a scalar",
|
|
input: Keybinding{"q"},
|
|
expected: "q\n",
|
|
},
|
|
{
|
|
name: "multiple keys emit a flow sequence",
|
|
input: Keybinding{"q", "<esc>"},
|
|
expected: "[q, <esc>]\n",
|
|
},
|
|
{
|
|
name: "empty keybinding emits an empty sequence",
|
|
input: Keybinding{},
|
|
expected: "[]\n",
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
out, err := yaml.Marshal(s.input)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, s.expected, string(out))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeybindingMarshalJSON(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
input Keybinding
|
|
expected string
|
|
}{
|
|
{
|
|
name: "single key emits a string",
|
|
input: Keybinding{"q"},
|
|
expected: `"q"`,
|
|
},
|
|
{
|
|
name: "multiple keys emit an array",
|
|
input: Keybinding{"q", "esc"},
|
|
expected: `["q","esc"]`,
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
out, err := json.Marshal(s.input)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, s.expected, string(out))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeLegacyAltKeybindings(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
quit Keybinding
|
|
quitAlt1 Keybinding
|
|
expected Keybinding
|
|
}{
|
|
{
|
|
name: "alt is folded into main",
|
|
quit: Keybinding{"q"},
|
|
quitAlt1: Keybinding{"<ctrl+c>"},
|
|
expected: Keybinding{"q", "<ctrl+c>"},
|
|
},
|
|
{
|
|
name: "alt is not appended if already present",
|
|
quit: Keybinding{"q", "<ctrl+c>"},
|
|
quitAlt1: Keybinding{"<ctrl+c>"},
|
|
expected: Keybinding{"q", "<ctrl+c>"},
|
|
},
|
|
{
|
|
name: "empty alt is ignored",
|
|
quit: Keybinding{"q"},
|
|
quitAlt1: nil,
|
|
expected: Keybinding{"q"},
|
|
},
|
|
{
|
|
name: "user-supplied multi-key main is preserved",
|
|
quit: Keybinding{"q", "<esc>"},
|
|
quitAlt1: Keybinding{"<ctrl+c>"},
|
|
expected: Keybinding{"q", "<esc>", "<ctrl+c>"},
|
|
},
|
|
{
|
|
name: "multi-key alt is folded element by element",
|
|
quit: Keybinding{"q"},
|
|
quitAlt1: Keybinding{"<ctrl+c>", "<esc>"},
|
|
expected: Keybinding{"q", "<ctrl+c>", "<esc>"},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
cfg := KeybindingConfig{
|
|
Universal: KeybindingUniversalConfig{
|
|
Quit: s.quit,
|
|
QuitAlt1: s.quitAlt1,
|
|
},
|
|
}
|
|
cfg.MergeLegacyAltKeybindings()
|
|
assert.Equal(t, s.expected, cfg.Universal.Quit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestKeybindingYAMLRoundTrip(t *testing.T) {
|
|
scenarios := []Keybinding{
|
|
{"q"},
|
|
{"q", "<esc>"},
|
|
{"<ctrl+c>", "<ctrl+d>", "<esc>"},
|
|
}
|
|
for _, original := range scenarios {
|
|
out, err := yaml.Marshal(original)
|
|
assert.NoError(t, err)
|
|
var decoded Keybinding
|
|
assert.NoError(t, yaml.Unmarshal(out, &decoded))
|
|
assert.Equal(t, original, decoded)
|
|
}
|
|
}
|
|
|
|
func TestKeybindingConfigYAMLAcceptsBothForms(t *testing.T) {
|
|
scenarios := []struct {
|
|
name string
|
|
yaml string
|
|
expected Keybinding
|
|
}{
|
|
{
|
|
name: "scalar form",
|
|
yaml: "quit: q\n",
|
|
expected: Keybinding{"q"},
|
|
},
|
|
{
|
|
name: "sequence form",
|
|
yaml: "quit: [q, <esc>]\n",
|
|
expected: Keybinding{"q", "<esc>"},
|
|
},
|
|
{
|
|
name: "block sequence form",
|
|
yaml: "quit:\n - q\n - <esc>\n",
|
|
expected: Keybinding{"q", "<esc>"},
|
|
},
|
|
}
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
var cfg KeybindingUniversalConfig
|
|
assert.NoError(t, yaml.Unmarshal([]byte(s.yaml), &cfg))
|
|
assert.Equal(t, s.expected, cfg.Quit)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJumpToBlockYAMLAcceptsMixedForms(t *testing.T) {
|
|
yamlInput := `
|
|
jumpToBlock:
|
|
- "1"
|
|
- ["2", "@"]
|
|
- "3"
|
|
- "4"
|
|
- "5"
|
|
`
|
|
var cfg KeybindingUniversalConfig
|
|
assert.NoError(t, yaml.Unmarshal([]byte(yamlInput), &cfg))
|
|
expected := []Keybinding{{"1"}, {"2", "@"}, {"3"}, {"4"}, {"5"}}
|
|
assert.Equal(t, expected, cfg.JumpToBlock)
|
|
}
|