Files
cthulhu/internal/app/notification_list_test.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 := &notificationListController{}
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
}