112 lines
3.6 KiB
Go
112 lines
3.6 KiB
Go
package braille
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type clientBackend struct {
|
|
fakeBackend
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
func (backend *clientBackend) Write(text string, cursor int) error {
|
|
if err := backend.fakeBackend.Write(text, cursor); err != nil {
|
|
return err
|
|
}
|
|
backend.cancel()
|
|
return nil
|
|
}
|
|
|
|
func TestRunClientShowsLiveFrameAndReleasesDisplay(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
backend := &clientBackend{fakeBackend: fakeBackend{geometry: Geometry{20, 1}}, cancel: cancel}
|
|
input := strings.NewReader("{\"text\":\"Save, push button\",\"cursor\":-1}\n")
|
|
var ready bytes.Buffer
|
|
if err := RunClient(ctx, backend, io.NopCloser(input), &ready); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if ready.String() != "ready\n" || len(backend.writes) != 1 ||
|
|
backend.writes[0] != "Save, push button " || backend.opens != 1 || backend.closes != 1 {
|
|
t.Fatalf("client lifecycle: ready=%q backend=%+v", ready.String(), backend)
|
|
}
|
|
}
|
|
|
|
func TestRunClientPlacesCursorOnUnicodeCaret(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
backend := &clientBackend{fakeBackend: fakeBackend{geometry: Geometry{4, 1}}, cancel: cancel}
|
|
input := strings.NewReader("{\"text\":\"é界abcde\",\"cursor\":5}\n")
|
|
if err := RunClient(ctx, backend, io.NopCloser(input), &bytes.Buffer{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(backend.writes) != 1 || backend.writes[0] != "cde " ||
|
|
len(backend.cursors) != 1 || backend.cursors[0] != 2 {
|
|
t.Fatalf("writes=%q cursors=%v", backend.writes, backend.cursors)
|
|
}
|
|
}
|
|
|
|
func TestRunClientRejectsInvalidCursorAndCloses(t *testing.T) {
|
|
backend := &fakeBackend{geometry: Geometry{4, 1}}
|
|
err := RunClient(context.Background(), backend,
|
|
io.NopCloser(strings.NewReader("{\"text\":\"abc\",\"cursor\":4}\n")), &bytes.Buffer{})
|
|
if err == nil || backend.opens != 1 || backend.closes != 1 || len(backend.writes) != 0 {
|
|
t.Fatalf("error=%v backend=%+v", err, backend)
|
|
}
|
|
}
|
|
|
|
func TestOfferLatestFrameReplacesPendingFrame(t *testing.T) {
|
|
frames := make(chan Frame, 1)
|
|
offerLatestFrame(frames, Frame{Text: "old"})
|
|
offerLatestFrame(frames, Frame{Text: "new"})
|
|
if frame := <-frames; frame.Text != "new" {
|
|
t.Fatalf("retained %q", frame.Text)
|
|
}
|
|
offerLatestFrame(frames, Frame{Text: "newer"})
|
|
if frame := <-frames; frame.Text != "newer" {
|
|
t.Fatalf("lost empty-queue update: %q", frame.Text)
|
|
}
|
|
}
|
|
|
|
type routingBackend struct{ fakeBackend }
|
|
|
|
func (b *routingBackend) ReadKey() (Key, error) {
|
|
if len(b.writes) == 0 {
|
|
return NoKey, nil
|
|
}
|
|
return b.fakeBackend.ReadKey()
|
|
}
|
|
|
|
func TestRunClientRoutesDisplayedFrameAfterPanningAndJoinsReader(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
input, writer := io.Pipe()
|
|
defer writer.Close()
|
|
backend := &routingBackend{fakeBackend{geometry: Geometry{4, 1}, keys: []Key{PanRight, RouteBase + 1}, cancel: cancel}}
|
|
var output bytes.Buffer
|
|
done := make(chan error, 1)
|
|
go func() { done <- RunClient(ctx, backend, input, &output) }()
|
|
if _, err := io.WriteString(writer, "{\"text\":\"é猫abcdef\",\"cursor\":-1,\"id\":42}\n"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := <-done; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if output.String() != "ready\n{\"id\":42,\"offset\":5}\n" {
|
|
t.Fatalf("helper output %q", output.String())
|
|
}
|
|
if backend.closes != 1 {
|
|
t.Fatalf("close count %d", backend.closes)
|
|
}
|
|
// The writer has deliberately remained open; return requires the session to
|
|
// close the blocked input reader and join it.
|
|
if _, err := writer.Write([]byte("x")); err == nil {
|
|
t.Fatal("reader still open")
|
|
}
|
|
}
|