Unverified Commit 00ee2e59 authored by chunhtai's avatar chunhtai Committed by GitHub

Adds text attributes support for semantics (#79599)

* Adds label attributes

* fix test

* comments

* list equal
parent a65328b4
......@@ -3757,7 +3757,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
///
/// The [container] argument must not be null.
///
/// If the [label] is not null, the [textDirection] must also not be null.
/// If the [attributedLabel] is not null, the [textDirection] must also not be null.
RenderSemanticsAnnotations({
RenderBox? child,
bool container = false,
......@@ -3786,11 +3786,11 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
bool? liveRegion,
int? maxValueLength,
int? currentValueLength,
String? label,
String? value,
String? increasedValue,
String? decreasedValue,
String? hint,
AttributedString? attributedLabel,
AttributedString? attributedValue,
AttributedString? attributedIncreasedValue,
AttributedString? attributedDecreasedValue,
AttributedString? attributedHint,
SemanticsHintOverrides? hintOverrides,
TextDirection? textDirection,
SemanticsSortKey? sortKey,
......@@ -3844,11 +3844,11 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_hidden = hidden,
_image = image,
_onDismiss = onDismiss,
_label = label,
_value = value,
_increasedValue = increasedValue,
_decreasedValue = decreasedValue,
_hint = hint,
_attributedLabel = attributedLabel,
_attributedValue = attributedValue,
_attributedIncreasedValue = attributedIncreasedValue,
_attributedDecreasedValue = attributedDecreasedValue,
_attributedHint = attributedHint,
_hintOverrides = hintOverrides,
_textDirection = textDirection,
_sortKey = sortKey,
......@@ -4183,65 +4183,63 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
/// If non-null, sets the [SemanticsNode.attributedLabel] semantic to the given value.
///
/// The reading direction is given by [textDirection].
String? get label => _label;
String? _label;
set label(String? value) {
if (_label == value)
AttributedString? get attributedLabel => _attributedLabel;
AttributedString? _attributedLabel;
set attributedLabel(AttributedString? value) {
if (_attributedLabel == value)
return;
_label = value;
_attributedLabel = value;
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.value] semantic to the given value.
/// If non-null, sets the [SemanticsNode.attributedValue] semantic to the given value.
///
/// The reading direction is given by [textDirection].
String? get value => _value;
String? _value;
set value(String? value) {
if (_value == value)
AttributedString? get attributedValue => _attributedValue;
AttributedString? _attributedValue;
set attributedValue(AttributedString? value) {
if (_attributedValue == value)
return;
_value = value;
_attributedValue = value;
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.increasedValue] semantic to the given
/// value.
/// If non-null, sets the [SemanticsNode.attributedIncreasedValue] semantic to the given value.
///
/// The reading direction is given by [textDirection].
String? get increasedValue => _increasedValue;
String? _increasedValue;
set increasedValue(String? value) {
if (_increasedValue == value)
AttributedString? get attributedIncreasedValue => _attributedIncreasedValue;
AttributedString? _attributedIncreasedValue;
set attributedIncreasedValue(AttributedString? value) {
if (_attributedIncreasedValue == value)
return;
_increasedValue = value;
_attributedIncreasedValue = value;
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.decreasedValue] semantic to the given
/// value.
/// If non-null, sets the [SemanticsNode.attributedDecreasedValue] semantic to the given value.
///
/// The reading direction is given by [textDirection].
String? get decreasedValue => _decreasedValue;
String? _decreasedValue;
set decreasedValue(String? value) {
if (_decreasedValue == value)
AttributedString? get attributedDecreasedValue => _attributedDecreasedValue;
AttributedString? _attributedDecreasedValue;
set attributedDecreasedValue(AttributedString? value) {
if (_attributedDecreasedValue == value)
return;
_decreasedValue = value;
_attributedDecreasedValue = value;
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.hint] semantic to the given value.
/// If non-null, sets the [SemanticsNode.attributedHint] semantic to the given value.
///
/// The reading direction is given by [textDirection].
String? get hint => _hint;
String? _hint;
set hint(String? value) {
if (_hint == value)
AttributedString? get attributedHint => _attributedHint;
AttributedString? _attributedHint;
set attributedHint(AttributedString? value) {
if (_attributedHint == value)
return;
_hint = value;
_attributedHint = value;
markNeedsSemanticsUpdate();
}
......@@ -4255,10 +4253,12 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given
/// value.
///
/// This must not be null if [label], [hint], [value], [increasedValue], or
/// [decreasedValue] are not null.
/// This must not be null if [attributedLabel], [attributedHint],
/// [attributedValue], [attributedIncreasedValue], or
/// [attributedDecreasedValue] are not null.
TextDirection? get textDirection => _textDirection;
TextDirection? _textDirection;
set textDirection(TextDirection? value) {
......@@ -4765,16 +4765,16 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.isHidden = hidden!;
if (image != null)
config.isImage = image!;
if (label != null)
config.label = label!;
if (value != null)
config.value = value!;
if (increasedValue != null)
config.increasedValue = increasedValue!;
if (decreasedValue != null)
config.decreasedValue = decreasedValue!;
if (hint != null)
config.hint = hint!;
if (attributedLabel != null)
config.attributedLabel = attributedLabel!;
if (attributedValue != null)
config.attributedValue = attributedValue!;
if (attributedIncreasedValue != null)
config.attributedIncreasedValue = attributedIncreasedValue!;
if (attributedDecreasedValue != null)
config.attributedDecreasedValue = attributedDecreasedValue!;
if (attributedHint != null)
config.attributedHint = attributedHint!;
if (hintOverrides != null && hintOverrides!.isNotEmpty)
config.hintOverrides = hintOverrides;
if (scopesRoute != null)
......
......@@ -7305,10 +7305,15 @@ class Semantics extends SingleChildRenderObjectWidget {
int? maxValueLength,
int? currentValueLength,
String? label,
AttributedString? attributedLabel,
String? value,
AttributedString? attributedValue,
String? increasedValue,
AttributedString? attributedIncreasedValue,
String? decreasedValue,
AttributedString? attributedDecreasedValue,
String? hint,
AttributedString? attributedHint,
String? onTapHint,
String? onLongPressHint,
TextDirection? textDirection,
......@@ -7364,10 +7369,15 @@ class Semantics extends SingleChildRenderObjectWidget {
maxValueLength: maxValueLength,
currentValueLength: currentValueLength,
label: label,
attributedLabel: attributedLabel,
value: value,
attributedValue: attributedValue,
increasedValue: increasedValue,
attributedIncreasedValue: attributedIncreasedValue,
decreasedValue: decreasedValue,
attributedDecreasedValue: attributedDecreasedValue,
hint: hint,
attributedHint: attributedHint,
textDirection: textDirection,
sortKey: sortKey,
tagForChildren: tagForChildren,
......@@ -7452,6 +7462,31 @@ class Semantics extends SingleChildRenderObjectWidget {
/// an [ExcludeSemantics] widget and then another [Semantics] widget.
final bool excludeSemantics;
AttributedString? get _effectiveAttributedLabel {
return properties.attributedLabel ??
(properties.label == null ? null : AttributedString(properties.label!));
}
AttributedString? get _effectiveAttributedValue {
return properties.attributedValue ??
(properties.value == null ? null : AttributedString(properties.value!));
}
AttributedString? get _effectiveAttributedIncreasedValue {
return properties.attributedIncreasedValue ??
(properties.increasedValue == null ? null : AttributedString(properties.increasedValue!));
}
AttributedString? get _effectiveAttributedDecreasedValue {
return properties.attributedDecreasedValue ??
(properties.decreasedValue == null ? null : AttributedString(properties.decreasedValue!));
}
AttributedString? get _effectiveAttributedHint {
return properties.attributedHint ??
(properties.hint == null ? null : AttributedString(properties.hint!));
}
@override
RenderSemanticsAnnotations createRenderObject(BuildContext context) {
return RenderSemanticsAnnotations(
......@@ -7481,11 +7516,11 @@ class Semantics extends SingleChildRenderObjectWidget {
namesRoute: properties.namesRoute,
hidden: properties.hidden,
image: properties.image,
label: properties.label,
value: properties.value,
increasedValue: properties.increasedValue,
decreasedValue: properties.decreasedValue,
hint: properties.hint,
attributedLabel: _effectiveAttributedLabel,
attributedValue: _effectiveAttributedValue,
attributedIncreasedValue: _effectiveAttributedIncreasedValue,
attributedDecreasedValue: _effectiveAttributedDecreasedValue,
attributedHint: _effectiveAttributedHint,
hintOverrides: properties.hintOverrides,
textDirection: _getTextDirection(context),
sortKey: properties.sortKey,
......@@ -7518,7 +7553,10 @@ class Semantics extends SingleChildRenderObjectWidget {
if (properties.textDirection != null)
return properties.textDirection;
final bool containsText = properties.label != null || properties.value != null || properties.hint != null;
final bool containsText = properties.attributedLabel != null ||
properties.label != null ||
properties.value != null ||
properties.hint != null;
if (!containsText)
return null;
......@@ -7554,11 +7592,11 @@ class Semantics extends SingleChildRenderObjectWidget {
..liveRegion = properties.liveRegion
..maxValueLength = properties.maxValueLength
..currentValueLength = properties.currentValueLength
..label = properties.label
..value = properties.value
..increasedValue = properties.increasedValue
..decreasedValue = properties.decreasedValue
..hint = properties.hint
..attributedLabel = _effectiveAttributedLabel
..attributedValue = _effectiveAttributedValue
..attributedIncreasedValue = _effectiveAttributedIncreasedValue
..attributedDecreasedValue = _effectiveAttributedDecreasedValue
..attributedHint = _effectiveAttributedHint
..hintOverrides = properties.hintOverrides
..namesRoute = properties.namesRoute
..textDirection = _getTextDirection(context)
......
......@@ -280,22 +280,22 @@ class _SemanticsDebuggerPainter extends CustomPainter {
if (isAdjustable)
annotations.add('adjustable');
assert(data.label != null);
assert(data.attributedLabel != null);
final String message;
if (data.label.isEmpty) {
if (data.attributedLabel.string.isEmpty) {
message = annotations.join('; ');
} else {
final String label;
if (data.textDirection == null) {
label = '${Unicode.FSI}${data.label}${Unicode.PDI}';
label = '${Unicode.FSI}${data.attributedLabel.string}${Unicode.PDI}';
annotations.insert(0, 'MISSING TEXT DIRECTION');
} else {
switch (data.textDirection!) {
case TextDirection.rtl:
label = '${Unicode.RLI}${data.label}${Unicode.PDF}';
label = '${Unicode.RLI}${data.attributedLabel.string}${Unicode.PDF}';
break;
case TextDirection.ltr:
label = data.label;
label = data.attributedLabel.string;
break;
}
}
......
......@@ -28,7 +28,7 @@ class TestTree {
child: RenderPositionedBox(
child: child = RenderConstrainedBox(
additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
child: RenderSemanticsAnnotations(label: 'Hello there foo', textDirection: TextDirection.ltr),
child: RenderSemanticsAnnotations(attributedLabel: AttributedString('Hello there foo'), textDirection: TextDirection.ltr),
),
),
),
......
......@@ -11,7 +11,7 @@ import 'rendering_tester.dart';
void main() {
test('only send semantics update if semantics have changed', () {
final TestRender testRender = TestRender()
..label = 'hello'
..attributedLabel = AttributedString('hello')
..textDirection = TextDirection.ltr;
final RenderConstrainedBox tree = RenderConstrainedBox(
......@@ -46,7 +46,7 @@ void main() {
semanticsUpdateCount = 0;
// Change semantics and request update.
testRender.label = 'bye';
testRender.attributedLabel = AttributedString('bye');
testRender.markNeedsSemanticsUpdate();
pumpFrame(phase: EnginePhase.flushSemantics);
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'package:flutter/rendering.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart';
......@@ -63,6 +64,75 @@ void main() {
expect(node.getSemanticsData().tags, tags);
});
test('SemanticsConfiguration can set both string label/value/hint and attributed version', () {
final SemanticsConfiguration config = SemanticsConfiguration();
config.label = 'label1';
expect(config.label, 'label1');
expect(config.attributedLabel.string, 'label1');
expect(config.attributedLabel.attributes.isEmpty, isTrue);
config.attributedLabel = AttributedString(
'label2',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end:1)),
]
);
expect(config.label, 'label2');
expect(config.attributedLabel.string, 'label2');
expect(config.attributedLabel.attributes.length, 1);
expect(config.attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue);
expect(config.attributedLabel.attributes[0].range, const TextRange(start: 0, end: 1));
config.label = 'label3';
expect(config.label, 'label3');
expect(config.attributedLabel.string, 'label3');
expect(config.attributedLabel.attributes.isEmpty, isTrue);
config.value = 'value1';
expect(config.value, 'value1');
expect(config.attributedValue.string, 'value1');
expect(config.attributedValue.attributes.isEmpty, isTrue);
config.attributedValue = AttributedString(
'value2',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end:1)),
]
);
expect(config.value, 'value2');
expect(config.attributedValue.string, 'value2');
expect(config.attributedValue.attributes.length, 1);
expect(config.attributedValue.attributes[0] is SpellOutStringAttribute, isTrue);
expect(config.attributedValue.attributes[0].range, const TextRange(start: 0, end: 1));
config.value = 'value3';
expect(config.value, 'value3');
expect(config.attributedValue.string, 'value3');
expect(config.attributedValue.attributes.isEmpty, isTrue);
config.hint = 'hint1';
expect(config.hint, 'hint1');
expect(config.attributedHint.string, 'hint1');
expect(config.attributedHint.attributes.isEmpty, isTrue);
config.attributedHint = AttributedString(
'hint2',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end:1)),
]
);
expect(config.hint, 'hint2');
expect(config.attributedHint.string, 'hint2');
expect(config.attributedHint.attributes.length, 1);
expect(config.attributedHint.attributes[0] is SpellOutStringAttribute, isTrue);
expect(config.attributedHint.attributes[0].range, const TextRange(start: 0, end: 1));
config.hint = 'hint3';
expect(config.hint, 'hint3');
expect(config.attributedHint.string, 'hint3');
expect(config.attributedHint.attributes.isEmpty, isTrue);
});
test('mutate existing semantic node list errors', () {
final SemanticsNode node = SemanticsNode()
..rect = const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0);
......@@ -570,6 +640,26 @@ void main() {
);
});
test('Attributed String can concate', () {
final AttributedString string1 = AttributedString(
'string1',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start:0, end:4)),
]
);
final AttributedString string2 = AttributedString(
'string2',
attributes: <StringAttribute>[
LocaleStringAttribute(locale: const Locale('es', 'MX'), range: const TextRange(start:0, end:4)),
]
);
final AttributedString result = string1 + string2;
expect(result.string, 'string1string2');
expect(result.attributes.length, 2);
expect(result.attributes[0].range, const TextRange(start:0, end:4));
expect(result.attributes[0] is SpellOutStringAttribute, isTrue);
});
test('Semantics id does not repeat', () {
final SemanticsOwner owner = SemanticsOwner();
const int expectId = 1400;
......
......@@ -6,6 +6,7 @@ import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
......@@ -83,6 +84,76 @@ void main() {
SemanticsUpdateBuilderSpy.observations.clear();
handle.dispose();
});
testWidgets('Semantics update receives attributed text', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
// Pumps a placeholder to trigger the warm up frame.
await tester.pumpWidget(
const Placeholder(),
// Stops right after the warm up frame.
null,
EnginePhase.build,
);
// The warm up frame will send update for an empty semantics tree. We
// ignore this one time update.
SemanticsUpdateBuilderSpy.observations.clear();
// Builds the real widget tree.
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
attributedLabel: AttributedString(
'label',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)),
],
),
attributedValue: AttributedString(
'value',
attributes: <StringAttribute>[
LocaleStringAttribute(range: const TextRange(start: 0, end: 5), locale: const Locale('en', 'MX')),
],
),
attributedHint: AttributedString(
'hint',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)),
],
),
child: const Placeholder(),
),
),
);
expect(SemanticsUpdateBuilderSpy.observations.length, 2);
expect(SemanticsUpdateBuilderSpy.observations.containsKey(0), isTrue);
expect(SemanticsUpdateBuilderSpy.observations[0]!.childrenInTraversalOrder.length, 1);
expect(SemanticsUpdateBuilderSpy.observations[0]!.childrenInTraversalOrder[0], 1);
expect(SemanticsUpdateBuilderSpy.observations.containsKey(1), isTrue);
expect(SemanticsUpdateBuilderSpy.observations[1]!.childrenInTraversalOrder.length, 0);
expect(SemanticsUpdateBuilderSpy.observations[1]!.label, 'label');
expect(SemanticsUpdateBuilderSpy.observations[1]!.labelAttributes!.length, 1);
expect(SemanticsUpdateBuilderSpy.observations[1]!.labelAttributes![0] is SpellOutStringAttribute, isTrue);
expect(SemanticsUpdateBuilderSpy.observations[1]!.labelAttributes![0].range, const TextRange(start: 0, end: 5));
expect(SemanticsUpdateBuilderSpy.observations[1]!.value, 'value');
expect(SemanticsUpdateBuilderSpy.observations[1]!.valueAttributes!.length, 1);
expect(SemanticsUpdateBuilderSpy.observations[1]!.valueAttributes![0] is LocaleStringAttribute, isTrue);
final LocaleStringAttribute localeAttribute = SemanticsUpdateBuilderSpy.observations[1]!.valueAttributes![0] as LocaleStringAttribute;
expect(localeAttribute.range, const TextRange(start: 0, end: 5));
expect(localeAttribute.locale, const Locale('en', 'MX'));
expect(SemanticsUpdateBuilderSpy.observations[1]!.hint, 'hint');
expect(SemanticsUpdateBuilderSpy.observations[1]!.hintAttributes!.length, 1);
expect(SemanticsUpdateBuilderSpy.observations[1]!.hintAttributes![0] is SpellOutStringAttribute, isTrue);
expect(SemanticsUpdateBuilderSpy.observations[1]!.hintAttributes![0].range, const TextRange(start: 1, end: 2));
SemanticsUpdateBuilderSpy.observations.clear();
handle.dispose();
});
}
class SemanticsUpdateTestBinding extends AutomatedTestWidgetsFlutterBinding {
......@@ -114,18 +185,15 @@ class SemanticsUpdateBuilderSpy extends ui.SemanticsUpdateBuilder {
required double thickness,
required Rect rect,
required String label,
// TODO(chunhtai): change the Object? to List<StringAttribute> when engine
// pr lands: https://github.com/flutter/engine/pull/25373.
// https://github.com/flutter/flutter/issues/79318.
Object? labelAttributes,
List<ui.StringAttribute>? labelAttributes,
required String value,
Object? valueAttributes,
List<ui.StringAttribute>? valueAttributes,
required String increasedValue,
Object? increasedValueAttributes,
List<ui.StringAttribute>? increasedValueAttributes,
required String decreasedValue,
Object? decreasedValueAttributes,
List<ui.StringAttribute>? decreasedValueAttributes,
required String hint,
Object? hintAttributes,
List<ui.StringAttribute>? hintAttributes,
TextDirection? textDirection,
required Float64List transform,
required Int32List childrenInTraversalOrder,
......@@ -152,10 +220,15 @@ class SemanticsUpdateBuilderSpy extends ui.SemanticsUpdateBuilder {
thickness: thickness,
rect: rect,
label: label,
labelAttributes: labelAttributes,
hint: hint,
hintAttributes: hintAttributes,
value: value,
valueAttributes: valueAttributes,
increasedValue: increasedValue,
increasedValueAttributes: increasedValueAttributes,
decreasedValue: decreasedValue,
decreasedValueAttributes: decreasedValueAttributes,
textDirection: textDirection,
transform: transform,
childrenInTraversalOrder: childrenInTraversalOrder,
......@@ -184,10 +257,15 @@ class SemanticsNodeUpdateObservation {
required this.thickness,
required this.rect,
required this.label,
required this.hint,
this.labelAttributes,
required this.value,
this.valueAttributes,
required this.increasedValue,
this.increasedValueAttributes,
required this.decreasedValue,
this.decreasedValueAttributes,
required this.hint,
this.hintAttributes,
this.textDirection,
required this.transform,
required this.childrenInTraversalOrder,
......@@ -212,10 +290,15 @@ class SemanticsNodeUpdateObservation {
final double thickness;
final Rect rect;
final String label;
final String hint;
final List<ui.StringAttribute>? labelAttributes;
final String value;
final List<ui.StringAttribute>? valueAttributes;
final String increasedValue;
final List<ui.StringAttribute>? increasedValueAttributes;
final String decreasedValue;
final List<ui.StringAttribute>? decreasedValueAttributes;
final String hint;
final List<ui.StringAttribute>? hintAttributes;
final TextDirection? textDirection;
final Float64List transform;
final Int32List childrenInTraversalOrder;
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
......@@ -211,6 +212,152 @@ void main() {
});
});
group('Semantics', () {
testWidgets('Semantics can set attributed Text', (WidgetTester tester) async {
final UniqueKey key = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Semantics(
key: key,
attributedLabel: AttributedString(
'label',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)),
],
),
attributedValue: AttributedString(
'value',
attributes: <StringAttribute>[
LocaleStringAttribute(range: const TextRange(start: 0, end: 5), locale: const Locale('en', 'MX')),
],
),
attributedHint: AttributedString(
'hint',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)),
],
),
child: const Placeholder(),
)
),
)
);
final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel;
expect(attributedLabel.string, 'label');
expect(attributedLabel.attributes.length, 1);
expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue);
expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5));
final AttributedString attributedValue = tester.getSemantics(find.byKey(key)).attributedValue;
expect(attributedValue.string, 'value');
expect(attributedValue.attributes.length, 1);
expect(attributedValue.attributes[0] is LocaleStringAttribute, isTrue);
final LocaleStringAttribute valueLocale = attributedValue.attributes[0] as LocaleStringAttribute;
expect(valueLocale.range, const TextRange(start:0, end: 5));
expect(valueLocale.locale, const Locale('en', 'MX'));
final AttributedString attributedHint = tester.getSemantics(find.byKey(key)).attributedHint;
expect(attributedHint.string, 'hint');
expect(attributedHint.attributes.length, 1);
expect(attributedHint.attributes[0] is SpellOutStringAttribute, isTrue);
expect(attributedHint.attributes[0].range, const TextRange(start:1, end: 2));
});
testWidgets('Semantics can merge attributed strings', (WidgetTester tester) async {
final UniqueKey key = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Semantics(
key: key,
attributedLabel: AttributedString(
'label',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)),
],
),
attributedHint: AttributedString(
'hint',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)),
],
),
child: Semantics(
attributedLabel: AttributedString(
'label',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)),
],
),
attributedHint: AttributedString(
'hint',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 1, end: 2)),
],
),
child: const Placeholder(),
)
)
),
)
);
final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel;
expect(attributedLabel.string, 'label\nlabel');
expect(attributedLabel.attributes.length, 2);
expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue);
expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5));
expect(attributedLabel.attributes[1] is SpellOutStringAttribute, isTrue);
expect(attributedLabel.attributes[1].range, const TextRange(start:6, end: 11));
final AttributedString attributedHint = tester.getSemantics(find.byKey(key)).attributedHint;
expect(attributedHint.string, 'hint\nhint');
expect(attributedHint.attributes.length, 2);
expect(attributedHint.attributes[0] is SpellOutStringAttribute, isTrue);
expect(attributedHint.attributes[0].range, const TextRange(start:1, end: 2));
expect(attributedHint.attributes[1] is SpellOutStringAttribute, isTrue);
expect(attributedHint.attributes[1].range, const TextRange(start:6, end: 7));
});
testWidgets('Semantics can merge attributed strings with non attributed string', (WidgetTester tester) async {
final UniqueKey key = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Semantics(
key: key,
attributedLabel: AttributedString(
'label1',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 0, end: 5)),
],
),
child: Semantics(
label: 'label2',
child: Semantics(
attributedLabel: AttributedString(
'label3',
attributes: <StringAttribute>[
SpellOutStringAttribute(range: const TextRange(start: 1, end: 3)),
],
),
child: const Placeholder(),
),
)
)
),
)
);
final AttributedString attributedLabel = tester.getSemantics(find.byKey(key)).attributedLabel;
expect(attributedLabel.string, 'label1\nlabel2\nlabel3');
expect(attributedLabel.attributes.length, 2);
expect(attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue);
expect(attributedLabel.attributes[0].range, const TextRange(start:0, end: 5));
expect(attributedLabel.attributes[1] is SpellOutStringAttribute, isTrue);
expect(attributedLabel.attributes[1].range, const TextRange(start:15, end: 17));
});
});
group('Row', () {
testWidgets('multiple baseline aligned children', (WidgetTester tester) async {
final UniqueKey key1 = UniqueKey();
......
......@@ -546,11 +546,11 @@ void main() {
final SemanticsData data = SemanticsData(
flags: flags,
actions: actions,
label: 'a',
increasedValue: 'b',
value: 'c',
decreasedValue: 'd',
hint: 'e',
attributedLabel: AttributedString('a'),
attributedIncreasedValue: AttributedString('b'),
attributedValue: AttributedString('c'),
attributedDecreasedValue: AttributedString('d'),
attributedHint: AttributedString('e'),
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment