// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package notification import ( "testing" "time" ) func TestPresenterSavesHistoryAndEnforcesMaximumSize(t *testing.T) { now := time.Unix(1_800_000_000, 0) presenter := NewPresenterAt(func() time.Time { return now }) for number := 0; number < historySize+2; number++ { presenter.Save(time.Unix(int64(number), 0).Format("2006")) now = now.Add(time.Second) } entries := presenter.Entries() if len(entries) != historySize { t.Fatalf("history length = %d, want %d", len(entries), historySize) } if entries[0].Source != SourceGeneric { t.Fatalf("first source = %q, want %q", entries[0].Source, SourceGeneric) } } func TestPresenterNavigationMatchesReferenceBoundaries(t *testing.T) { now := time.Unix(1_800_000_000, 0) presenter := NewPresenterAt(func() time.Time { return now }) presenter.Save("first") presenter.Save("second") assertMessages := func(t *testing.T, got []string, want ...string) { t.Helper() if len(got) != len(want) { t.Fatalf("message count = %d, want %d: %q", len(got), len(want), got) } for index := range want { if got[index] != want[index] { t.Fatalf("message %d = %q, want %q", index, got[index], want[index]) } } } t.Run("last", func(t *testing.T) { assertMessages(t, presenter.PresentLast(), "second 0 seconds ago") }) t.Run("previous then top", func(t *testing.T) { assertMessages(t, presenter.PresentPrevious(), "first 0 seconds ago") assertMessages(t, presenter.PresentPrevious(), MessageListTop, "first 0 seconds ago") }) t.Run("next then bottom", func(t *testing.T) { assertMessages(t, presenter.PresentNext(), "second 0 seconds ago") assertMessages(t, presenter.PresentNext(), MessageListBottom, "second 0 seconds ago") assertMessages(t, presenter.PresentNext(), MessageListBottom, "second 0 seconds ago") }) t.Run("empty", func(t *testing.T) { empty := NewPresenterAt(func() time.Time { return now }) assertMessages(t, empty.PresentLast(), MessageNoMessages) assertMessages(t, empty.PresentPrevious(), MessageNoMessages) assertMessages(t, empty.PresentNext(), MessageNoMessages) }) } func TestPresenterStartsAtLatestNotification(t *testing.T) { now := time.Unix(1_800_000_000, 0) presenter := NewPresenterAt(func() time.Time { return now }) presenter.Save("first") presenter.Save("second") assertMessages(t, presenter.PresentPrevious(), "first 0 seconds ago") } func TestPresenterRemovalKeepsHistoryNavigationValid(t *testing.T) { for _, removeAt := range []int{0, 1, 2} { t.Run(time.Duration(removeAt).String(), func(t *testing.T) { now := time.Unix(1_800_000_000, 0) presenter := NewPresenterAt(func() time.Time { return now }) entries := []Entry{presenter.Save("first"), presenter.Save("second"), presenter.Save("third")} presenter.PresentPrevious() presenter.PresentPrevious() if !presenter.Remove(entries[removeAt]) { t.Fatal("notification was not removed") } want := "first 0 seconds ago" if removeAt == 0 { want = "second 0 seconds ago" } assertMessages(t, presenter.PresentPrevious(), MessageListTop, want) presenter.PresentNext() }) } } func assertMessages(t *testing.T, got []string, want ...string) { t.Helper() if len(got) != len(want) { t.Fatalf("message count = %d, want %d: %q", len(got), len(want), got) } for index := range want { if got[index] != want[index] { t.Fatalf("message %d = %q, want %q", index, got[index], want[index]) } } } func TestRelativeTimeUsesReferenceRounding(t *testing.T) { now := time.Unix(1_800_000_000, 0) for _, test := range []struct { elapsed time.Duration want string }{ {90 * time.Second, "2 minutes ago"}, {150 * time.Second, "2 minutes ago"}, {89 * time.Second, "1 minute ago"}, {90 * time.Minute, "2 hours ago"}, {90 * time.Hour, "4 days ago"}, } { if got := relativeTime(now.Add(-test.elapsed), now); got != test.want { t.Fatalf("relativeTime(%s) = %q, want %q", test.elapsed, got, test.want) } } }