mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
* ensures that only semantics boundaries will be added to owner._nodesNeedingSemantics as expected by compiler. * no longer throws assert if markNeedsSemanticsUpdate is called on non-semantic-boundary render object with a non-semantic-boundary parent. * Fixes #13109. * removes onlyLocalUpdates from markNeedsSemanticsUpdate as its no longer needed.
259 lines
10 KiB
Dart
259 lines
10 KiB
Dart
// Copyright 2017 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/semantics.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:vector_math/vector_math_64.dart';
|
|
|
|
import '../rendering/rendering_tester.dart';
|
|
|
|
|
|
void main() {
|
|
group('SemanticsNode', () {
|
|
const SemanticsTag tag1 = const SemanticsTag('Tag One');
|
|
const SemanticsTag tag2 = const SemanticsTag('Tag Two');
|
|
const SemanticsTag tag3 = const SemanticsTag('Tag Three');
|
|
|
|
test('tagging', () {
|
|
final SemanticsNode node = new SemanticsNode();
|
|
|
|
expect(node.isTagged(tag1), isFalse);
|
|
expect(node.isTagged(tag2), isFalse);
|
|
|
|
node.tags = new Set<SemanticsTag>()..add(tag1);
|
|
expect(node.isTagged(tag1), isTrue);
|
|
expect(node.isTagged(tag2), isFalse);
|
|
|
|
node.tags.add(tag2);
|
|
expect(node.isTagged(tag1), isTrue);
|
|
expect(node.isTagged(tag2), isTrue);
|
|
});
|
|
|
|
test('getSemanticsData includes tags', () {
|
|
final Set<SemanticsTag> tags = new Set<SemanticsTag>()
|
|
..add(tag1)
|
|
..add(tag2);
|
|
|
|
final SemanticsNode node = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 10.0)
|
|
..tags = tags;
|
|
|
|
expect(node.getSemanticsData().tags, tags);
|
|
|
|
tags.add(tag3);
|
|
|
|
final SemanticsConfiguration config = new SemanticsConfiguration()
|
|
..isMergingSemanticsOfDescendants = true;
|
|
|
|
node.updateWith(
|
|
config: config,
|
|
childrenInInversePaintOrder: <SemanticsNode>[
|
|
new SemanticsNode()
|
|
..isMergedIntoParent = true
|
|
..rect = new Rect.fromLTRB(5.0, 5.0, 10.0, 10.0)
|
|
..tags = tags,
|
|
],
|
|
);
|
|
|
|
expect(node.getSemanticsData().tags, tags);
|
|
});
|
|
|
|
test('after markNeedsSemanticsUpdate() all render objects between two semantic boundaries are asked for annotations', () {
|
|
renderer.pipelineOwner.ensureSemantics();
|
|
|
|
TestRender middle;
|
|
final TestRender root = new TestRender(
|
|
action: SemanticsAction.tap,
|
|
isSemanticBoundary: true,
|
|
child: new TestRender(
|
|
action: SemanticsAction.longPress,
|
|
isSemanticBoundary: false,
|
|
child: middle = new TestRender(
|
|
action: SemanticsAction.scrollLeft,
|
|
isSemanticBoundary: false,
|
|
child: new TestRender(
|
|
action: SemanticsAction.scrollRight,
|
|
isSemanticBoundary: false,
|
|
child: new TestRender(
|
|
action: SemanticsAction.scrollUp,
|
|
isSemanticBoundary: true,
|
|
)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
|
|
layout(root);
|
|
pumpFrame(phase: EnginePhase.flushSemantics);
|
|
|
|
int expectedActions = SemanticsAction.tap.index | SemanticsAction.longPress.index | SemanticsAction.scrollLeft.index | SemanticsAction.scrollRight.index;
|
|
expect(root.debugSemantics.getSemanticsData().actions, expectedActions);
|
|
|
|
middle.action = SemanticsAction.scrollDown;
|
|
middle.markNeedsSemanticsUpdate();
|
|
|
|
pumpFrame(phase: EnginePhase.flushSemantics);
|
|
|
|
expectedActions = SemanticsAction.tap.index | SemanticsAction.longPress.index | SemanticsAction.scrollDown.index | SemanticsAction.scrollRight.index;
|
|
expect(root.debugSemantics.getSemanticsData().actions, expectedActions);
|
|
});
|
|
});
|
|
|
|
test('toStringDeep() does not throw with transform == null', () {
|
|
final SemanticsNode child1 = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 5.0, 5.0);
|
|
final SemanticsNode child2 = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0);
|
|
final SemanticsNode root = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
|
|
root.updateWith(
|
|
config: null,
|
|
childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
|
|
);
|
|
|
|
expect(root.transform, isNull);
|
|
expect(child1.transform, isNull);
|
|
expect(child2.transform, isNull);
|
|
|
|
expect(
|
|
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
|
|
'SemanticsNode#8(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
|
|
'├SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
|
|
'└SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n',
|
|
);
|
|
});
|
|
|
|
test('toStringDeep respects childOrder parameter', () {
|
|
final SemanticsNode child1 = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(15.0, 0.0, 20.0, 5.0);
|
|
final SemanticsNode child2 = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(10.0, 0.0, 15.0, 5.0);
|
|
final SemanticsNode root = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 20.0, 5.0);
|
|
root.updateWith(
|
|
config: null,
|
|
childrenInInversePaintOrder: <SemanticsNode>[child1, child2],
|
|
);
|
|
expect(
|
|
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
|
|
'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
|
|
'├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
|
|
'└SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
|
|
);
|
|
|
|
expect(
|
|
root.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
|
|
'SemanticsNode#11(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 20.0, 5.0))\n'
|
|
'├SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
|
|
'└SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n',
|
|
);
|
|
|
|
final SemanticsNode child3 = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 10.0, 5.0);
|
|
child3.updateWith(
|
|
config: null,
|
|
childrenInInversePaintOrder: <SemanticsNode>[
|
|
new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(5.0, 0.0, 10.0, 5.0),
|
|
new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 5.0, 5.0),
|
|
],
|
|
);
|
|
|
|
final SemanticsNode rootComplex = new SemanticsNode()
|
|
..rect = new Rect.fromLTRB(0.0, 0.0, 25.0, 5.0);
|
|
rootComplex.updateWith(
|
|
config: null,
|
|
childrenInInversePaintOrder: <SemanticsNode>[child1, child2, child3]
|
|
);
|
|
|
|
expect(
|
|
rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.traversal),
|
|
'SemanticsNode#15(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
|
|
'├SemanticsNode#12(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
|
|
'│├SemanticsNode#14(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n'
|
|
'│└SemanticsNode#13(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
|
|
'├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
|
|
'└SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n',
|
|
);
|
|
|
|
expect(
|
|
rootComplex.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest),
|
|
'SemanticsNode#15(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 25.0, 5.0))\n'
|
|
'├SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(15.0, 0.0, 20.0, 5.0))\n'
|
|
'├SemanticsNode#10(STALE, owner: null, Rect.fromLTRB(10.0, 0.0, 15.0, 5.0))\n'
|
|
'└SemanticsNode#12(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 10.0, 5.0))\n'
|
|
' ├SemanticsNode#13(STALE, owner: null, Rect.fromLTRB(5.0, 0.0, 10.0, 5.0))\n'
|
|
' └SemanticsNode#14(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 5.0, 5.0))\n',
|
|
);
|
|
});
|
|
|
|
test('debug properties', () {
|
|
final SemanticsNode minimalProperties = new SemanticsNode();
|
|
expect(
|
|
minimalProperties.toStringDeep(),
|
|
'SemanticsNode#16(Rect.fromLTRB(0.0, 0.0, 0.0, 0.0))\n',
|
|
);
|
|
|
|
expect(
|
|
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
|
|
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n'
|
|
);
|
|
|
|
final SemanticsConfiguration config = new SemanticsConfiguration()
|
|
..isMergingSemanticsOfDescendants = true
|
|
..addAction(SemanticsAction.scrollUp, () { })
|
|
..addAction(SemanticsAction.longPress, () { })
|
|
..addAction(SemanticsAction.showOnScreen, () { })
|
|
..isChecked = false
|
|
..isSelected = true
|
|
..isButton = true
|
|
..label = 'Use all the properties'
|
|
..textDirection = TextDirection.rtl;
|
|
final SemanticsNode allProperties = new SemanticsNode()
|
|
..rect = new Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
|
|
..transform = new Matrix4.translation(new Vector3(10.0, 10.0, 0.0))
|
|
..updateWith(config: config, childrenInInversePaintOrder: null);
|
|
expect(
|
|
allProperties.toStringDeep(),
|
|
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl)\n',
|
|
);
|
|
expect(
|
|
allProperties.getSemanticsData().toString(),
|
|
'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [1.0,0.0,0.0,10.0; 0.0,1.0,0.0,10.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0], actions: [longPress, scrollUp, showOnScreen], flags: [hasCheckedState, isSelected, isButton], label: "Use all the properties", textDirection: rtl)',
|
|
);
|
|
|
|
final SemanticsNode scaled = new SemanticsNode()
|
|
..rect = new Rect.fromLTWH(50.0, 10.0, 20.0, 30.0)
|
|
..transform = new Matrix4.diagonal3(new Vector3(10.0, 10.0, 1.0));
|
|
expect(
|
|
scaled.toStringDeep(),
|
|
'SemanticsNode#18(STALE, owner: null, Rect.fromLTRB(50.0, 10.0, 70.0, 40.0) scaled by 10.0x)\n',
|
|
);
|
|
expect(
|
|
scaled.getSemanticsData().toString(),
|
|
'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [10.0,0.0,0.0,0.0; 0.0,10.0,0.0,0.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0])',
|
|
);
|
|
});
|
|
}
|
|
|
|
class TestRender extends RenderProxyBox {
|
|
|
|
TestRender({ this.action, this.isSemanticBoundary, RenderObject child }) : super(child);
|
|
|
|
final bool isSemanticBoundary;
|
|
|
|
SemanticsAction action;
|
|
|
|
@override
|
|
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
|
super.describeSemanticsConfiguration(config);
|
|
|
|
config
|
|
..isSemanticBoundary = isSemanticBoundary
|
|
..addAction(action, () { });
|
|
}
|
|
}
|