194 lines
5.6 KiB
Go
194 lines
5.6 KiB
Go
package mujica
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
)
|
|
|
|
func toastIDs(q *toastQueue, visible bool) []string {
|
|
var ids []string
|
|
for _, it := range q.items {
|
|
if it.visible == visible {
|
|
ids = append(ids, it.msg.ID)
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func TestToastQueueLimitsVisible(t *testing.T) {
|
|
var q toastQueue
|
|
t0 := time.Unix(0, 0)
|
|
for _, id := range []string{"a", "b", "c", "d", "e"} {
|
|
q.push(ToastMessage{ID: id, Message: id}, t0)
|
|
}
|
|
if v, w := toastIDs(&q, true), toastIDs(&q, false); !slices.Equal(v, []string{"a", "b", "c"}) || !slices.Equal(w, []string{"d", "e"}) {
|
|
t.Fatalf("visible %v waiting %v", v, w)
|
|
}
|
|
// d starts its 5 s only when it shows.
|
|
if next := q.expire(t0.Add(ToastDuration)); next != ToastDuration {
|
|
t.Fatalf("next wake %v", next)
|
|
}
|
|
if v := toastIDs(&q, true); !slices.Equal(v, []string{"d", "e"}) {
|
|
t.Fatalf("after expiry visible %v", v)
|
|
}
|
|
q.expire(t0.Add(2*ToastDuration - time.Millisecond))
|
|
if len(q.items) != 2 {
|
|
t.Fatalf("expired early: %v", toastIDs(&q, true))
|
|
}
|
|
q.expire(t0.Add(2 * ToastDuration))
|
|
if len(q.items) != 0 {
|
|
t.Fatalf("left %v", toastIDs(&q, true))
|
|
}
|
|
}
|
|
|
|
func TestToastQueueDedupesByID(t *testing.T) {
|
|
var q toastQueue
|
|
t0 := time.Unix(0, 0)
|
|
q.push(ToastMessage{ID: "sync", Message: "Syncing 1 file"}, t0)
|
|
q.push(ToastMessage{Message: "Saved"}, t0)
|
|
q.push(ToastMessage{ID: "sync", Message: "Syncing 2 files"}, t0.Add(4*time.Second))
|
|
q.push(ToastMessage{Message: "Saved"}, t0.Add(4*time.Second))
|
|
if len(q.items) != 2 || q.items[0].msg.Message != "Syncing 2 files" || q.items[1].msg.ID != "Saved" {
|
|
t.Fatalf("items %+v", q.items)
|
|
}
|
|
// The replaced toast restarted its time.
|
|
q.expire(t0.Add(6 * time.Second))
|
|
if len(q.items) != 2 {
|
|
t.Fatalf("replacement kept the old time: %d left", len(q.items))
|
|
}
|
|
}
|
|
|
|
func TestToastQueueErrorsStayAndPauseHolds(t *testing.T) {
|
|
var q toastQueue
|
|
t0 := time.Unix(0, 0)
|
|
q.push(ToastMessage{ID: "e", Severity: SeverityError, Message: "Failed"}, t0)
|
|
q.push(ToastMessage{ID: "w", Severity: SeverityWarning, Message: "Slow", Action: "Details"}, t0)
|
|
q.pause("w", true, t0.Add(4*time.Second))
|
|
if next := q.expire(t0.Add(time.Hour)); next != 0 {
|
|
t.Fatalf("paused toast still counts down: %v", next)
|
|
}
|
|
if !slices.Equal(toastIDs(&q, true), []string{"e", "w"}) {
|
|
t.Fatalf("visible %v", toastIDs(&q, true))
|
|
}
|
|
q.pause("w", false, t0.Add(time.Hour))
|
|
if next := q.expire(t0.Add(time.Hour)); next != time.Second {
|
|
t.Fatalf("resumed toast left %v, want 1s", next)
|
|
}
|
|
q.expire(t0.Add(time.Hour + time.Second))
|
|
if !slices.Equal(toastIDs(&q, true), []string{"e"}) {
|
|
t.Fatalf("visible %v", toastIDs(&q, true))
|
|
}
|
|
}
|
|
|
|
func TestToastQueuePanics(t *testing.T) {
|
|
for _, m := range []ToastMessage{{}, {Message: "x", Duration: -1}} {
|
|
func() {
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Errorf("%+v: no panic", m)
|
|
}
|
|
}()
|
|
var q toastQueue
|
|
q.push(m, time.Now())
|
|
}()
|
|
}
|
|
}
|
|
|
|
func toastTester(m ToastMessage, res *ToasterResult) *ui.Tester {
|
|
return ui.NewTester(func(c *ui.Context) {
|
|
Use(c, Settings{})
|
|
if Button(c, "Notify", ButtonOptions{}).Clicked() {
|
|
ShowToast(c, m)
|
|
}
|
|
if r := Toaster(c); r != (ToasterResult{}) {
|
|
*res = r
|
|
}
|
|
}, 600, 400)
|
|
}
|
|
|
|
func TestToastActionAndClose(t *testing.T) {
|
|
var res ToasterResult
|
|
tt := toastTester(ToastMessage{ID: "del", Message: "Note deleted", Action: "Undo"}, &res)
|
|
if err := tt.Click("Notify"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !tt.HasText("Note deleted") {
|
|
t.Fatal("toast not shown")
|
|
}
|
|
if err := tt.Click("Undo"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Action != "del" || tt.HasText("Note deleted") {
|
|
t.Fatalf("after undo %+v, still shown %v", res, tt.HasText("Note deleted"))
|
|
}
|
|
res = ToasterResult{}
|
|
_ = tt.Click("Notify")
|
|
if err := tt.Click("Dismiss notification"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Closed != "del" || tt.HasText("Note deleted") {
|
|
t.Fatalf("after close %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestToastKeepsFocusAndEscapeCloses(t *testing.T) {
|
|
var res ToasterResult
|
|
tt := toastTester(ToastMessage{ID: "s", Message: "Saved"}, &res)
|
|
tt.Key(0, ui.KeyTab)
|
|
tt.Key(0, ui.KeyEnter)
|
|
if !tt.HasText("Saved") || !tt.Focused("Notify") {
|
|
t.Fatalf("shown %v, focus kept %v", tt.HasText("Saved"), tt.Focused("Notify"))
|
|
}
|
|
if a := tt.Announcements(); !slices.Contains(a, "Saved") {
|
|
t.Fatalf("announcements %q", a)
|
|
}
|
|
tt.Key(0, ui.KeyTab)
|
|
if !tt.Focused("Dismiss notification") {
|
|
t.Fatal("Tab does not reach the toast")
|
|
}
|
|
tt.Key(0, ui.KeyEscape)
|
|
if res.Closed != "s" || tt.HasText("Saved") {
|
|
t.Fatalf("Escape: %+v", res)
|
|
}
|
|
}
|
|
|
|
func TestToastExpiresAndHoverPauses(t *testing.T) {
|
|
var res ToasterResult
|
|
tt := toastTester(ToastMessage{Message: "Copied", Duration: 40 * time.Millisecond}, &res)
|
|
_ = tt.Click("Notify")
|
|
time.Sleep(60 * time.Millisecond)
|
|
tt.Frame()
|
|
if tt.HasText("Copied") {
|
|
t.Fatal("toast outlived its duration")
|
|
}
|
|
_ = tt.Click("Notify")
|
|
r, _ := tt.Find("Copied")
|
|
tt.Move(r.X+r.W/2, r.Y+r.H/2)
|
|
time.Sleep(60 * time.Millisecond)
|
|
tt.Frame()
|
|
if !tt.HasText("Copied") {
|
|
t.Fatal("hovered toast expired")
|
|
}
|
|
tt.Move(5, 395)
|
|
time.Sleep(60 * time.Millisecond)
|
|
tt.Frame()
|
|
if tt.HasText("Copied") {
|
|
t.Fatal("toast stayed after the pointer left")
|
|
}
|
|
}
|
|
|
|
func TestToastSnapshot(t *testing.T) {
|
|
snapshot(t, "toast", 480, 300, func(c *ui.Context) {
|
|
if len(toasts(c).items) == 0 {
|
|
ShowToast(c, ToastMessage{ID: "1", Message: "Settings saved", Severity: SeveritySuccess})
|
|
ShowToast(c, ToastMessage{ID: "2", Message: "Note moved to trash", Action: "Undo"})
|
|
ShowToast(c, ToastMessage{ID: "3", Message: "Upload failed: network lost", Severity: SeverityError})
|
|
ShowToast(c, ToastMessage{ID: "4", Message: "Queued and not shown"})
|
|
}
|
|
Toaster(c)
|
|
})
|
|
}
|