Files
cthulhu/internal/app/review_spelling_test.go

98 lines
3.4 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"errors"
"log/slog"
"reflect"
"strings"
"testing"
"git.stormux.org/storm/cthulhu/internal/input"
"git.stormux.org/storm/cthulhu/internal/review"
)
func TestFlatReviewSpellingPresentation(t *testing.T) {
for _, tc := range []struct {
command input.Command
text string
parts []string
character bool
}{
{input.ReviewCurrentWord, "book", nil, false},
{input.ReviewCurrentCharacter, "b", nil, true},
{input.ReviewPhoneticCharacter, "B", []string{"bravo"}, false},
{input.ReviewSpellWord, "Book", []string{"B", "o", "o", "k"}, true},
{input.ReviewPhoneticWord, "Book", []string{"bravo", "oscar", "oscar", "kilo"}, false},
{input.ReviewSpellWord, "猫1!", []string{"猫", "1", "!"}, true},
{input.ReviewPhoneticWord, "猫1!", []string{"猫", "1", "!"}, false},
{input.ReviewPhoneticCharacter, " ", nil, true},
{input.ReviewPhoneticWord, "", nil, false},
} {
queue := &fakeSpeechQueue{requests: make(chan speechRequest, 1), spoken: make(chan string, 1)}
unit, step := flatReviewCommand(tc.command)
result := review.Result{FlatUnit: unit, FlatStep: step, FlatSpeech: flat_review_speech(tc.command), FlatPosition: review.FlatPosition{Text: tc.text}}
presentFlatReviewResult(slog.New(slog.DiscardHandler), queue, result)
request := <-queue.requests
if !reflect.DeepEqual(request.Spelling, tc.parts) || request.Character != tc.character {
t.Fatalf("%s %q request=%#v", tc.command, tc.text, request)
}
}
}
type spellingTestSpeaker struct {
parts []string
characters []string
after func()
}
func (*spellingTestSpeaker) Connect(context.Context) error { return nil }
func (*spellingTestSpeaker) Close(context.Context) error { return nil }
func (*spellingTestSpeaker) Cancel(context.Context) error { return nil }
func (speaker *spellingTestSpeaker) Speak(_ context.Context, text string) error {
speaker.parts = append(speaker.parts, text)
if speaker.after != nil {
speaker.after()
}
return nil
}
func (speaker *spellingTestSpeaker) SpeakCharacter(ctx context.Context, text string) error {
speaker.characters = append(speaker.characters, text)
return speaker.Speak(ctx, text)
}
func TestReviewSpellingDeliveryAndInterruption(t *testing.T) {
for _, style := range []review.FlatSpeech{review.FlatSpell, review.FlatPhonetic} {
speaker := &spellingTestSpeaker{}
worker := newSpeechWorker(slog.New(slog.DiscardHandler), speaker)
request := speechRequest{Text: "book", Cause: "flat-review"}
set_review_spelling(&request, strings.Repeat("book", 20), style)
if err := worker.deliver(context.Background(), request); err != nil {
t.Fatal(err)
}
if len(speaker.parts) != 80 {
t.Fatalf("long spelling lost letters: %d", len(speaker.parts))
}
wantCharacters := 0
if style == review.FlatSpell {
wantCharacters = 80
}
if len(speaker.characters) != wantCharacters {
t.Fatalf("character-mode delivery=%d", len(speaker.characters))
}
worker.running = true // Exercise the real interruption generation barrier.
speaker.parts = nil
speaker.after = worker.InterruptForUser
if err := worker.deliver(context.Background(), request); !errors.Is(err, context.Canceled) {
t.Fatalf("interrupt=%v", err)
}
if len(speaker.parts) != 1 {
t.Fatalf("spelling continued after input: %v", speaker.parts)
}
}
}