| // Copyright 2013 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 'package:test/bootstrap/browser.dart'; |
| import 'package:test/test.dart'; |
| import 'package:ui/src/engine/browser_detection.dart'; |
| import 'package:ui/src/engine/dom.dart'; |
| import 'package:ui/src/engine/pointer_binding.dart'; |
| import 'package:ui/src/engine/semantics.dart'; |
| |
| const PointerSupportDetector _defaultSupportDetector = PointerSupportDetector(); |
| |
| void main() { |
| internalBootstrapBrowserTest(() => testMain); |
| } |
| |
| void testMain() { |
| group('$DesktopSemanticsEnabler', () { |
| late DesktopSemanticsEnabler desktopSemanticsEnabler; |
| late DomElement? placeholder; |
| |
| setUp(() { |
| EngineSemanticsOwner.instance.semanticsEnabled = false; |
| desktopSemanticsEnabler = DesktopSemanticsEnabler(); |
| placeholder = desktopSemanticsEnabler.prepareAccessibilityPlaceholder(); |
| domDocument.body!.append(placeholder!); |
| }); |
| |
| tearDown(() { |
| expect(placeholder, isNotNull, |
| reason: 'Expected the test to create a placeholder'); |
| placeholder!.remove(); |
| EngineSemanticsOwner.instance.semanticsEnabled = false; |
| }); |
| |
| test('prepare accessibility placeholder', () async { |
| expect(placeholder!.getAttribute('role'), 'button'); |
| expect(placeholder!.getAttribute('aria-live'), 'polite'); |
| expect(placeholder!.getAttribute('tabindex'), '0'); |
| |
| domDocument.body!.append(placeholder!); |
| |
| expect(domDocument.getElementsByTagName('flt-semantics-placeholder'), |
| isNotEmpty); |
| |
| expect(placeholder!.getBoundingClientRect().height, 1); |
| expect(placeholder!.getBoundingClientRect().width, 1); |
| expect(placeholder!.getBoundingClientRect().top, -1); |
| expect(placeholder!.getBoundingClientRect().left, -1); |
| }); |
| |
| test('Not relevant events should be forwarded to the framework', () async { |
| // Attach the placeholder to dom. |
| domDocument.body!.append(placeholder!); |
| |
| DomEvent event = createDomEvent('Event', 'mousemove'); |
| bool shouldForwardToFramework = |
| desktopSemanticsEnabler.tryEnableSemantics(event); |
| |
| expect(shouldForwardToFramework, isTrue); |
| |
| // Pointer events are not defined in webkit. |
| if (browserEngine != BrowserEngine.webkit) { |
| event = createDomEvent('Event', 'pointermove'); |
| shouldForwardToFramework = |
| desktopSemanticsEnabler.tryEnableSemantics(event); |
| |
| expect(shouldForwardToFramework, isTrue); |
| } |
| }); |
| |
| test( |
| 'Relevant events targeting placeholder should not be forwarded to the framework', |
| () async { |
| final DomEvent event = createDomEvent('Event', 'mousedown'); |
| placeholder!.dispatchEvent(event); |
| |
| final bool shouldForwardToFramework = |
| desktopSemanticsEnabler.tryEnableSemantics(event); |
| |
| expect(shouldForwardToFramework, isFalse); |
| }); |
| |
| test('disposes of the placeholder', () { |
| domDocument.body!.append(placeholder!); |
| |
| expect(placeholder!.isConnected, isTrue); |
| desktopSemanticsEnabler.dispose(); |
| expect(placeholder!.isConnected, isFalse); |
| }); |
| }, skip: isMobile); |
| |
| group( |
| '$MobileSemanticsEnabler', |
| () { |
| late MobileSemanticsEnabler mobileSemanticsEnabler; |
| DomElement? placeholder; |
| |
| setUp(() { |
| EngineSemanticsOwner.instance.semanticsEnabled = false; |
| mobileSemanticsEnabler = MobileSemanticsEnabler(); |
| placeholder = mobileSemanticsEnabler.prepareAccessibilityPlaceholder(); |
| domDocument.body!.append(placeholder!); |
| }); |
| |
| tearDown(() { |
| placeholder!.remove(); |
| EngineSemanticsOwner.instance.semanticsEnabled = false; |
| }); |
| |
| test('prepare accessibility placeholder', () async { |
| expect(placeholder!.getAttribute('role'), 'button'); |
| |
| // Placeholder should cover all the screen on a mobile device. |
| final num bodyHeight = domWindow.innerHeight!; |
| final num bodyWidth = domWindow.innerWidth!; |
| |
| expect(placeholder!.getBoundingClientRect().height, bodyHeight); |
| expect(placeholder!.getBoundingClientRect().width, bodyWidth); |
| }); |
| |
| test('Non-relevant events should be forwarded to the framework', |
| () async { |
| DomEvent event; |
| if (_defaultSupportDetector.hasPointerEvents) { |
| event = createDomPointerEvent('pointermove'); |
| } else if (_defaultSupportDetector.hasTouchEvents) { |
| event = createDomTouchEvent('touchcancel'); |
| } else { |
| event = createDomMouseEvent('mousemove'); |
| } |
| |
| final bool shouldForwardToFramework = |
| mobileSemanticsEnabler.tryEnableSemantics(event); |
| |
| expect(shouldForwardToFramework, isTrue); |
| }); |
| |
| test('Enables semantics when receiving a relevant event', () { |
| expect(mobileSemanticsEnabler.semanticsActivationTimer, isNull); |
| |
| // Send a click off center |
| placeholder!.dispatchEvent(createDomMouseEvent( |
| 'click', |
| <Object?, Object?>{ |
| 'clientX': 0, |
| 'clientY': 0, |
| } |
| )); |
| expect(mobileSemanticsEnabler.semanticsActivationTimer, isNull); |
| |
| // Send a click at center |
| final DomRect activatingElementRect = |
| placeholder!.getBoundingClientRect(); |
| final int midX = (activatingElementRect.left + |
| (activatingElementRect.right - activatingElementRect.left) / 2) |
| .toInt(); |
| final int midY = (activatingElementRect.top + |
| (activatingElementRect.bottom - activatingElementRect.top) / 2) |
| .toInt(); |
| placeholder!.dispatchEvent(createDomMouseEvent( |
| 'click', |
| <Object?, Object?>{ |
| 'clientX': midX, |
| 'clientY': midY, |
| } |
| )); |
| expect(mobileSemanticsEnabler.semanticsActivationTimer, isNotNull); |
| }); |
| }, |
| // We can run `MobileSemanticsEnabler` tests in mobile browsers and in desktop Chrome. |
| skip: isDesktop && browserEngine != BrowserEngine.blink, |
| ); |
| } |