fix(chat): expire typing indicators by deadline instead of timestamp equality
On Windows the coarse clock can return the same time for two addTyper calls, so a stale expiry matched the extended indicator and removed it early. Compare the stored deadline against the current time instead.
This commit is contained in:
parent
25bc35212e
commit
f45dfd58ad
2 changed files with 8 additions and 9 deletions
|
|
@ -257,7 +257,7 @@ func (m *Model) Update(msg tview.Msg) tview.Cmd {
|
||||||
case messageslist.Msg:
|
case messageslist.Msg:
|
||||||
return m.messagesList.Update(msg)
|
return m.messagesList.Update(msg)
|
||||||
case typingExpiredMsg:
|
case typingExpiredMsg:
|
||||||
if m.typers[msg.userID] == msg.until {
|
if until, ok := m.typers[msg.userID]; ok && !time.Now().Before(until) {
|
||||||
m.removeTyper(msg.userID)
|
m.removeTyper(msg.userID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -421,10 +421,9 @@ func (m *Model) View() tview.Element {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// typingExpiredMsg ends a user's typing indicator unless they typed again after it was set to expire at until.
|
// typingExpiredMsg ends a user's typing indicator unless they typed again and extended it.
|
||||||
type typingExpiredMsg struct {
|
type typingExpiredMsg struct {
|
||||||
userID discord.UserID
|
userID discord.UserID
|
||||||
until time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) clearTypers() {
|
func (m *Model) clearTypers() {
|
||||||
|
|
@ -434,12 +433,11 @@ func (m *Model) clearTypers() {
|
||||||
|
|
||||||
// addTyper shows userID as typing and returns a command that ends it after the typing duration.
|
// addTyper shows userID as typing and returns a command that ends it after the typing duration.
|
||||||
func (m *Model) addTyper(userID discord.UserID) tview.Cmd {
|
func (m *Model) addTyper(userID discord.UserID) tview.Cmd {
|
||||||
until := time.Now().Add(composer.TypingDuration)
|
m.typers[userID] = time.Now().Add(composer.TypingDuration)
|
||||||
m.typers[userID] = until
|
|
||||||
m.updateFooter()
|
m.updateFooter()
|
||||||
return func() tview.Msg {
|
return func() tview.Msg {
|
||||||
time.Sleep(composer.TypingDuration)
|
time.Sleep(composer.TypingDuration)
|
||||||
return typingExpiredMsg{userID, until}
|
return typingExpiredMsg{userID}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package chat
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ayn2op/arikawa/v3/discord"
|
"github.com/ayn2op/arikawa/v3/discord"
|
||||||
"github.com/ayn2op/arikawa/v3/gateway"
|
"github.com/ayn2op/arikawa/v3/gateway"
|
||||||
|
|
@ -41,13 +42,13 @@ func TestModelUpdate(t *testing.T) {
|
||||||
t.Run("typing expires unless extended", func(t *testing.T) {
|
t.Run("typing expires unless extended", func(t *testing.T) {
|
||||||
m := newTestModel(t)
|
m := newTestModel(t)
|
||||||
m.addTyper(1)
|
m.addTyper(1)
|
||||||
first := m.typers[1]
|
|
||||||
m.addTyper(1)
|
m.addTyper(1)
|
||||||
m.Update(typingExpiredMsg{userID: 1, until: first})
|
m.Update(typingExpiredMsg{userID: 1})
|
||||||
if _, ok := m.typers[1]; !ok {
|
if _, ok := m.typers[1]; !ok {
|
||||||
t.Fatal("an extended typing indicator expired early")
|
t.Fatal("an extended typing indicator expired early")
|
||||||
}
|
}
|
||||||
m.Update(typingExpiredMsg{userID: 1, until: m.typers[1]})
|
m.typers[1] = time.Now().Add(-time.Second)
|
||||||
|
m.Update(typingExpiredMsg{userID: 1})
|
||||||
if _, ok := m.typers[1]; ok {
|
if _, ok := m.typers[1]; ok {
|
||||||
t.Fatal("the typing indicator did not expire")
|
t.Fatal("the typing indicator did not expire")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue