110 lines
3.6 KiB
Go
110 lines
3.6 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option)
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
// more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/input"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
)
|
|
|
|
func TestPresentationDiagnosticsExplainSuppressionWithoutText(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
name string
|
|
action command.ID
|
|
muted bool
|
|
silent bool
|
|
}{
|
|
{name: "ordinary"},
|
|
{name: "muted", action: command.Mute, muted: true},
|
|
{name: "temporary silence", action: command.TempDisableSpeech, silent: true},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
var log bytes.Buffer
|
|
previousLogger := slog.Default()
|
|
slog.SetDefault(slog.New(slog.NewJSONHandler(&log, &slog.HandlerOptions{Level: slog.LevelDebug})))
|
|
defer slog.SetDefault(previousLogger)
|
|
source := &fakeSource{snapshot: screen.Snapshot{Terminal: 2, Active: true, Rows: 1, Cols: 7, Cells: []rune("PRIVATE")}, events: make(chan error, 1)}
|
|
keys := &fakeKeys{events: make(chan error, 1)}
|
|
reads, polls := 0, 0
|
|
source.afterRead = func() {
|
|
reads++
|
|
if reads == 2 {
|
|
keys.events <- nil
|
|
}
|
|
}
|
|
keys.poll = func(_ bool, handle func(input.Decision)) error {
|
|
polls++
|
|
if polls == 1 {
|
|
handle(input.Decision{Command: testCase.action, Interrupt: true, Keypress: true})
|
|
source.snapshot.Cells = []rune("SECRET!")
|
|
source.events <- nil
|
|
} else {
|
|
handle(input.Decision{Command: command.Quit})
|
|
}
|
|
return nil
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
if err := run(ctx, source, keys, &fakeVoice{}, nil); err != nil || reads != 2 || polls != 2 {
|
|
t.Fatal("fixture did not process the screen update", err, reads, polls)
|
|
}
|
|
if strings.Contains(log.String(), "PRIVATE") || strings.Contains(log.String(), "SECRET") {
|
|
t.Fatal("diagnostics exposed screen or speech text")
|
|
}
|
|
var presentation struct {
|
|
Msg string
|
|
Messages int
|
|
Muted bool
|
|
TemporarySilence bool `json:"temporary_silence"`
|
|
UIActive bool `json:"ui_active"`
|
|
}
|
|
inputLogged := false
|
|
for _, line := range bytes.Split(bytes.TrimSpace(log.Bytes()), []byte("\n")) {
|
|
var record struct {
|
|
Msg string
|
|
Interrupt bool
|
|
Keypress bool
|
|
}
|
|
if err := json.Unmarshal(line, &record); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if record.Msg == "reader presentation" {
|
|
if err := json.Unmarshal(line, &presentation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if record.Msg == "input decision" && record.Interrupt && record.Keypress {
|
|
inputLogged = true
|
|
}
|
|
}
|
|
if !inputLogged || presentation.Messages != 1 || presentation.Muted != testCase.muted || presentation.TemporarySilence != testCase.silent || presentation.UIActive {
|
|
t.Fatal("missing or incorrect input/presentation state", inputLogged, presentation)
|
|
}
|
|
})
|
|
}
|
|
}
|