flutter_flutter/packages/flutter/test/widgets/multi_view_testing.dart
David Iglesias f9a76aea87
Throw StateError when implicitView is null on wrapWithDefaultView. (#155734)
This PR tweaks `wrapWithDefaultView` (used by `runApp`) to raise a StateError with a legible error message when the `platformDispatcher.implicitView` is missing (for example, when enabling multi-view embedding on the web), instead of crashing with an unexpected `nullCheck` a few lines below.

* Before:
  <img width="619" alt="Screenshot 2024-09-25 at 7 33 47 PM" src="https://github.com/user-attachments/assets/4897dd3c-bdd0-4217-9f23-7eee9fab4999">

* After:
  <img width="613" alt="Screenshot 2024-09-26 at 5 01 49 PM" src="https://github.com/user-attachments/assets/3febb91d-a8c3-41b6-bf34-c2c8743b637c">

## Issues

* Fixes https://github.com/flutter/flutter/issues/153198

## Tests

Added a test to ensure the assertion is thrown when the `implicitView` is missing. Had to hack a little because I couldn't find any clean way of overriding the `implicitView`. The problem is that the flutter_test bindings [use `runApp` internally](8925e1ffdf/packages/flutter_test/lib/src/binding.dart (L1020)) a couple of times, so I can only disable the implicitView inside the test body (and must re-enable it before returning). Not sure if it's the best way, but it seems to do the trick for this simple test case!
2024-09-27 00:55:31 +00:00

79 lines
2.9 KiB
Dart

// 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';
import 'package:flutter_test/flutter_test.dart';
class FakeView extends TestFlutterView {
FakeView(FlutterView view, { this.viewId = 100 }) : super(
view: view,
platformDispatcher: view.platformDispatcher as TestPlatformDispatcher,
display: view.display as TestDisplay,
);
@override
final int viewId;
@override
void render(Scene scene, {Size? size}) {
// Do not render the scene in the engine. The engine only observes one
// instance of FlutterView (the _view), and it is generally expected that
// the framework will render no more than one `Scene` per frame.
}
@override
void updateSemantics(SemanticsUpdate update) {
// Do not send the update to the engine. The engine only observes one
// instance of FlutterView (the _view). Sending semantic updates meant for
// different views to the same engine view does not work as the updates do
// not produce consistent semantics trees.
}
}
/// A test platform dispatcher that can show/hide its underlying `implicitView`,
/// depending on the value of the [implicitViewHidden] flag.
class NoImplicitViewPlatformDispatcher extends TestPlatformDispatcher {
NoImplicitViewPlatformDispatcher({ required super.platformDispatcher }) : superPlatformDispatcher = platformDispatcher;
final PlatformDispatcher superPlatformDispatcher;
bool implicitViewHidden = false;
@override
TestFlutterView? get implicitView {
return implicitViewHidden
? null
: superPlatformDispatcher.implicitView as TestFlutterView?;
}
}
/// Test Flutter Bindings that allow tests to hide/show the `implicitView`
/// of their [NoImplicitViewPlatformDispatcher] `platformDispatcher`.
///
/// This is used to test that [runApp] throws an assertion error with an
/// explanation when used when the `implicitView` is disabled (like in Flutter
/// web when multi-view is enabled).
///
/// Because of how [testWidgets] uses `runApp` internally to manage the lifecycle
/// of a test, the implicitView must be disabled/reenabled inside of the body of
/// the [WidgetTesterCallback] under test. In practice: the implicitView is disabled
/// in the first line of the test, and reenabled in the last.
///
/// See: multi_view_no_implicitView_binding_test.dart
class NoImplicitViewWidgetsBinding extends AutomatedTestWidgetsFlutterBinding {
late final NoImplicitViewPlatformDispatcher _platformDispatcher = NoImplicitViewPlatformDispatcher(platformDispatcher: super.platformDispatcher);
@override
NoImplicitViewPlatformDispatcher get platformDispatcher => _platformDispatcher;
void hideImplicitView() {
platformDispatcher.implicitViewHidden = true;
}
void showImplicitView() {
platformDispatcher.implicitViewHidden = false;
}
}