Redesign default tree controls
This commit is contained in:
@@ -223,12 +223,13 @@ When in the message input box:
|
|||||||
* enter submits the entered message
|
* enter submits the entered message
|
||||||
|
|
||||||
When in the treeview, pressing:
|
When in the treeview, pressing:
|
||||||
* f5 or f6 on a channel changes the volume for all users in that channel
|
* left or right arrow on a channel changes the volume for all users in that channel
|
||||||
* f5 or f6 on a user changes the volume for that user.
|
* left or right arrow on a user changes the volume for that user.
|
||||||
|
* backspace restores the focused user, or every user in the focused channel, to unmuted, 100% volume, and normal boost.
|
||||||
* enter on de-selected user selects that user for PM mode.
|
* enter on de-selected user selects that user for PM mode.
|
||||||
* enter on selected user de-selects the user
|
* enter on selected user de-selects the user
|
||||||
* enter on a channel de-selects any selected users (if any) and moves you to the specified channel.
|
* enter on a channel de-selects any selected users (if any) and moves you to the specified channel.
|
||||||
* f11 opens the actions menu for the focused user or channel.
|
* f10 opens the actions menu for the focused user or channel.
|
||||||
|
|
||||||
The actions menu is a plain tree view for screen reader accessibility. It starts with non-privileged information actions, such as requesting user comments, user stats, channel descriptions, and channel permissions. If Barnard has permission information showing that you cannot perform an admin action, that privileged action is hidden. The server still remains the final authority and will reject actions if permissions are missing or stale. Destructive actions such as kick, ban, channel deletion, deregister, and raw ACL edits prompt in the input line before sending. Press Escape to close the menu or cancel an active prompt.
|
The actions menu is a plain tree view for screen reader accessibility. It starts with non-privileged information actions, such as requesting user comments, user stats, channel descriptions, and channel permissions. If Barnard has permission information showing that you cannot perform an admin action, that privileged action is hidden. The server still remains the final authority and will reject actions if permissions are missing or stale. Destructive actions such as kick, ban, channel deletion, deregister, and raw ACL edits prompt in the input line before sending. Press Escape to close the menu or cancel an active prompt.
|
||||||
|
|
||||||
@@ -250,7 +251,7 @@ Admin actions are also available through `/admin` commands and the FIFO command
|
|||||||
|
|
||||||
## Volume
|
## Volume
|
||||||
|
|
||||||
If you set the volume for a user (using the F5/F6 keys by default), Barnard will remember that volume, and will keep that user at that volume.
|
If you set the volume for a user (using the left/right arrow keys by default), Barnard will remember that volume, and will keep that user at that volume.
|
||||||
The volume is set for a single user on a single server.
|
The volume is set for a single user on a single server.
|
||||||
This means you may have to set a person to a custom volume multiple times, if you are both on multiple servers together.
|
This means you may have to set a person to a custom volume multiple times, if you are both on multiple servers together.
|
||||||
|
|
||||||
@@ -302,7 +303,7 @@ After running the command above, `barnard` will be compiled as `$(go env GOPATH)
|
|||||||
- <kbd>F1</kbd>: toggle voice transmission
|
- <kbd>F1</kbd>: toggle voice transmission
|
||||||
- <kbd>F9</kbd>: toggle noise suppression
|
- <kbd>F9</kbd>: toggle noise suppression
|
||||||
- <kbd>F12</kbd>: toggle automatic gain control
|
- <kbd>F12</kbd>: toggle automatic gain control
|
||||||
- <kbd>F11</kbd>: open actions menu for the focused tree item
|
- <kbd>F10</kbd>: open actions menu for the focused tree item
|
||||||
- <kbd>Ctrl+R</kbd>: toggle recording
|
- <kbd>Ctrl+R</kbd>: toggle recording
|
||||||
- <kbd>Ctrl+L</kbd>: clear chat log
|
- <kbd>Ctrl+L</kbd>: clear chat log
|
||||||
- <kbd>Tab</kbd>: toggle focus between chat and user tree
|
- <kbd>Tab</kbd>: toggle focus between chat and user tree
|
||||||
@@ -310,7 +311,9 @@ After running the command above, `barnard` will be compiled as `$(go env GOPATH)
|
|||||||
- <kbd>Page Down</kbd>: scroll chat down
|
- <kbd>Page Down</kbd>: scroll chat down
|
||||||
- <kbd>Home</kbd>: scroll chat to the top
|
- <kbd>Home</kbd>: scroll chat to the top
|
||||||
- <kbd>End</kbd>: scroll chat to the bottom
|
- <kbd>End</kbd>: scroll chat to the bottom
|
||||||
- <kbd>F10</kbd>: quit
|
- <kbd>Ctrl+Q</kbd>: quit
|
||||||
|
|
||||||
|
With the user/channel tree focused, Left/Right changes incoming volume and Backspace restores the focused user or channel to unmuted, 100% volume, and normal boost. In the message input, those keys retain their normal text-editing behavior.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+14
-2
@@ -253,6 +253,17 @@ func (b *Barnard) StopTransmission() {
|
|||||||
func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) {
|
func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchesVolumeResetKey(configured *uiterm.Key, pressed uiterm.Key) bool {
|
||||||
|
if configured == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pressed == *configured {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return (*configured == uiterm.KeyBackspace || *configured == uiterm.KeyBackspace2) &&
|
||||||
|
(pressed == uiterm.KeyBackspace || pressed == uiterm.KeyBackspace2)
|
||||||
|
}
|
||||||
|
|
||||||
func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, key uiterm.Key) {
|
func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, key uiterm.Key) {
|
||||||
treeItem := item.(TreeItem)
|
treeItem := item.(TreeItem)
|
||||||
if key == uiterm.KeyEnter {
|
if key == uiterm.KeyEnter {
|
||||||
@@ -310,8 +321,9 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm
|
|||||||
if key == *b.Hotkeys.VolumeUp {
|
if key == *b.Hotkeys.VolumeUp {
|
||||||
b.changeVolume(makeUsersArray(treeItem.Channel.Users), 0.1)
|
b.changeVolume(makeUsersArray(treeItem.Channel.Users), 0.1)
|
||||||
}
|
}
|
||||||
if key == *b.Hotkeys.VolumeReset {
|
if matchesVolumeResetKey(b.Hotkeys.VolumeReset, key) {
|
||||||
b.resetVolume(makeUsersArray(treeItem.Channel.Users))
|
b.resetVolume(makeUsersArray(treeItem.Channel.Users))
|
||||||
|
b.setChannelMuted(treeItem.Channel.ID, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +343,7 @@ func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm
|
|||||||
if key == *b.Hotkeys.VolumeUp {
|
if key == *b.Hotkeys.VolumeUp {
|
||||||
b.changeVolume([]*gumble.User{treeItem.User}, 0.1)
|
b.changeVolume([]*gumble.User{treeItem.User}, 0.1)
|
||||||
}
|
}
|
||||||
if key == *b.Hotkeys.VolumeReset {
|
if matchesVolumeResetKey(b.Hotkeys.VolumeReset, key) {
|
||||||
b.resetVolume([]*gumble.User{treeItem.User})
|
b.resetVolume([]*gumble.User{treeItem.User})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-10
@@ -95,12 +95,12 @@ func (c *Config) LoadConfig() {
|
|||||||
jc = exportableConfig{}
|
jc = exportableConfig{}
|
||||||
jc.Hotkeys = &Hotkeys{
|
jc.Hotkeys = &Hotkeys{
|
||||||
Talk: key(uiterm.KeyF1),
|
Talk: key(uiterm.KeyF1),
|
||||||
VolumeDown: key(uiterm.KeyF5),
|
VolumeDown: key(uiterm.KeyArrowLeft),
|
||||||
VolumeUp: key(uiterm.KeyF6),
|
VolumeUp: key(uiterm.KeyArrowRight),
|
||||||
VolumeReset: key(uiterm.KeyF8),
|
VolumeReset: key(uiterm.KeyBackspace),
|
||||||
MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey
|
MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey
|
||||||
RecordToggle: key(uiterm.KeyCtrlR),
|
RecordToggle: key(uiterm.KeyCtrlR),
|
||||||
Exit: key(uiterm.KeyF10),
|
Exit: key(uiterm.KeyCtrlQ),
|
||||||
ToggleTimestamps: key(uiterm.KeyF3),
|
ToggleTimestamps: key(uiterm.KeyF3),
|
||||||
SwitchViews: key(uiterm.KeyTab),
|
SwitchViews: key(uiterm.KeyTab),
|
||||||
ClearOutput: key(uiterm.KeyCtrlL),
|
ClearOutput: key(uiterm.KeyCtrlL),
|
||||||
@@ -108,7 +108,7 @@ func (c *Config) LoadConfig() {
|
|||||||
ScrollDown: key(uiterm.KeyPgdn),
|
ScrollDown: key(uiterm.KeyPgdn),
|
||||||
ScrollToTop: key(uiterm.KeyHome),
|
ScrollToTop: key(uiterm.KeyHome),
|
||||||
ScrollToBottom: key(uiterm.KeyEnd),
|
ScrollToBottom: key(uiterm.KeyEnd),
|
||||||
AdminMenu: key(uiterm.KeyF11),
|
AdminMenu: key(uiterm.KeyF10),
|
||||||
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
||||||
AGCToggle: key(uiterm.KeyF12),
|
AGCToggle: key(uiterm.KeyF12),
|
||||||
}
|
}
|
||||||
@@ -182,12 +182,12 @@ func (c *Config) ensureHotkeys() {
|
|||||||
}
|
}
|
||||||
defaults := Hotkeys{
|
defaults := Hotkeys{
|
||||||
Talk: key(uiterm.KeyF1),
|
Talk: key(uiterm.KeyF1),
|
||||||
VolumeDown: key(uiterm.KeyF5),
|
VolumeDown: key(uiterm.KeyArrowLeft),
|
||||||
VolumeUp: key(uiterm.KeyF6),
|
VolumeUp: key(uiterm.KeyArrowRight),
|
||||||
VolumeReset: key(uiterm.KeyF8),
|
VolumeReset: key(uiterm.KeyBackspace),
|
||||||
MuteToggle: key(uiterm.KeyF7),
|
MuteToggle: key(uiterm.KeyF7),
|
||||||
RecordToggle: key(uiterm.KeyCtrlR),
|
RecordToggle: key(uiterm.KeyCtrlR),
|
||||||
Exit: key(uiterm.KeyF10),
|
Exit: key(uiterm.KeyCtrlQ),
|
||||||
ToggleTimestamps: key(uiterm.KeyF3),
|
ToggleTimestamps: key(uiterm.KeyF3),
|
||||||
SwitchViews: key(uiterm.KeyTab),
|
SwitchViews: key(uiterm.KeyTab),
|
||||||
ClearOutput: key(uiterm.KeyCtrlL),
|
ClearOutput: key(uiterm.KeyCtrlL),
|
||||||
@@ -195,7 +195,7 @@ func (c *Config) ensureHotkeys() {
|
|||||||
ScrollDown: key(uiterm.KeyPgdn),
|
ScrollDown: key(uiterm.KeyPgdn),
|
||||||
ScrollToTop: key(uiterm.KeyHome),
|
ScrollToTop: key(uiterm.KeyHome),
|
||||||
ScrollToBottom: key(uiterm.KeyEnd),
|
ScrollToBottom: key(uiterm.KeyEnd),
|
||||||
AdminMenu: key(uiterm.KeyF11),
|
AdminMenu: key(uiterm.KeyF10),
|
||||||
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
NoiseSuppressionToggle: key(uiterm.KeyF9),
|
||||||
AGCToggle: key(uiterm.KeyF12),
|
AGCToggle: key(uiterm.KeyF12),
|
||||||
}
|
}
|
||||||
@@ -309,6 +309,26 @@ func (c *Config) ToggleMute(u *gumble.User) error {
|
|||||||
return c.saveConfigLocked()
|
return c.saveConfigLocked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetUserAudio restores and saves the default incoming audio settings for
|
||||||
|
// each user on the given server.
|
||||||
|
func (c *Config) ResetUserAudio(address string, users []*gumble.User) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
for _, u := range users {
|
||||||
|
if u == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
storedUser := c.findUser(address, u.Name)
|
||||||
|
storedUser.Boost = 1
|
||||||
|
storedUser.Volume = 1.0
|
||||||
|
storedUser.LocallyMuted = false
|
||||||
|
u.SetBoost(1)
|
||||||
|
u.SetVolume(1.0)
|
||||||
|
u.SetLocallyMuted(false)
|
||||||
|
}
|
||||||
|
return c.saveConfigLocked()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Config) SetMicVolume(v float32) {
|
func (c *Config) SetMicVolume(v float32) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||||
"git.stormux.org/storm/barnard/uiterm"
|
"git.stormux.org/storm/barnard/uiterm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,8 +52,8 @@ func TestConfigBackfillsRecordingDefaults(t *testing.T) {
|
|||||||
if cfg.GetHotkeys().AdminMenu == nil {
|
if cfg.GetHotkeys().AdminMenu == nil {
|
||||||
t.Fatal("expected admin menu hotkey to be backfilled")
|
t.Fatal("expected admin menu hotkey to be backfilled")
|
||||||
}
|
}
|
||||||
if got := *cfg.GetHotkeys().AdminMenu; got != uiterm.KeyF11 {
|
if got := *cfg.GetHotkeys().AdminMenu; got != uiterm.KeyF10 {
|
||||||
t.Fatalf("expected admin menu f11, got %s", got)
|
t.Fatalf("expected admin menu f10, got %s", got)
|
||||||
}
|
}
|
||||||
for name, got := range map[string]*uiterm.Key{
|
for name, got := range map[string]*uiterm.Key{
|
||||||
"clear output": cfg.GetHotkeys().ClearOutput,
|
"clear output": cfg.GetHotkeys().ClearOutput,
|
||||||
@@ -74,6 +75,66 @@ func TestConfigBackfillsRecordingDefaults(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewHotkeyDefaults(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||||
|
cfg := NewConfig(&configPath)
|
||||||
|
if err := cfg.SaveConfig(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hotkeys := NewConfig(&configPath).GetHotkeys()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
got *uiterm.Key
|
||||||
|
want uiterm.Key
|
||||||
|
}{
|
||||||
|
{name: "volume down", got: hotkeys.VolumeDown, want: uiterm.KeyArrowLeft},
|
||||||
|
{name: "volume up", got: hotkeys.VolumeUp, want: uiterm.KeyArrowRight},
|
||||||
|
{name: "volume reset", got: hotkeys.VolumeReset, want: uiterm.KeyBackspace},
|
||||||
|
{name: "actions menu", got: hotkeys.AdminMenu, want: uiterm.KeyF10},
|
||||||
|
{name: "exit", got: hotkeys.Exit, want: uiterm.KeyCtrlQ},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
if tt.got == nil || *tt.got != tt.want {
|
||||||
|
t.Errorf("%s default = %v, want %s", tt.name, tt.got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExplicitLegacyHotkeysRemainConfigured(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||||
|
data := []byte("[hotkeys]\nvolumedown = \"f5\"\nvolumeup = \"f6\"\nvolumereset = \"f8\"\nadminmenu = \"f11\"\nexit = \"f10\"\n")
|
||||||
|
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hotkeys := NewConfig(&configPath).GetHotkeys()
|
||||||
|
if *hotkeys.VolumeDown != uiterm.KeyF5 || *hotkeys.VolumeUp != uiterm.KeyF6 ||
|
||||||
|
*hotkeys.VolumeReset != uiterm.KeyF8 || *hotkeys.AdminMenu != uiterm.KeyF11 ||
|
||||||
|
*hotkeys.Exit != uiterm.KeyF10 {
|
||||||
|
t.Fatalf("explicit legacy hotkeys were replaced: %+v", hotkeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetUserAudioRestoresAndPersistsDefaults(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||||
|
cfg := NewConfig(&configPath)
|
||||||
|
user := &gumble.User{Name: "Username"}
|
||||||
|
user.SetBoost(4)
|
||||||
|
user.SetVolume(0.4)
|
||||||
|
user.SetLocallyMuted(true)
|
||||||
|
|
||||||
|
if err := cfg.ResetUserAudio("example.test:64738", []*gumble.User{user}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if user.Boost() != 1 || user.Volume() != 1.0 || user.LocallyMuted() {
|
||||||
|
t.Fatalf("runtime audio was not reset: boost=%d volume=%v muted=%v", user.Boost(), user.Volume(), user.LocallyMuted())
|
||||||
|
}
|
||||||
|
storedUser := cfg.findUser("example.test:64738", user.Name)
|
||||||
|
if storedUser.Boost != 1 || storedUser.Volume != 1.0 || storedUser.LocallyMuted {
|
||||||
|
t.Fatalf("saved audio was not reset: %+v", storedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAGCDefaultsOnAndPersists(t *testing.T) {
|
func TestAGCDefaultsOnAndPersists(t *testing.T) {
|
||||||
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
configPath := filepath.Join(t.TempDir(), "barnard.toml")
|
||||||
if err := os.WriteFile(configPath, []byte("[hotkeys]\ntalk = \"f1\"\n"), 0600); err != nil {
|
if err := os.WriteFile(configPath, []byte("[hotkeys]\ntalk = \"f1\"\n"), 0600); err != nil {
|
||||||
|
|||||||
+9
-11
@@ -69,22 +69,20 @@ func (b *Barnard) changeVolume(users []*gumble.User, change float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Barnard) resetVolume(users []*gumble.User) {
|
func (b *Barnard) resetVolume(users []*gumble.User) {
|
||||||
changed := b.withStream(func(stream *gumbleopenal.Stream) {
|
if b.Client == nil || b.Client.Config == nil {
|
||||||
for _, u := range users {
|
return
|
||||||
// Reset to original volume (1.0) and boost (1)
|
|
||||||
u.SetBoost(uint16(1))
|
|
||||||
u.SetVolume(1.0)
|
|
||||||
stream.UpdateUserGain(u)
|
|
||||||
b.UserConfig.UpdateConfig(u)
|
|
||||||
}
|
}
|
||||||
if err := b.UserConfig.SaveConfig(); err != nil {
|
if err := b.UserConfig.ResetUserAudio(b.Client.Config.Address, users); err != nil {
|
||||||
b.AddOutputLine("Volume: could not save setting: " + err.Error())
|
b.AddOutputLine("Volume: could not save reset: " + err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.withStream(func(stream *gumbleopenal.Stream) {
|
||||||
|
for _, u := range users {
|
||||||
|
stream.UpdateUserGain(u)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if changed {
|
|
||||||
b.refreshVolumeDisplay()
|
b.refreshVolumeDisplay()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Tree items render a display string snapshotted at build time, so a volume
|
// Tree items render a display string snapshotted at build time, so a volume
|
||||||
// change is only visible after the tree is rebuilt.
|
// change is only visible after the tree is rebuilt.
|
||||||
|
|||||||
@@ -4,8 +4,22 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.stormux.org/storm/barnard/gumble/gumble"
|
"git.stormux.org/storm/barnard/gumble/gumble"
|
||||||
|
"git.stormux.org/storm/barnard/uiterm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMatchesVolumeResetKeyAcceptsBothTerminalBackspaces(t *testing.T) {
|
||||||
|
configured := uiterm.KeyBackspace
|
||||||
|
if !matchesVolumeResetKey(&configured, uiterm.KeyBackspace) {
|
||||||
|
t.Fatal("configured backspace did not match itself")
|
||||||
|
}
|
||||||
|
if !matchesVolumeResetKey(&configured, uiterm.KeyBackspace2) {
|
||||||
|
t.Fatal("configured backspace did not match delete/127 representation")
|
||||||
|
}
|
||||||
|
if matchesVolumeResetKey(&configured, uiterm.KeyDelete) {
|
||||||
|
t.Fatal("delete key unexpectedly matched volume reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Regression: rebuilding the channel tree ranged protocol-owned maps without
|
// Regression: rebuilding the channel tree ranged protocol-owned maps without
|
||||||
// Client.Do while TCP handlers could add or remove users/channels.
|
// Client.Do while TCP handlers could add or remove users/channels.
|
||||||
func TestTreeItemBuildReadsMapsUnderClientSnapshot(t *testing.T) {
|
func TestTreeItemBuildReadsMapsUnderClientSnapshot(t *testing.T) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package uiterm
|
package uiterm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -15,6 +16,26 @@ func (i *cyclicItem) TreeItemStyle(fg, bg Attribute, active bool) (Attribute, At
|
|||||||
return fg, bg
|
return fg, bg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreePassesLeftRightAndBackspaceToFocusedItem(t *testing.T) {
|
||||||
|
item := &cyclicItem{name: "user"}
|
||||||
|
var received []Key
|
||||||
|
tree := Tree{
|
||||||
|
KeyListener: func(_ *Ui, _ *Tree, _ TreeItem, key Key) {
|
||||||
|
received = append(received, key)
|
||||||
|
},
|
||||||
|
lines: []renderedTreeItem{{Item: item}},
|
||||||
|
}
|
||||||
|
tree.uiInitialize(New(nil))
|
||||||
|
|
||||||
|
for _, key := range []Key{KeyArrowLeft, KeyArrowRight, KeyBackspace, KeyBackspace2} {
|
||||||
|
tree.uiKeyEvent(key)
|
||||||
|
}
|
||||||
|
want := []Key{KeyArrowLeft, KeyArrowRight, KeyBackspace, KeyBackspace2}
|
||||||
|
if !reflect.DeepEqual(received, want) {
|
||||||
|
t.Fatalf("tree keys = %v, want %v", received, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Regression: rebuild_rec followed parent/child links with no depth limit, so
|
// Regression: rebuild_rec followed parent/child links with no depth limit, so
|
||||||
// a cyclic channel graph recursed until the process ran out of memory. A
|
// a cyclic channel graph recursed until the process ran out of memory. A
|
||||||
// rebuild must now terminate and stay bounded.
|
// rebuild must now terminate and stay bounded.
|
||||||
|
|||||||
Reference in New Issue
Block a user