blob: 81f98bd149489bd196f6439b513ebc42f3a7105d [file]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@Tags(<String>['reduced-test-set'])
@TestOn('!chrome')
library;
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/src/widgets/accessibility_inspector.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('ext.flutter.accessibility.getSemanticsTree', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Semantics(
label: 'Root Node',
container: true,
explicitChildNodes: true,
child: Column(
children: <Widget>[
Semantics(
label: 'Child Node 1',
button: true,
tooltip: 'This is a tooltip',
child: const Text('Button 1'),
),
Semantics(
label: 'Child Node 2',
value: '42',
increasedValue: '43',
decreasedValue: '41',
onIncrease: () {},
onDecrease: () {},
child: const Text('Value 2'),
),
Transform.scale(
scale: 2.0,
child: Semantics(label: 'Child Node 3', child: const Text('Scaled')),
),
],
),
),
),
);
final accessibilityExtensions = <String, ServiceExtensionCallback>{};
AccessibilityInspector.instance.initServiceExtensions(({
required String name,
required ServiceExtensionCallback callback,
}) {
accessibilityExtensions[name] = callback;
});
Future<Map<String, Object?>> callExtension(String name) async {
return json.decode(
json.encode(await accessibilityExtensions[name]!(const <String, String>{})),
)
as Map<String, Object?>;
}
// Calling getSemanticsTree before semantics is enabled returns an error.
final Map<String, Object?> disabledResult = await callExtension(
AccessibilityServiceExtensions.getSemanticsTree.extensionName,
);
expect(disabledResult['error'], equals('Semantics not enabled.'));
expect(disabledResult['needsFrame'], isNull);
// Calling enableSemantics enables semantics without returning the tree.
// Ensure the returned map is mutable (required by BindingBase.registerServiceExtension).
final Map<String, Object?> enableResult =
await accessibilityExtensions[AccessibilityServiceExtensions
.enableSemantics
.extensionName]!(const <String, String>{});
expect(enableResult, isEmpty);
expect(() => enableResult['type'] = '_extensionType', returnsNormally);
// Calling getSemanticsTree schedules a frame and returns an error map indicating root is null.
final Map<String, Object?> result1 = await callExtension(
AccessibilityServiceExtensions.getSemanticsTree.extensionName,
);
expect(result1['error'], equals('rootSemanticsNode is null'));
expect(result1['needsFrame'], isTrue);
// Pump a frame to build/flush the semantics tree.
await tester.pump();
// The second call returns the populated semantics tree.
final Map<String, Object?> result2 = await callExtension(
AccessibilityServiceExtensions.getSemanticsTree.extensionName,
);
expect(result2['error'], isNull);
expect(result2['data'], isA<Map<String, Object?>>());
final nodes = result2['data']! as Map<String, Object?>;
expect(nodes, isNotEmpty);
Map<String, Object?> findNodeWithLabel(Map<String, Object?> nodes, String label) {
for (final Object? value in nodes.values) {
final node = value! as Map<String, Object?>;
if ((node['label']! as String).contains(label)) {
return node;
}
}
return const <String, Object?>{};
}
final Map<String, Object?> rootNode = findNodeWithLabel(nodes, 'Root Node');
expect(rootNode, isNotEmpty);
expect(rootNode['id'], isNotNull);
final Map<String, Object?> child1 = findNodeWithLabel(nodes, 'Child Node 1');
expect(child1, isNotEmpty);
expect(child1['flags']! as List<Object?>, contains('isButton'));
expect(child1['tooltip'], equals('This is a tooltip'));
final Map<String, Object?> child2 = findNodeWithLabel(nodes, 'Child Node 2');
expect(child2, isNotEmpty);
expect(child2['value'], equals('42'));
expect(child2['increasedValue'], equals('43'));
expect(child2['decreasedValue'], equals('41'));
expect(child2['actions']! as List<Object?>, contains('increase'));
expect(child2['actions']! as List<Object?>, contains('decrease'));
final Map<String, Object?> child3 = findNodeWithLabel(nodes, 'Child Node 3');
expect(child3, isNotEmpty);
expect(child3['transform'], isNotNull);
final transform = child3['transform']! as List<Object?>;
expect(transform, hasLength(16));
expect(transform[0], equals(2.0));
expect(
rootNode['childrenInTraversalOrder']! as List<Object?>,
containsAll(<Object?>[child1['id'], child2['id'], child3['id']]),
);
expect(
rootNode['childrenInHitTestOrder']! as List<Object?>,
containsAll(<Object?>[child1['id'], child2['id'], child3['id']]),
);
// Calling disposeSemantics succeeds and cleans up semantics handle.
// Ensure the returned map is mutable.
final Map<String, Object?> disposeResult =
await accessibilityExtensions[AccessibilityServiceExtensions
.disposeSemantics
.extensionName]!(const <String, String>{});
expect(disposeResult, isEmpty);
expect(() => disposeResult['type'] = '_extensionType', returnsNormally);
AccessibilityInspector.instance.resetAllState();
}, semanticsEnabled: false);
}