Skip to content

[pointer_interceptor] fix integration test #1675

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/pointer_interceptor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.3+1

* Updates example code and integration tests to accomodate hit-testing changes in the Flutter web engine.

## 0.9.3

* Require minimal version of flutter SDK to be `2.10`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,86 +11,141 @@ import 'package:integration_test/integration_test.dart';

import 'package:pointer_interceptor_example/main.dart' as app;

final Finder nonClickableButtonFinder =
find.byKey(const Key('transparent-button'));
final Finder clickableWrappedButtonFinder =
find.byKey(const Key('wrapped-transparent-button'));
final Finder clickableButtonFinder = find.byKey(const Key('clickable-button'));
final Finder backgroundFinder =
find.byKey(const ValueKey<String>('background-widget'));

void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();

group('Widget', () {
final Finder nonClickableButtonFinder =
find.byKey(const Key('transparent-button'));
final Finder clickableWrappedButtonFinder =
find.byKey(const Key('wrapped-transparent-button'));
final Finder clickableButtonFinder =
find.byKey(const Key('clickable-button'));

group('Without semantics', () {
testWidgets(
'on wrapped elements, the browser does not hit the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element? element =
_getHtmlElementFromFinder(clickableButtonFinder, tester);

if (html.document.querySelector('flt-glass-pane')?.shadowRoot != null) {
// In flutter master...
expect(element?.id, isNot('background-html-view'));
} else {
// In previous versions (--web-renderer=html only)...
expect(element?.tagName.toLowerCase(), 'flt-platform-view');
final html.Element? platformViewRoot =
element?.shadowRoot?.getElementById('background-html-view');
expect(platformViewRoot, isNull);
}
});
final html.Element element =
_getHtmlElementAtCenter(clickableButtonFinder, tester);

expect(element.id, isNot('background-html-view'));
}, semanticsEnabled: false);

testWidgets(
'on wrapped elements with intercepting set to false, the browser hits the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element? element =
_getHtmlElementFromFinder(clickableWrappedButtonFinder, tester);

if (html.document.querySelector('flt-glass-pane')?.shadowRoot != null) {
// In flutter master...
expect(element?.id, 'background-html-view');
} else {
// In previous versions (--web-renderer=html only)...
expect(element?.tagName.toLowerCase(), 'flt-platform-view');
final html.Element? platformViewRoot =
element?.shadowRoot?.getElementById('background-html-view');
expect(platformViewRoot, isNotNull);
}
});
final html.Element element =
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester);

expect(element.id, 'background-html-view');
}, semanticsEnabled: false);

testWidgets(
'on unwrapped elements, the browser hits the background-html-view',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element? element =
_getHtmlElementFromFinder(nonClickableButtonFinder, tester);

if (html.document.querySelector('flt-glass-pane')?.shadowRoot != null) {
// In flutter master...
expect(element?.id, 'background-html-view');
} else {
// In previous versions (--web-renderer=html only)...
expect(element?.tagName.toLowerCase(), 'flt-platform-view');
final html.Element? platformViewRoot =
element?.shadowRoot?.getElementById('background-html-view');
expect(platformViewRoot, isNotNull);
}
final html.Element element =
_getHtmlElementAtCenter(nonClickableButtonFinder, tester);

expect(element.id, 'background-html-view');
}, semanticsEnabled: false);

testWidgets('on background directly', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element element =
_getHtmlElementAt(tester.getTopLeft(backgroundFinder));

expect(element.id, 'background-html-view');
}, semanticsEnabled: false);
});

group('With semantics', () {
testWidgets('finds semantics of wrapped widgets',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element element =
_getHtmlElementAtCenter(clickableButtonFinder, tester);

expect(element.tagName.toLowerCase(), 'flt-semantics');
expect(element.getAttribute('aria-label'), 'Works As Expected');
});

testWidgets(
'finds semantics of wrapped widgets with intercepting set to false',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element element =
_getHtmlElementAtCenter(clickableWrappedButtonFinder, tester);

expect(element.tagName.toLowerCase(), 'flt-semantics');
expect(element.getAttribute('aria-label'),
'Never calls onPressed transparent');
});

testWidgets('finds semantics of unwrapped elements',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element element =
_getHtmlElementAtCenter(nonClickableButtonFinder, tester);

expect(element.tagName.toLowerCase(), 'flt-semantics');
expect(element.getAttribute('aria-label'), 'Never calls onPressed');
});

// Notice that, when hit-testing the background platform view, instead of
// finding a semantics node, the platform view itself is found. This is
// because the platform view does not add interactive semantics nodes into
// the framework's semantics tree. Instead, its semantics is determined by
// the HTML content of the platform view itself. Flutter's semantics tree
// simply allows the hit test to land on the platform view by making itself
// hit test transparent.
testWidgets('on background directly', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();

final html.Element element =
_getHtmlElementAt(tester.getTopLeft(backgroundFinder));

expect(element.id, 'background-html-view');
});
});
}

// This functions locates a widget from a Finder, and asks the browser what's the
// DOM element in the center of the coordinates of the widget. (Returns *which*
// DOM element will handle Mouse interactions first at those coordinates.)
html.Element? _getHtmlElementFromFinder(Finder finder, WidgetTester tester) {
// Calls [_getHtmlElementAt] passing it the center of the widget identified by
// the `finder`.
html.Element _getHtmlElementAtCenter(Finder finder, WidgetTester tester) {
final Offset point = tester.getCenter(finder);
return html.document.elementFromPoint(point.dx.toInt(), point.dy.toInt());
return _getHtmlElementAt(point);
}

// Locates the DOM element at the given `point` using `elementFromPoint`.
//
// `elementFromPoint` is an approximate proxy for a hit test, although it's
// sensitive to the presence of shadow roots and browser quirks (not all
// browsers agree on what it should return in all situations). Since this test
// runs only in Chromium, it relies on Chromium's behavior.
html.Element _getHtmlElementAt(Offset point) {
// Probe at the shadow so the browser reports semantics nodes in addition to
// platform view elements. If probed from `html.document` the browser hides
// the contents of <flt-glass-name> as an implementation detail.
final html.ShadowRoot glassPaneShadow =
html.document.querySelector('flt-glass-pane')!.shadowRoot!;
return glassPaneShadow.elementFromPoint(point.dx.toInt(), point.dy.toInt())!;
}
4 changes: 3 additions & 1 deletion packages/pointer_interceptor/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class _MyHomePageState extends State<MyHomePage> {
alignment: Alignment.center,
children: <Widget>[
HtmlElement(
key: const ValueKey<String>('background-widget'),
onClick: () {
_clickedOn('html-element');
},
Expand All @@ -134,7 +135,8 @@ class _MyHomePageState extends State<MyHomePage> {
intercepting: false,
child: ElevatedButton(
key: const Key('wrapped-transparent-button'),
child: const Text('Never calls onPressed'),
child:
const Text('Never calls onPressed transparent'),
onPressed: () {
_clickedOn('wrapped-transparent-button');
},
Expand Down
2 changes: 1 addition & 1 deletion packages/pointer_interceptor/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: pointer_interceptor
description: A widget to prevent clicks from being swallowed by underlying HtmlElementViews on the web.
repository: https://github.com/flutter/packages/tree/main/packages/pointer_interceptor
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+pointer_interceptor%22
version: 0.9.3
version: 0.9.3+1

environment:
sdk: ">=2.12.0 <3.0.0"
Expand Down