| // 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. |
| |
| import 'dart:ui' show window; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| testWidgets('SemanticsDebugger will schedule a frame', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| SemanticsDebugger( |
| child: Container(), |
| ), |
| ); |
| |
| expect(tester.binding.hasScheduledFrame, isTrue); |
| }); |
| |
| testWidgets('SemanticsDebugger smoke test', (WidgetTester tester) async { |
| |
| // This is a smoketest to verify that adding a debugger doesn't crash. |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Stack( |
| children: <Widget>[ |
| Semantics(), |
| Semantics( |
| container: true, |
| ), |
| Semantics( |
| label: 'label', |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Stack( |
| children: <Widget>[ |
| Semantics(), |
| Semantics( |
| container: true, |
| ), |
| Semantics( |
| label: 'label', |
| textDirection: TextDirection.ltr, |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| expect(true, isTrue); // expect that we reach here without crashing |
| }); |
| |
| testWidgets('SemanticsDebugger reparents subtree', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Stack( |
| children: <Widget>[ |
| Semantics(label: 'label1', textDirection: TextDirection.ltr), |
| Positioned( |
| key: key, |
| left: 0.0, |
| top: 0.0, |
| width: 100.0, |
| height: 100.0, |
| child: Semantics(label: 'label2', textDirection: TextDirection.ltr), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Stack( |
| children: <Widget>[ |
| Semantics(label: 'label1', textDirection: TextDirection.ltr), |
| Semantics( |
| container: true, |
| child: Stack( |
| children: <Widget>[ |
| Positioned( |
| key: key, |
| left: 0.0, |
| top: 0.0, |
| width: 100.0, |
| height: 100.0, |
| child: Semantics(label: 'label2', textDirection: TextDirection.ltr), |
| ), |
| Semantics(label: 'label3', textDirection: TextDirection.ltr), |
| ], |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Stack( |
| children: <Widget>[ |
| Semantics(label: 'label1', textDirection: TextDirection.ltr), |
| Semantics( |
| container: true, |
| child: Stack( |
| children: <Widget>[ |
| Positioned( |
| key: key, |
| left: 0.0, |
| top: 0.0, |
| width: 100.0, |
| height: 100.0, |
| child: Semantics(label: 'label2', textDirection: TextDirection.ltr), |
| ), |
| Semantics(label: 'label3', textDirection: TextDirection.ltr), |
| Semantics(label: 'label4', textDirection: TextDirection.ltr), |
| ], |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('SemanticsDebugger interaction test', (WidgetTester tester) async { |
| final List<String> log = <String>[]; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Material( |
| child: ListView( |
| children: <Widget>[ |
| ElevatedButton( |
| onPressed: () { |
| log.add('top'); |
| }, |
| child: const Text('TOP'), |
| ), |
| ElevatedButton( |
| onPressed: () { |
| log.add('bottom'); |
| }, |
| child: const Text('BOTTOM'), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.text('TOP'), warnIfMissed: false); // hitting the debugger |
| expect(log, equals(<String>['top'])); |
| log.clear(); |
| |
| await tester.tap(find.text('BOTTOM'), warnIfMissed: false); // hitting the debugger |
| expect(log, equals(<String>['bottom'])); |
| log.clear(); |
| }); |
| |
| testWidgets('SemanticsDebugger interaction test - negative', (WidgetTester tester) async { |
| final List<String> log = <String>[]; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Material( |
| child: ListView( |
| children: <Widget>[ |
| ElevatedButton( |
| onPressed: () { |
| log.add('top'); |
| }, |
| child: const Text('TOP', textDirection: TextDirection.ltr), |
| ), |
| ExcludeSemantics( |
| child: ElevatedButton( |
| onPressed: () { |
| log.add('bottom'); |
| }, |
| child: const Text('BOTTOM', textDirection: TextDirection.ltr), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.text('TOP'), warnIfMissed: false); // hitting the debugger |
| expect(log, equals(<String>['top'])); |
| log.clear(); |
| |
| await tester.tap(find.text('BOTTOM'), warnIfMissed: false); // hitting the debugger |
| expect(log, equals(<String>[])); |
| log.clear(); |
| }); |
| |
| testWidgets('SemanticsDebugger scroll test', (WidgetTester tester) async { |
| final Key childKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: ListView( |
| children: <Widget>[ |
| Container( |
| key: childKey, |
| height: 5000.0, |
| color: Colors.green[500], |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(0.0)); |
| |
| await tester.fling(find.byType(ListView), const Offset(0.0, -200.0), 200.0, warnIfMissed: false); // hitting the debugger); |
| await tester.pump(); |
| |
| expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(-480.0)); |
| |
| await tester.fling(find.byType(ListView), const Offset(200.0, 0.0), 200.0, warnIfMissed: false); // hitting the debugger); |
| await tester.pump(); |
| |
| expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(-480.0)); |
| |
| await tester.fling(find.byType(ListView), const Offset(-200.0, 0.0), 200.0, warnIfMissed: false); // hitting the debugger); |
| await tester.pump(); |
| |
| expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(-480.0)); |
| |
| await tester.fling(find.byType(ListView), const Offset(0.0, 200.0), 200.0, warnIfMissed: false); // hitting the debugger); |
| await tester.pump(); |
| |
| expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(0.0)); |
| }); |
| |
| testWidgets('SemanticsDebugger long press', (WidgetTester tester) async { |
| bool didLongPress = false; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: GestureDetector( |
| onLongPress: () { |
| expect(didLongPress, isFalse); |
| didLongPress = true; |
| }, |
| child: const Text('target', textDirection: TextDirection.ltr), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.longPress(find.text('target'), warnIfMissed: false); // hitting the debugger |
| expect(didLongPress, isTrue); |
| }); |
| |
| testWidgets('SemanticsDebugger slider', (WidgetTester tester) async { |
| double value = 0.75; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: MediaQueryData.fromWindow(window), |
| child: Material( |
| child: Center( |
| child: Slider( |
| value: value, |
| onChanged: (double newValue) { |
| value = newValue; |
| }, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // The fling below must be such that the velocity estimation examines an |
| // offset greater than the kTouchSlop. Too slow or too short a distance, and |
| // it won't trigger. The actual distance moved doesn't matter since this is |
| // interpreted as a gesture by the semantics debugger and sent to the widget |
| // as a semantic action that always moves by 10% of the complete track. |
| await tester.fling(find.byType(Slider), const Offset(-100.0, 0.0), 2000.0, warnIfMissed: false); // hitting the debugger |
| expect(value, equals(0.70)); |
| }); |
| |
| testWidgets('SemanticsDebugger checkbox', (WidgetTester tester) async { |
| final Key keyTop = UniqueKey(); |
| final Key keyBottom = UniqueKey(); |
| |
| bool? valueTop = false; |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| child: Material( |
| child: ListView( |
| children: <Widget>[ |
| Checkbox( |
| key: keyTop, |
| value: valueTop, |
| onChanged: (bool? newValue) { |
| valueTop = newValue; |
| }, |
| ), |
| Checkbox( |
| key: keyBottom, |
| value: false, |
| onChanged: null, |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.byKey(keyTop), warnIfMissed: false); // hitting the debugger |
| expect(valueTop, isTrue); |
| valueTop = false; |
| expect(valueTop, isFalse); |
| |
| await tester.tap(find.byKey(keyBottom), warnIfMissed: false); // hitting the debugger |
| expect(valueTop, isFalse); |
| }); |
| |
| testWidgets('SemanticsDebugger checkbox message', (WidgetTester tester) async { |
| final Key checkbox = UniqueKey(); |
| final Key checkboxUnchecked = UniqueKey(); |
| final Key checkboxDisabled = UniqueKey(); |
| final Key checkboxDisabledUnchecked = UniqueKey(); |
| final Key debugger = UniqueKey(); |
| |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| key: debugger, |
| child: Material( |
| child: ListView( |
| children: <Widget>[ |
| Semantics( |
| container: true, |
| key: checkbox, |
| child: Checkbox( |
| value: true, |
| onChanged: (bool? _) { }, |
| ), |
| ), |
| Semantics( |
| container: true, |
| key: checkboxUnchecked, |
| child: Checkbox( |
| value: false, |
| onChanged: (bool? _) { }, |
| ), |
| ), |
| Semantics( |
| container: true, |
| key: checkboxDisabled, |
| child: const Checkbox( |
| value: true, |
| onChanged: null, |
| ), |
| ), |
| Semantics( |
| container: true, |
| key: checkboxDisabledUnchecked, |
| child: const Checkbox( |
| value: false, |
| onChanged: null, |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect( |
| _getMessageShownInSemanticsDebugger(widgetKey: checkbox, debuggerKey: debugger, tester: tester), |
| 'checked', |
| ); |
| expect( |
| _getMessageShownInSemanticsDebugger(widgetKey: checkboxUnchecked, debuggerKey: debugger, tester: tester), |
| 'unchecked', |
| ); |
| expect( |
| _getMessageShownInSemanticsDebugger(widgetKey: checkboxDisabled, debuggerKey: debugger, tester: tester), |
| 'checked; disabled', |
| ); |
| expect( |
| _getMessageShownInSemanticsDebugger(widgetKey: checkboxDisabledUnchecked, debuggerKey: debugger, tester: tester), |
| 'unchecked; disabled', |
| ); |
| }); |
| |
| testWidgets('SemanticsDebugger textfield', (WidgetTester tester) async { |
| final UniqueKey textField = UniqueKey(); |
| final UniqueKey debugger = UniqueKey(); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: SemanticsDebugger( |
| key: debugger, |
| child: Material( |
| child: TextField( |
| key: textField, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final dynamic semanticsDebuggerPainter = _getSemanticsDebuggerPainter(debuggerKey: debugger, tester: tester); |
| final RenderObject renderTextfield = tester.renderObject(find.descendant(of: find.byKey(textField), matching: find.byType(Semantics)).first); |
| |
| expect( |
| // ignore: avoid_dynamic_calls |
| semanticsDebuggerPainter.getMessage(renderTextfield.debugSemantics), |
| 'textfield', |
| ); |
| }); |
| |
| testWidgets('SemanticsDebugger label style is used in the painter.', (WidgetTester tester) async { |
| final UniqueKey debugger = UniqueKey(); |
| const TextStyle labelStyle = TextStyle(color: Colors.amber); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: SemanticsDebugger( |
| key: debugger, |
| labelStyle: labelStyle, |
| child: Semantics( |
| label: 'label', |
| textDirection: TextDirection.ltr, |
| ), |
| ), |
| ), |
| ); |
| |
| // ignore: avoid_dynamic_calls |
| expect(_getSemanticsDebuggerPainter(debuggerKey: debugger, tester: tester).labelStyle, labelStyle); |
| }); |
| } |
| |
| String _getMessageShownInSemanticsDebugger({ |
| required Key widgetKey, |
| required Key debuggerKey, |
| required WidgetTester tester, |
| }) { |
| final dynamic semanticsDebuggerPainter = _getSemanticsDebuggerPainter(debuggerKey: debuggerKey, tester: tester); |
| // ignore: avoid_dynamic_calls |
| return semanticsDebuggerPainter.getMessage(tester.renderObject(find.byKey(widgetKey)).debugSemantics) as String; |
| } |
| |
| dynamic _getSemanticsDebuggerPainter({ |
| required Key debuggerKey, |
| required WidgetTester tester, |
| }) { |
| final CustomPaint customPaint = tester.widgetList(find.descendant( |
| of: find.byKey(debuggerKey), |
| matching: find.byType(CustomPaint), |
| )).first as CustomPaint; |
| final dynamic semanticsDebuggerPainter = customPaint.foregroundPainter; |
| expect(semanticsDebuggerPainter.runtimeType.toString(), '_SemanticsDebuggerPainter'); |
| return semanticsDebuggerPainter; |
| } |