package tray
import (
"strings"
"testing"
"time"
)
func TestPlainText(t *testing.T) {
for in, want := range map[string]string{
"": "",
"plain": "plain",
"bold text": "bold text",
"a
b": "a\nb",
"a
b
c": "a\nb\nc",
"
hi
": "hi",
"Tom & Jerry": "Tom & Jerry",
"<tag> "q" 's'": " \"q\" 's'",
"ABC": "ABC",
"": "",
"&unknown; &": "&unknown; &",
"1 < 2": "1 < 2",
"<x": "x",
"
z": "y'>z",
"\u00e9é": "éé",
} {
if got := plainText(in); got != want {
t.Errorf("plainText(%q) = %q, want %q", in, got, want)
}
}
long := plainText(strings.Repeat("é", 1000))
if len(long) > maxTipBytes || len(long)%2 != 0 || strings.ContainsRune(long, '\uFFFD') {
t.Errorf("long text: %d bytes", len(long))
}
if got := plainText(strings.Repeat("", 100000) + "x"); got != "x" {
t.Errorf("many tags = %q", got)
}
}
// controlLine waits for a control line with the given type and returns its JSON.
func controlLine(t *testing.T, out *lines, typ string, within time.Duration) string {
t.Helper()
timeout := time.After(within)
for {
select {
case s, ok := <-out.ch:
if !ok {
t.Fatalf("output ended while waiting for a %s line", typ)
}
if j, found := strings.CutPrefix(s, controlPrefix); found && strings.HasSuffix(j, "\x07") &&
strings.Contains(j, `"type":"`+typ+`"`) {
return strings.TrimSuffix(j, "\x07")
}
case <-timeout:
t.Fatalf("no %s control line within %v", typ, within)
}
}
}
// noControlLine fails when a control line arrives within d.
func noControlLine(t *testing.T, out *lines, d time.Duration) {
t.Helper()
timeout := time.After(d)
for {
select {
case s, ok := <-out.ch:
if ok && strings.HasPrefix(s, controlPrefix) {
t.Fatalf("unexpected control line %q", s)
}
case <-timeout:
return
}
}
}
func TestTooltipAfterHover(t *testing.T) {
steam, _, in, out := setup(t)
steam.setTip(t, "Steam", "3 friends online
& 1 game")
// Wait for the refresh the NewToolTip signal triggers.
deadline := time.Now().Add(5 * time.Second)
var got string
for time.Now().Before(deadline) {
in.send(t, "hover 0")
select {
case s := <-out.ch:
if strings.HasPrefix(s, controlPrefix) {
got = s
}
case <-time.After(900 * time.Millisecond):
}
if got != "" {
break
}
in.send(t, "leave")
}
want := controlPrefix + `{"type":"tooltip","col":0,"width":1,"title":"Steam","body":"3 friends online\n& 1 game"}` + "\x07"
if got != want {
t.Fatalf("tooltip line = %q\nwant %q", got, want)
}
// The pointer leaves: the tooltip closes.
in.send(t, "leave")
if j := controlLine(t, out, "close", time.Second); j != `{"type":"close"}` {
t.Fatalf("close line = %q", j)
}
}
func TestTooltipNotSentWhenLeftEarlyOrOnClick(t *testing.T) {
steam, obs, in, out := setup(t)
steam.run(t, func() { steam.tip = &[2]string{"Steam", "x"} })
obs.run(t, func() { obs.title = "Obsidian" })
steam.setTip(t, "Steam", "x") // refresh both through the signal path
obs.setTip(t, "", "")
time.Sleep(200 * time.Millisecond)
in.send(t, "hover 0")
time.Sleep(150 * time.Millisecond)
in.send(t, "leave")
noControlLine(t, out, 700*time.Millisecond)
// A click before the delay cancels the tooltip too, without a close line.
in.send(t, "hover 0")
time.Sleep(100 * time.Millisecond)
in.send(t, "click left 0 1")
expectCall(t, steam, "Activate(0,0)")
noControlLine(t, out, 700*time.Millisecond)
// Moving between the gap and another icon restarts the delay.
in.send(t, "hover 1", "hover 2")
start := time.Now()
j := controlLine(t, out, "tooltip", 2*time.Second)
if d := time.Since(start); d < 400*time.Millisecond {
t.Fatalf("the tooltip came after %v, want about 500ms", d)
}
// An item with no tooltip shows its Title.
if !strings.Contains(j, `"title":"Obsidian"`) || !strings.Contains(j, `"col":2`) {
t.Fatalf("tooltip = %q", j)
}
// A click after it was shown closes it.
in.send(t, "click left 2 2")
controlLine(t, out, "close", time.Second)
}
func TestTooltipWithNothingToSayIsNotSent(t *testing.T) {
_, _, in, out := setup(t) // fake items without ToolTip or Title
in.send(t, "hover 0")
noControlLine(t, out, 900*time.Millisecond)
in.send(t, "leave")
noControlLine(t, out, 100*time.Millisecond)
}