// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package review import ( "context" "strings" "testing" "git.stormux.org/storm/cthulhu/internal/atspi" ) type blockCaretReader struct { caretTextReader calls, blocks int malformed bool } func (reader *blockCaretReader) ReadDocumentCaret(ctx context.Context, object atspi.ObjectReference, offset int, unit atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) { reader.calls++ return reader.caretTextReader.ReadDocumentCaret(ctx, object, offset, unit) } func (reader *blockCaretReader) ReadDocumentCharacters(_ context.Context, object atspi.ObjectReference, offset int) (atspi.DocumentCaretText, error) { reader.calls++ reader.blocks++ runes := []rune(reader.caretTextReader[object.ID()]) start := offset / 128 * 128 end := min(len(runes), start+128) raw := string(runes[start:end]) if reader.malformed { raw = "" } return atspi.DocumentCaretText{Start: start, End: end, RawText: raw, CharacterCount: len(runes)}, nil } func TestDocumentWordSpacesUseBoundedBlocks(t *testing.T) { values := caretTextReader{webID("/text"): "A" + strings.Repeat(" \t\n\u2003", 250) + "猫"} cursor := newCaretTextCursor(t, values) reader := &blockCaretReader{caretTextReader: values} for _, step := range []struct { command DocumentCaretCommand text string }{{CaretNextWord, "猫"}, {CaretPreviousWord, "猫"}, {CaretPreviousWord, "A \t\n\u2003"}} { move, err := cursor.MoveCaret(context.Background(), reader, step.command) // Native word providers include varying trailing whitespace. Retain // their text and verify the actual requested word and Unicode offset. if err != nil || !strings.HasPrefix(move.Text, strings.TrimSpace(step.text)) { t.Fatalf("%s: %+v error=%v", step.command, move, err) } } t.Logf("three word commands across 1000 whitespace characters: %d reads, %d blocks", reader.calls, reader.blocks) if reader.blocks == 0 || reader.calls > 50 { t.Fatalf("word traversal used %d reads (%d blocks)", reader.calls, reader.blocks) } } func TestDocumentCharacterMovementDoesNotReadBlocks(t *testing.T) { values := caretTextReader{webID("/text"): "A B"} cursor := newCaretTextCursor(t, values) reader := &blockCaretReader{caretTextReader: values} move, err := cursor.MoveCaret(context.Background(), reader, CaretNextCharacter) if err != nil || move.Text != " " || reader.blocks != 0 || reader.calls != 1 { t.Fatalf("move=%+v error=%v calls=%d blocks=%d", move, err, reader.calls, reader.blocks) } } func TestDocumentWordMalformedBlockCannotSkipText(t *testing.T) { values := caretTextReader{webID("/text"): "A B"} cursor := newCaretTextCursor(t, values) reader := &blockCaretReader{caretTextReader: values, malformed: true} move, err := cursor.MoveCaret(context.Background(), reader, CaretNextWord) if err != nil || move.Text != "B" || move.Position.Offset != 4 { t.Fatalf("move=%+v error=%v", move, err) } }