Expand embedded objects in braille document lines
Structural navigation to a heading whose native text is a single embedded-object placeholder for its child link showed only that placeholder in braille while speech used the expanded title. Braille line reads now expand embedded objects like speech capture, preserving RawText and native offsets; expanded lines carry no cell routes or native selection/attribute dots and only a line-start cursor. Frame assembly strips any remaining placeholders and falls back to displayed text so a placeholder never reaches the display. Regression tests cover placeholder-only, mixed, expanded, assembled, and empty headings plus native linked-heading and Firefox mixed-line braille. Maintainer confirms physical braille and speech agree on YouTube headings.
This commit is contained in:
1 parent
60f7f7a2e9
commit
8ce5e5cdad
7 files changed
+245
-21
No files matched your search
@@ -362,6 +362,125 @@ func TestLinkedHeadingBrailleKeepsHeadingAndLinkIndicators(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// A heading whose native text is a single embedded-object placeholder for
|
||||
// its child link must braille the same displayed title speech uses, never
|
||||
// the placeholder itself.
|
||||
func TestBrowseBrailleEmbeddedObjectHeadingUsesDisplayedText(t *testing.T) {
|
||||
options := config.DefaultSettings().Braille
|
||||
heading := atspi.NewSnapshot("/heading", "Cool video title", "heading", "", "")
|
||||
heading.BusName = ":1.999"
|
||||
heading.HeadingLevel = 3
|
||||
link := atspi.NewSnapshot("/link", "Cool video title", "link", "", "")
|
||||
link.BusName = ":1.999"
|
||||
result := review.Result{
|
||||
DocumentPosition: review.DocumentPosition{Snapshot: heading, LinkedHeading: &link},
|
||||
DocumentBrailleLine: &atspi.DocumentCaretText{Start: 0, End: 1, Text: "\ufffc", RawText: "\ufffc"},
|
||||
}
|
||||
frame := brailleDocumentFrame(result, options)
|
||||
if frame.Text != "Cool video title h3 lnk" || frame.Cursor != 0 || len(frame.Routes) != 0 {
|
||||
t.Fatalf("embedded-object heading frame = %+v", frame)
|
||||
}
|
||||
// An expanded line on the snapshot wins over the accessible name.
|
||||
heading.Text = "Expanded title"
|
||||
result.DocumentPosition.Snapshot = heading
|
||||
frame = brailleDocumentFrame(result, options)
|
||||
if frame.Text != "Expanded title h3 lnk" || len(frame.Routes) != 0 {
|
||||
t.Fatalf("expanded heading frame = %+v", frame)
|
||||
}
|
||||
// No displayed text anywhere: role markers only, no placeholder.
|
||||
heading.Name, heading.Text = "", ""
|
||||
result.DocumentPosition.Snapshot = heading
|
||||
result.DocumentPosition.LinkedHeading = nil
|
||||
frame = brailleDocumentFrame(result, options)
|
||||
// The leading cell is the usual empty-line cursor cell.
|
||||
if strings.ContainsRune(frame.Text, '\ufffc') || strings.TrimSpace(frame.Text) != "h3" {
|
||||
t.Fatalf("empty heading frame = %+v", frame)
|
||||
}
|
||||
}
|
||||
|
||||
// An expanded braille line no longer maps cells to native offsets: it must
|
||||
// carry no routes and no native selection dots, and only a caret at the
|
||||
// start of the native line can be shown.
|
||||
func TestBrowseBrailleExpandedLineHasNoNativeMapping(t *testing.T) {
|
||||
options := config.DefaultSettings().Braille
|
||||
options.SelectionIndicator, options.LinkIndicator = 0x40, 0x80
|
||||
heading := atspi.NewSnapshot("/heading", "Cool video title", "heading", "", "")
|
||||
heading.BusName = ":1.999"
|
||||
heading.HeadingLevel = 3
|
||||
link := atspi.NewSnapshot("/link", "Cool video title", "link", "", "")
|
||||
link.BusName = ":1.999"
|
||||
doc := atspi.Snapshot{BusName: heading.BusName, Path: "/document", Role: "document web"}
|
||||
tree, err := atspi.NewAccessibleTree(doc.ID(), []atspi.TreeNode{{Snapshot: doc, Children: []atspi.ObjectID{heading.ID()}}, {Snapshot: heading}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
docRef, _ := tree.Reference(doc.ID())
|
||||
result := review.Result{DocumentEpoch: 1, DocumentFocus: doc.ID(),
|
||||
DocumentPosition: review.DocumentPosition{Document: docRef, Snapshot: heading, LinkedHeading: &link},
|
||||
DocumentBrailleLine: &atspi.DocumentCaretText{Start: 0, End: 1, Text: "Cool video title", RawText: "\ufffc", Expanded: true, SelectionStart: 0, SelectionEnd: 1}}
|
||||
frame := brailleDocumentFrame(result, options)
|
||||
if frame.Text != "Cool video title h3 lnk" || frame.Cursor != 0 || len(frame.Routes) != 0 || frame.Document == nil {
|
||||
t.Fatalf("expanded frame = %+v", frame)
|
||||
}
|
||||
// Link dots are display-relative and survive; selection dots are
|
||||
// native-offset based and must not appear on expanded text.
|
||||
if len(frame.Dots) != utf8.RuneCountInString(frame.Text) || frame.Dots[0] != 0x80 || frame.Dots[15] != 0x80 || frame.Dots[16] != 0 {
|
||||
t.Fatalf("expanded dots = %v", frame.Dots)
|
||||
}
|
||||
result.DocumentPosition.Offset = 1
|
||||
frame = brailleDocumentFrame(result, options)
|
||||
if frame.Cursor != -1 {
|
||||
t.Fatalf("interior native offset must hide the cursor on expanded text: %+v", frame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrailleFocusEmbeddedObjectLineUsesDisplayedText(t *testing.T) {
|
||||
heading := atspi.NewSnapshot("/heading", "Cool video title", "heading", "", "Cool video title")
|
||||
heading.HeadingLevel = 3
|
||||
heading.TextCaretKnown = true
|
||||
heading.BrailleLine = "\ufffc"
|
||||
heading.BrailleCaret = 0
|
||||
frame := brailleFocusFrameWithOptions(heading, config.DefaultSettings().Braille)
|
||||
if frame.Text != "Cool video title h3" || frame.Cursor != 0 || len(frame.Routes) != 0 {
|
||||
t.Fatalf("focus heading frame = %+v", frame)
|
||||
}
|
||||
// Mixed native text keeps its real surrounding text; only the
|
||||
// placeholder is dropped when no expansion is available.
|
||||
heading.Text = "Before \ufffc after"
|
||||
heading.BrailleLine = "Before \ufffc after"
|
||||
frame = brailleFocusFrameWithOptions(heading, config.DefaultSettings().Braille)
|
||||
if strings.ContainsRune(frame.Text, '\ufffc') || !strings.Contains(frame.Text, "Before") || !strings.Contains(frame.Text, "after") || len(frame.Routes) != 0 {
|
||||
t.Fatalf("mixed focus frame = %+v", frame)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrowseBrailleAssembledLineDropsEmbeddedObjectPlaceholders(t *testing.T) {
|
||||
heading := atspi.NewSnapshot("/heading", "Cool video title", "heading", "", "")
|
||||
heading.BusName = ":1.999"
|
||||
heading.HeadingLevel = 3
|
||||
static := atspi.NewSnapshot("/static", "", "static", "", "")
|
||||
static.BusName = ":1.999"
|
||||
result := review.Result{
|
||||
DocumentPosition: review.DocumentPosition{Snapshot: static},
|
||||
DocumentCaretLine: &atspi.DocumentLine{Contents: []atspi.DocumentContent{
|
||||
{Snapshot: heading, Start: 0, End: 1, RawText: "\ufffc"},
|
||||
{Snapshot: static, Start: 0, End: 16, RawText: "Cool video title"},
|
||||
}},
|
||||
DocumentCaretContext: []atspi.Snapshot{{Role: "heading", HeadingLevel: 3}},
|
||||
}
|
||||
frame := brailleDocumentFrame(result, config.DefaultSettings().Braille)
|
||||
if strings.ContainsRune(frame.Text, '\ufffc') || strings.Count(frame.Text, "Cool video title") != 1 {
|
||||
t.Fatalf("assembled frame = %+v", frame)
|
||||
}
|
||||
// A placeholder-only assembled line falls back to displayed text.
|
||||
result.DocumentCaretLine.Contents = result.DocumentCaretLine.Contents[:1]
|
||||
result.DocumentPosition.Snapshot = heading
|
||||
frame = brailleDocumentFrame(result, config.DefaultSettings().Braille)
|
||||
if frame.Text != "Cool video title h3" || len(frame.Routes) != 0 {
|
||||
t.Fatalf("placeholder-only assembled frame = %+v", frame)
|
||||
}
|
||||
}
|
||||
|
||||
func (*tableBrailleCapturer) ReadDocumentCaret(context.Context, atspi.ObjectReference, int, atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) {
|
||||
return atspi.DocumentCaretText{}, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user