142 lines
4.6 KiB
Go
142 lines
4.6 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/notification"
|
|
"git.stormux.org/storm/cthulhu/internal/notificationui"
|
|
)
|
|
|
|
func TestNotificationListSnapshotMatchesReferenceLayout(t *testing.T) {
|
|
presenter, controller, speech := notificationListFixture(t)
|
|
worker := newNotificationListWorker(nil, presenter, speech)
|
|
response := worker.snapshot("")
|
|
|
|
if response.Title != "2 notifications" {
|
|
t.Fatalf("title = %q, want %q", response.Title, "2 notifications")
|
|
}
|
|
if response.Selected != "1" {
|
|
t.Fatalf("selected = %q, want %q", response.Selected, "1")
|
|
}
|
|
if len(response.Entries) != 2 {
|
|
t.Fatalf("entry count = %d, want 2", len(response.Entries))
|
|
}
|
|
latest := response.Entries[0]
|
|
if latest.Message != "Live message" || latest.Received != "0 seconds ago" || !latest.CanControl {
|
|
t.Fatalf("latest entry = %+v", latest)
|
|
}
|
|
if len(latest.Actions) != 2 || latest.Actions[0].Key != "default" || latest.Actions[1].Key != "reply" {
|
|
t.Fatalf("actions = %+v, want sorted default and reply", latest.Actions)
|
|
}
|
|
if response.Entries[1].CanControl {
|
|
t.Fatalf("older generic entry unexpectedly controllable: %+v", response.Entries[1])
|
|
}
|
|
if len(controller.dismissed) != 0 || len(controller.invoked) != 0 {
|
|
t.Fatalf("snapshot unexpectedly changed controller state: %+v", controller)
|
|
}
|
|
}
|
|
|
|
func TestNotificationListActionsMatchReferenceBehavior(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
request notificationui.Request
|
|
wantClose bool
|
|
wantSpeech string
|
|
}{
|
|
{
|
|
name: "copy",
|
|
request: notificationui.Request{Copy: true},
|
|
wantSpeech: messageClipboardCopiedFull,
|
|
},
|
|
{
|
|
name: "dismiss",
|
|
request: notificationui.Request{Dismiss: true, Selection: "1", Row: intPointer(0)},
|
|
wantSpeech: notification.MessageDismissed,
|
|
},
|
|
{
|
|
name: "invoke action",
|
|
request: notificationui.Request{Action: "reply", Selection: "1", Row: intPointer(0)},
|
|
wantSpeech: notification.MessageActionInvoked,
|
|
},
|
|
{
|
|
name: "clear",
|
|
request: notificationui.Request{Clear: true},
|
|
wantClose: true,
|
|
wantSpeech: notification.MessageNoMessages,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
presenter, controller, speech := notificationListFixture(t)
|
|
worker := newNotificationListWorker(nil, presenter, speech)
|
|
response, close := worker.handle(test.request)
|
|
if close != test.wantClose {
|
|
t.Fatalf("close = %v, want %v", close, test.wantClose)
|
|
}
|
|
select {
|
|
case message := <-speech.spoken:
|
|
if message != test.wantSpeech {
|
|
t.Fatalf("speech = %q, want %q", message, test.wantSpeech)
|
|
}
|
|
default:
|
|
t.Fatal("no speech was presented")
|
|
}
|
|
if test.request.Clear && len(presenter.Entries()) != 0 {
|
|
t.Fatalf("history length after clear = %d, want 0", len(presenter.Entries()))
|
|
}
|
|
if test.request.Dismiss && len(presenter.Entries()) != 1 {
|
|
t.Fatalf("history length after dismiss = %d, want 1", len(presenter.Entries()))
|
|
}
|
|
if test.request.Action != "" && controller.invoked[0].key != "reply" {
|
|
t.Fatalf("invoked = %+v, want reply", controller.invoked)
|
|
}
|
|
if response.Selected == "" && !test.wantClose {
|
|
t.Fatalf("selected = %q, want a live selection", response.Selected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func notificationListFixture(t *testing.T) (*notification.Presenter, *notificationListController, *fakeSpeechQueue) {
|
|
t.Helper()
|
|
now := time.Unix(1_800_000_000, 0)
|
|
presenter := notification.NewPresenterAt(func() time.Time { return now })
|
|
controller := ¬ificationListController{}
|
|
presenter.SetMakoController(controller)
|
|
presenter.Save("Older message")
|
|
identifier := 42
|
|
presenter.Save("Live message", notification.SaveOptions{
|
|
Source: notification.MakoSource, NotificationID: &identifier, Live: true,
|
|
Actions: map[string]string{"reply": "Reply", "default": "View"},
|
|
})
|
|
speech := &fakeSpeechQueue{spoken: make(chan string, 4)}
|
|
return presenter, controller, speech
|
|
}
|
|
|
|
func intPointer(value int) *int { return &value }
|
|
|
|
type notificationListAction struct {
|
|
id int
|
|
key string
|
|
}
|
|
|
|
type notificationListController struct {
|
|
dismissed []int
|
|
invoked []notificationListAction
|
|
}
|
|
|
|
func (controller *notificationListController) IsCurrent(notification.Entry) bool { return true }
|
|
func (controller *notificationListController) Dismiss(id int) bool {
|
|
controller.dismissed = append(controller.dismissed, id)
|
|
return true
|
|
}
|
|
func (controller *notificationListController) InvokeAction(id int, key string) bool {
|
|
controller.invoked = append(controller.invoked, notificationListAction{id: id, key: key})
|
|
return true
|
|
}
|