Files
soft_quay/app-modern/ui/gio/icon_events_test.go
T

265 lines
6.9 KiB
Go
Raw Normal View History

package gio
import (
"context"
"errors"
"image"
"strings"
"testing"
"time"
"softbox.local/core/application"
)
func TestIconEventRelayAppliesOnlyDuringUIDrain(t *testing.T) {
reference := testIconReference("11")
shell := NewAppShell("Test", application.CatalogListItem{
ID: "app-one",
Name: "One",
IconRef: reference,
})
identity := testIconIdentity(t, "request-one", "app-one", reference, 96)
if err := shell.ExpectIcon(identity); err != nil {
t.Fatal(err)
}
ready, err := application.NewIconReadyEvent(
identity,
image.NewNRGBA(image.Rect(0, 0, 24, 24)),
)
if err != nil {
t.Fatal(err)
}
relay, err := application.NewEventRelay(1)
if err != nil {
t.Fatal(err)
}
submitted := make(chan error, 1)
go func() {
submitted <- relay.Submit(context.Background(), ready)
}()
if err := waitIconSubmit(submitted); err != nil {
t.Fatalf("Submit() error = %v", err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("background relay changed shell before UI drain")
}
if err := relay.Drain(shell.ApplyEvent); err != nil {
t.Fatalf("Drain() error = %v", err)
}
icon, exists := shell.icons["app-one"]
if !exists || icon.Size() != image.Pt(24, 24) {
t.Fatalf("applied icon = (%t, %v)", exists, icon.Size())
}
}
func TestAppShellAcceptsOnlyLatestIconRequest(t *testing.T) {
reference := testIconReference("22")
shell := NewAppShell("Test", application.CatalogListItem{
ID: "app-one",
Name: "One",
IconRef: reference,
})
oldIdentity := testIconIdentity(t, "request-old", "app-one", reference, 96)
newIdentity := testIconIdentity(t, "request-new", "app-one", reference, 96)
if err := shell.ExpectIcon(oldIdentity); err != nil {
t.Fatal(err)
}
if err := shell.ExpectIcon(newIdentity); err != nil {
t.Fatal(err)
}
oldReady, err := application.NewIconReadyEvent(
oldIdentity,
image.NewNRGBA(image.Rect(0, 0, 12, 12)),
)
if err != nil {
t.Fatal(err)
}
if err := shell.ApplyEvent(oldReady); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("stale request inserted an icon")
}
newReady, err := application.NewIconReadyEvent(
newIdentity,
image.NewNRGBA(image.Rect(0, 0, 30, 30)),
)
if err != nil {
t.Fatal(err)
}
if err := shell.ApplyEvent(newReady); err != nil {
t.Fatal(err)
}
if got := shell.icons["app-one"].Size(); got != image.Pt(30, 30) {
t.Fatalf("latest icon size = %v", got)
}
retryIdentity := testIconIdentity(t, "request-retry", "app-one", reference, 96)
if err := shell.ExpectIcon(retryIdentity); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; !exists {
t.Fatal("same-resource retry discarded an already valid icon")
}
failed, err := application.NewIconFailedEvent(
retryIdentity,
application.IconFailureUnavailable,
)
if err != nil {
t.Fatal(err)
}
if err := shell.ApplyEvent(failed); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; !exists {
t.Fatal("matching failure discarded an already valid icon")
}
if failure, exists := shell.IconFailure("app-one"); !exists || failure != application.IconFailureUnavailable {
t.Fatalf("IconFailure() = (%q, %t)", failure, exists)
}
dpiIdentity := testIconIdentity(t, "request-dpi", "app-one", reference, 144)
if err := shell.ExpectIcon(dpiIdentity); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("different-DPI request retained an unmatching image")
}
dpiFailed, err := application.NewIconFailedEvent(
dpiIdentity,
application.IconFailureUnavailable,
)
if err != nil {
t.Fatal(err)
}
if err := shell.ApplyEvent(dpiFailed); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("different-DPI failure restored an unmatching image")
}
}
func TestAppShellDropsChangedRemovedAndCanceledIconResults(t *testing.T) {
oldReference := testIconReference("33")
newReference := testIconReference("44")
shell := NewAppShell("Test", application.CatalogListItem{
ID: "app-one",
Name: "One",
IconRef: oldReference,
})
oldIdentity := testIconIdentity(t, "request-old", "app-one", oldReference, 96)
if err := shell.ExpectIcon(oldIdentity); err != nil {
t.Fatal(err)
}
oldReady, err := application.NewIconReadyEvent(
oldIdentity,
image.NewNRGBA(image.Rect(0, 0, 20, 20)),
)
if err != nil {
t.Fatal(err)
}
if err := shell.ApplyEvent(oldReady); err != nil {
t.Fatal(err)
}
shell.SetItems([]application.CatalogListItem{{
ID: "app-one",
Name: "One",
IconRef: newReference,
}})
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("IconRef change retained the previous image")
}
if err := shell.ApplyEvent(oldReady); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("old IconRef result was reinserted")
}
newIdentity := testIconIdentity(t, "request-new", "app-one", newReference, 96)
if err := shell.ExpectIcon(newIdentity); err != nil {
t.Fatal(err)
}
if !shell.CancelIconRequest("app-one", newIdentity.RequestID) {
t.Fatal("CancelIconRequest() did not cancel the latest request")
}
newReady, err := application.NewIconReadyEvent(
newIdentity,
image.NewNRGBA(image.Rect(0, 0, 22, 22)),
)
if err != nil {
t.Fatal(err)
}
if err := shell.ApplyEvent(newReady); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("canceled result was applied")
}
shell.SetItems(nil)
if err := shell.ExpectIcon(newIdentity); !errors.Is(err, ErrIconRequestStale) {
t.Fatalf("ExpectIcon(removed app) error = %v", err)
}
if err := shell.ApplyEvent(newReady); err != nil {
t.Fatal(err)
}
if _, exists := shell.icons["app-one"]; exists {
t.Fatal("removed app was reinserted by a late result")
}
}
func TestAppShellRejectsMalformedIconEventAndIgnoresOtherEvents(t *testing.T) {
shell := NewAppShell("Test")
if err := shell.ApplyEvent(application.Event{Type: application.EventCatalogRefreshed}); err != nil {
t.Fatalf("ApplyEvent(non-icon) error = %v", err)
}
err := shell.ApplyEvent(application.Event{
Type: application.EventIconReady,
RequestID: "request",
AppID: "app-one",
Payload: "wrong",
})
if !errors.Is(err, application.ErrInvalidIconEvent) {
t.Fatalf("ApplyEvent(invalid payload) error = %v", err)
}
if len(shell.icons) != 0 {
t.Fatal("invalid payload polluted icon state")
}
}
func testIconReference(pair string) string {
return "sha256:" + strings.Repeat(pair, 32)
}
func testIconIdentity(
t *testing.T,
requestID string,
appID string,
reference string,
dpi int,
) application.IconEventIdentity {
t.Helper()
identity, err := application.NewIconEventIdentity(
requestID,
appID,
reference,
dpi,
)
if err != nil {
t.Fatal(err)
}
return identity
}
func waitIconSubmit(result <-chan error) error {
select {
case err := <-result:
return err
case <-time.After(2 * time.Second):
return errors.New("timed out waiting for icon relay")
}
}