mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Adds text attributes support for semantics (#79599)
* Adds label attributes * fix test * comments * list equal
This commit is contained in:
parent
a65328b460
commit
00ee2e59e7
@ -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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user