A controlled reader renders accepted application frames in a new virtual-only cthulhu-braille mode at the controller's width and applies its pan and route gestures; reader UI, flashes and secure frames are never exported, and hidden frames are unroutable. A controller reader reports its display width, shows peer cells as an untranslated raw frame and forwards local pan and routing keys. The physical helper now reports its width and edge pans. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
2.7 KiB
Go
97 lines
2.7 KiB
Go
//go:build brlapi && cgo
|
|
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package braille
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func runVirtualLines(t *testing.T, lines ...string) []VirtualResponse {
|
|
t.Helper()
|
|
var output bytes.Buffer
|
|
if err := RunVirtual(context.Background(), strings.NewReader(strings.Join(lines, "\n")+"\n"), &output); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var responses []VirtualResponse
|
|
decoder := json.NewDecoder(&output)
|
|
for decoder.More() {
|
|
var response VirtualResponse
|
|
if err := decoder.Decode(&response); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
responses = append(responses, response)
|
|
}
|
|
return responses
|
|
}
|
|
|
|
func TestVirtualHelperRendersPansAndRoutes(t *testing.T) {
|
|
responses := runVirtualLines(t,
|
|
`{"frame":{"text":"abcdef","cursor":1,"id":9}}`, // No width yet: nothing rendered.
|
|
`{"width":4}`,
|
|
`{"pan":"right"}`,
|
|
`{"route":1}`,
|
|
`{"pan":"left"}`,
|
|
`{"width":0}`,
|
|
`{"frame":{"text":"x","cursor":-1,"id":10}}`,
|
|
)
|
|
if len(responses) != 4 {
|
|
t.Fatalf("responses = %+v", responses)
|
|
}
|
|
first := responses[0].Cells
|
|
// Computer Braille a=dot1, b=dots12; the cursor on b adds dots 7 and 8.
|
|
if len(first) != 4 || first[0] != 0x01 || first[1] != 0x03|VirtualCursorDots {
|
|
t.Fatalf("first page = %#v", first)
|
|
}
|
|
if second := responses[1].Cells; len(second) != 4 || second[0] == first[0] {
|
|
t.Fatalf("pan right did not change the page: %#v", second)
|
|
}
|
|
if route := responses[2].Route; route == nil || route.ID != 9 || route.Offset != 5 {
|
|
t.Fatalf("route = %+v", route)
|
|
}
|
|
if !equalCells(responses[3].Cells, first) {
|
|
t.Fatalf("pan left did not restore the first page: %#v", responses[3].Cells)
|
|
}
|
|
}
|
|
|
|
func equalCells(a, b []int) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for index := range a {
|
|
if a[index] != b[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestVirtualHelperUsesContractedFrameTable(t *testing.T) {
|
|
responses := runVirtualLines(t, `{"width":8}`,
|
|
`{"frame":{"text":"the cat","cursor":-1,"id":1,"contract":true,"table":"en-ueb-g2.ctb"}}`)
|
|
if len(responses) != 1 || len(responses[0].Cells) != 8 {
|
|
t.Fatalf("responses = %+v", responses)
|
|
}
|
|
// Contraction makes "the" a single cell, so the line fits in fewer cells
|
|
// than its seven characters: the last cell is blank padding.
|
|
if responses[0].Cells[7] != 0 {
|
|
t.Fatalf("contracted output was not shorter: %#v", responses[0].Cells)
|
|
}
|
|
}
|
|
|
|
func TestVirtualHelperRejectsInvalidRequests(t *testing.T) {
|
|
for _, line := range []string{`{"width":5000}`, `{}`, `not json`, `{"pan":"up"}`} {
|
|
var output bytes.Buffer
|
|
if RunVirtual(context.Background(), strings.NewReader(line+"\n"), &output) == nil {
|
|
t.Fatal("invalid request accepted", line)
|
|
}
|
|
}
|
|
}
|