blob: cbbb296df339f1b6c0752d52e041c2c5bd32d1b2 [file] [log] [blame] [edit]
// 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.
@TestOn('chrome')
library;
import 'dart:async';
import 'dart:ui_web' as ui_web;
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:web/web.dart' as web;
import 'web_platform_view_registry_utils.dart';
final Object _mockHtmlElement = Object();
Object _mockViewFactory(int id, {Object? params}) {
return _mockHtmlElement;
}
void main() {
late FakePlatformViewRegistry fakePlatformViewRegistry;
setUp(() {
fakePlatformViewRegistry = FakePlatformViewRegistry();
// Simulate the engine registering default factories.
fakePlatformViewRegistry.registerViewFactory(
ui_web.PlatformViewRegistry.defaultVisibleViewType,
(int viewId, {Object? params}) {
params!;
params as Map<Object?, Object?>;
return web.document.createElement(params['tagName']! as String);
},
);
fakePlatformViewRegistry.registerViewFactory(
ui_web.PlatformViewRegistry.defaultInvisibleViewType,
(int viewId, {Object? params}) {
params!;
params as Map<Object?, Object?>;
return web.document.createElement(params['tagName']! as String);
},
);
});
group('HtmlElementView', () {
testWidgets('Create HTML view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(width: 200.0, height: 100.0, child: HtmlElementView(viewType: 'webview')),
),
);
expect(
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Create HTML view with PlatformViewCreatedCallback', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
bool hasPlatformViewCreated = false;
void onPlatformViewCreatedCallBack(int id) {
hasPlatformViewCreated = true;
}
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView(
viewType: 'webview',
onPlatformViewCreated: onPlatformViewCreatedCallBack,
),
),
),
);
// Check the onPlatformViewCreatedCallBack has been called.
expect(hasPlatformViewCreated, true);
expect(
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Create HTML view with creation params', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Column(
children: <Widget>[
SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView(viewType: 'webview', creationParams: 'foobar'),
),
SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView(viewType: 'webview', creationParams: 123),
),
],
),
);
expect(
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(
id: currentViewId + 1,
viewType: 'webview',
params: 'foobar',
htmlElement: _mockHtmlElement,
),
(id: currentViewId + 2, viewType: 'webview', params: 123, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Resize HTML view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(width: 200.0, height: 100.0, child: HtmlElementView(viewType: 'webview')),
),
);
final Completer<void> resizeCompleter = Completer<void>();
await tester.pumpWidget(
const Center(
child: SizedBox(width: 100.0, height: 50.0, child: HtmlElementView(viewType: 'webview')),
),
);
resizeCompleter.complete();
await tester.pump();
expect(
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Change HTML view type', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
fakePlatformViewRegistry.registerViewFactory('maps', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(width: 200.0, height: 100.0, child: HtmlElementView(viewType: 'webview')),
),
);
await tester.pumpWidget(
const Center(
child: SizedBox(width: 200.0, height: 100.0, child: HtmlElementView(viewType: 'maps')),
),
);
expect(
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 2, viewType: 'maps', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('Dispose HTML view', (WidgetTester tester) async {
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
const Center(
child: SizedBox(width: 200.0, height: 100.0, child: HtmlElementView(viewType: 'webview')),
),
);
await tester.pumpWidget(const Center(child: SizedBox(width: 200.0, height: 100.0)));
expect(fakePlatformViewRegistry.views, isEmpty);
});
testWidgets('HTML view survives widget tree change', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
final GlobalKey key = GlobalKey();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView(viewType: 'webview', key: key),
),
),
);
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView(viewType: 'webview', key: key),
),
),
);
expect(
fakePlatformViewRegistry.views,
unorderedEquals(<FakePlatformView>[
(id: currentViewId + 1, viewType: 'webview', params: null, htmlElement: _mockHtmlElement),
]),
);
});
testWidgets('HtmlElementView has correct semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
expect(currentViewId, greaterThanOrEqualTo(0));
fakePlatformViewRegistry.registerViewFactory('webview', _mockViewFactory);
await tester.pumpWidget(
Semantics(
container: true,
child: const Align(
alignment: Alignment.bottomRight,
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView(viewType: 'webview'),
),
),
),
);
// First frame is before the platform view was created so the render object
// is not yet in the tree.
await tester.pump();
// The platform view ID is set on the child of the HtmlElementView render object.
final SemanticsNode semantics = tester.getSemantics(find.byType(PlatformViewSurface));
expect(semantics.platformViewId, currentViewId + 1);
expect(semantics.rect, const Rect.fromLTWH(0, 0, 200, 100));
// A 200x100 rect positioned at bottom right of a 800x600 box.
expect(semantics.transform, Matrix4.translationValues(600, 500, 0));
expect(semantics.childrenCount, 0);
handle.dispose();
});
});
group('HtmlElementView.fromTagName', () {
setUp(() {
ui_web.debugOverridePlatformViewRegistry(fakePlatformViewRegistry);
});
tearDown(() {
ui_web.debugOverridePlatformViewRegistry(null);
});
testWidgets('Create platform view from tagName', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView.fromTagName(tagName: 'div'),
),
),
);
await tester.pumpAndSettle();
expect(fakePlatformViewRegistry.views, hasLength(1));
final FakePlatformView fakePlatformView = fakePlatformViewRegistry.views.single;
expect(fakePlatformView.id, currentViewId + 1);
expect(fakePlatformView.viewType, ui_web.PlatformViewRegistry.defaultVisibleViewType);
expect(fakePlatformView.params, <dynamic, dynamic>{'tagName': 'div'});
// The HTML element should be a div.
final web.HTMLElement htmlElement = fakePlatformView.htmlElement as web.HTMLElement;
expect(htmlElement.tagName, equalsIgnoringCase('div'));
});
testWidgets('Create invisible platform view', (WidgetTester tester) async {
final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView.fromTagName(tagName: 'script', isVisible: false),
),
),
);
await tester.pumpAndSettle();
expect(fakePlatformViewRegistry.views, hasLength(1));
final FakePlatformView fakePlatformView = fakePlatformViewRegistry.views.single;
expect(fakePlatformView.id, currentViewId + 1);
// The view should be invisible.
expect(fakePlatformView.viewType, ui_web.PlatformViewRegistry.defaultInvisibleViewType);
expect(fakePlatformView.params, <dynamic, dynamic>{'tagName': 'script'});
// The HTML element should be a script.
final web.HTMLElement htmlElement = fakePlatformView.htmlElement as web.HTMLElement;
expect(htmlElement.tagName, equalsIgnoringCase('script'));
});
testWidgets('onElementCreated', (WidgetTester tester) async {
final List<Object> createdElements = <Object>[];
void onElementCreated(Object element) {
createdElements.add(element);
}
await tester.pumpWidget(
Center(
child: SizedBox(
width: 200.0,
height: 100.0,
child: HtmlElementView.fromTagName(
tagName: 'table',
onElementCreated: onElementCreated,
),
),
),
);
await tester.pumpAndSettle();
expect(fakePlatformViewRegistry.views, hasLength(1));
final FakePlatformView fakePlatformView = fakePlatformViewRegistry.views.single;
expect(createdElements, hasLength(1));
final Object createdElement = createdElements.single;
expect(createdElement, fakePlatformView.htmlElement);
});
group('hitTestBehavior', () {
testWidgets('opaque by default', (WidgetTester tester) async {
final Key containerKey = UniqueKey();
int taps = 0;
await tester.pumpWidget(
GestureDetector(
onTap: () => taps++,
child: Container(
key: containerKey,
width: 200,
height: 200,
// Add a color to make it a visible container. This ensures that
// GestureDetector's default hit test behavior works.
color: const Color(0xFF00FF00),
child: HtmlElementView.fromTagName(tagName: 'div'),
),
),
);
await tester.pumpAndSettle();
expect(taps, isZero);
await tester.tap(find.byKey(containerKey), warnIfMissed: false);
// Taps are still zero on the container because the HtmlElementView is
// opaque and prevents widgets behind it from receiving pointer events.
expect(taps, isZero);
});
testWidgets('can be set to transparent', (WidgetTester tester) async {
final Key containerKey = UniqueKey();
int taps = 0;
await tester.pumpWidget(
GestureDetector(
onTap: () => taps++,
child: Container(
key: containerKey,
width: 200,
height: 200,
// Add a color to make it a visible container. This ensures that
// GestureDetector's default hit test behavior works.
color: const Color(0xFF00FF00),
child: HtmlElementView.fromTagName(
tagName: 'div',
hitTestBehavior: PlatformViewHitTestBehavior.transparent,
),
),
),
);
await tester.pumpAndSettle();
expect(taps, isZero);
await tester.tap(find.byKey(containerKey), warnIfMissed: false);
// The container can receive taps because the HtmlElementView is
// transparent from a hit testing perspective.
expect(taps, 1);
});
});
});
}